mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00:00
empty? rewrite. (#2641)
This commit is contained in:
parent
df07be6a42
commit
5d945ef869
12 changed files with 498 additions and 338 deletions
|
@ -196,7 +196,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||||
whole_stream_command(Each),
|
whole_stream_command(Each),
|
||||||
whole_stream_command(EachGroup),
|
whole_stream_command(EachGroup),
|
||||||
whole_stream_command(EachWindow),
|
whole_stream_command(EachWindow),
|
||||||
whole_stream_command(IsEmpty),
|
whole_stream_command(Empty),
|
||||||
// Table manipulation
|
// Table manipulation
|
||||||
whole_stream_command(Move),
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Merge),
|
whole_stream_command(Merge),
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub(crate) mod drop;
|
||||||
pub(crate) mod du;
|
pub(crate) mod du;
|
||||||
pub(crate) mod each;
|
pub(crate) mod each;
|
||||||
pub(crate) mod echo;
|
pub(crate) mod echo;
|
||||||
|
pub(crate) mod empty;
|
||||||
pub(crate) mod enter;
|
pub(crate) mod enter;
|
||||||
pub(crate) mod every;
|
pub(crate) mod every;
|
||||||
pub(crate) mod exec;
|
pub(crate) mod exec;
|
||||||
|
@ -67,7 +68,6 @@ pub(crate) mod history;
|
||||||
pub(crate) mod if_;
|
pub(crate) mod if_;
|
||||||
pub(crate) mod insert;
|
pub(crate) mod insert;
|
||||||
pub(crate) mod into_int;
|
pub(crate) mod into_int;
|
||||||
pub(crate) mod is_empty;
|
|
||||||
pub(crate) mod keep;
|
pub(crate) mod keep;
|
||||||
pub(crate) mod last;
|
pub(crate) mod last;
|
||||||
pub(crate) mod lines;
|
pub(crate) mod lines;
|
||||||
|
@ -161,8 +161,8 @@ pub(crate) use each::Each;
|
||||||
pub(crate) use each::EachGroup;
|
pub(crate) use each::EachGroup;
|
||||||
pub(crate) use each::EachWindow;
|
pub(crate) use each::EachWindow;
|
||||||
pub(crate) use echo::Echo;
|
pub(crate) use echo::Echo;
|
||||||
|
pub(crate) use empty::Command as Empty;
|
||||||
pub(crate) use if_::If;
|
pub(crate) use if_::If;
|
||||||
pub(crate) use is_empty::IsEmpty;
|
|
||||||
pub(crate) use nu::NuPlugin;
|
pub(crate) use nu::NuPlugin;
|
||||||
pub(crate) use update::Command as Update;
|
pub(crate) use update::Command as Update;
|
||||||
pub(crate) mod kill;
|
pub(crate) mod kill;
|
||||||
|
@ -280,12 +280,11 @@ mod tests {
|
||||||
|
|
||||||
fn commands() -> Vec<Command> {
|
fn commands() -> Vec<Command> {
|
||||||
vec![
|
vec![
|
||||||
// Table operations
|
|
||||||
whole_stream_command(Append),
|
whole_stream_command(Append),
|
||||||
whole_stream_command(GroupBy),
|
whole_stream_command(GroupBy),
|
||||||
// Row specific operations
|
|
||||||
whole_stream_command(Insert),
|
whole_stream_command(Insert),
|
||||||
whole_stream_command(Update),
|
whole_stream_command(Update),
|
||||||
|
whole_stream_command(Empty),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
288
crates/nu-cli/src/commands/empty.rs
Normal file
288
crates/nu-cli/src/commands/empty.rs
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::classified::block::run_block;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
hir::Block, ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use nu_value_ext::{as_string, ValueExt};
|
||||||
|
|
||||||
|
use futures::stream::once;
|
||||||
|
use indexmap::indexmap;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
rest: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"empty?"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("empty?").rest(
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the names of the columns to check emptiness. Pass an optional block to replace if empty",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Check for empty values"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
is_empty(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Check if a value is empty",
|
||||||
|
example: "echo '' | empty?",
|
||||||
|
result: Some(vec![UntaggedValue::boolean(true).into()]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "more than one column",
|
||||||
|
example: "echo [[meal size]; [arepa small] [taco '']] | empty? meal size",
|
||||||
|
result: Some(
|
||||||
|
vec![
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"meal".to_string() => Value::from(false),
|
||||||
|
"size".to_string() => Value::from(false),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"meal".to_string() => Value::from(false),
|
||||||
|
"size".to_string() => Value::from(true),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},Example {
|
||||||
|
description: "use a block if setting the empty cell contents is wanted",
|
||||||
|
example: "echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] }",
|
||||||
|
result: Some(
|
||||||
|
vec![
|
||||||
|
UntaggedValue::row(indexmap! {
|
||||||
|
"2020/04/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(33).into(), UntaggedValue::int(37).into()]).into(),
|
||||||
|
"2020/07/10".to_string() => UntaggedValue::table(&[UntaggedValue::int(27).into()]).into(),
|
||||||
|
"2020/11/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(37).into()]).into(),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_empty(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let name_tag = Arc::new(args.call_info.name_tag.clone());
|
||||||
|
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||||
|
let scope = args.call_info.scope.clone();
|
||||||
|
let (Arguments { rest }, input) = args.process(®istry).await?;
|
||||||
|
let (columns, default_block): (Vec<ColumnPath>, Option<Block>) = arguments(rest)?;
|
||||||
|
let default_block = Arc::new(default_block);
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
let stream = futures::stream::iter(vec![
|
||||||
|
UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Ok(InputStream::from_stream(stream)
|
||||||
|
.then(move |input| {
|
||||||
|
let tag = name_tag.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = vec![];
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(scope, context, input, block, columns, tag).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.then(move |input| {
|
||||||
|
let tag = name_tag.clone();
|
||||||
|
let scope = scope.clone();
|
||||||
|
let context = context.clone();
|
||||||
|
let block = default_block.clone();
|
||||||
|
let columns = columns.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(scope, context, input, block, columns, tag).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arguments(rest: Vec<Value>) -> Result<(Vec<ColumnPath>, Option<Block>), ShellError> {
|
||||||
|
let mut rest = rest;
|
||||||
|
let mut columns = vec![];
|
||||||
|
let mut default = None;
|
||||||
|
|
||||||
|
let last_argument = rest.pop();
|
||||||
|
|
||||||
|
match last_argument {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Block(call),
|
||||||
|
..
|
||||||
|
}) => default = Some(call),
|
||||||
|
Some(other) => {
|
||||||
|
let Tagged { item: path, .. } = other.as_column_path()?;
|
||||||
|
|
||||||
|
columns = vec![path];
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
for argument in rest {
|
||||||
|
let Tagged { item: path, .. } = argument.as_column_path()?;
|
||||||
|
|
||||||
|
columns.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((columns, default))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_row(
|
||||||
|
scope: Arc<Scope>,
|
||||||
|
mut context: Arc<EvaluationContext>,
|
||||||
|
input: Value,
|
||||||
|
default_block: Arc<Option<Block>>,
|
||||||
|
column_paths: Vec<ColumnPath>,
|
||||||
|
tag: Arc<Tag>,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let _tag = &*tag;
|
||||||
|
let mut out = Arc::new(None);
|
||||||
|
let results = Arc::make_mut(&mut out);
|
||||||
|
|
||||||
|
if let Some(default_block) = &*default_block {
|
||||||
|
let for_block = input.clone();
|
||||||
|
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||||
|
|
||||||
|
let scope = Scope::append_it(scope, input.clone());
|
||||||
|
|
||||||
|
let mut stream = run_block(
|
||||||
|
&default_block,
|
||||||
|
Arc::make_mut(&mut context),
|
||||||
|
input_stream,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*results = Some({
|
||||||
|
let values = stream.drain_vec().await;
|
||||||
|
|
||||||
|
let errors = context.get_errors();
|
||||||
|
|
||||||
|
if let Some(error) = errors.first() {
|
||||||
|
return Err(error.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if values.len() == 1 {
|
||||||
|
let value = values
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| ShellError::unexpected("No value."))?;
|
||||||
|
|
||||||
|
Value {
|
||||||
|
value: value.value.clone(),
|
||||||
|
tag: input.tag.clone(),
|
||||||
|
}
|
||||||
|
} else if values.is_empty() {
|
||||||
|
UntaggedValue::nothing().into_value(&input.tag)
|
||||||
|
} else {
|
||||||
|
UntaggedValue::table(&values).into_value(&input.tag)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match input {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Row(ref r),
|
||||||
|
ref tag,
|
||||||
|
} => {
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value({
|
||||||
|
let is_empty = input.is_empty();
|
||||||
|
|
||||||
|
if default_block.is_some() {
|
||||||
|
if is_empty {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||||
|
} else {
|
||||||
|
input.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
let mut obj = input.clone();
|
||||||
|
|
||||||
|
for column in column_paths.clone() {
|
||||||
|
let path = UntaggedValue::Primitive(Primitive::ColumnPath(column.clone()))
|
||||||
|
.into_value(tag);
|
||||||
|
let data = r.get_data(&as_string(&path)?).borrow().clone();
|
||||||
|
let is_empty = data.is_empty();
|
||||||
|
|
||||||
|
let default = if default_block.is_some() {
|
||||||
|
if is_empty {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||||
|
} else {
|
||||||
|
data.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(value) =
|
||||||
|
obj.swap_data_by_column_path(&column, Box::new(move |_| Ok(default)))
|
||||||
|
{
|
||||||
|
obj = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(obj)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Ok(OutputStream::one(ReturnSuccess::value({
|
||||||
|
if other.is_empty() {
|
||||||
|
results
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(other.tag))
|
||||||
|
} else {
|
||||||
|
UntaggedValue::boolean(false).into_value(other.tag)
|
||||||
|
}
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,13 +103,16 @@ async fn process_row(
|
||||||
let result = if values.len() == 1 {
|
let result = if values.len() == 1 {
|
||||||
let value = values
|
let value = values
|
||||||
.get(0)
|
.get(0)
|
||||||
.ok_or_else(|| ShellError::unexpected("No value to insert with"))?;
|
.ok_or_else(|| ShellError::unexpected("No value to insert with."))?;
|
||||||
|
|
||||||
value.clone()
|
Value {
|
||||||
|
value: value.value.clone(),
|
||||||
|
tag: input.tag.clone(),
|
||||||
|
}
|
||||||
} else if values.is_empty() {
|
} else if values.is_empty() {
|
||||||
UntaggedValue::nothing().into_untagged_value()
|
UntaggedValue::nothing().into_value(&input.tag)
|
||||||
} else {
|
} else {
|
||||||
UntaggedValue::table(&values).into_untagged_value()
|
UntaggedValue::table(&values).into_value(&input.tag)
|
||||||
};
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
|
|
|
@ -1,218 +0,0 @@
|
||||||
use crate::command_registry::CommandRegistry;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
|
||||||
use nu_source::Tagged;
|
|
||||||
use nu_value_ext::ValueExt;
|
|
||||||
|
|
||||||
enum IsEmptyFor {
|
|
||||||
Value,
|
|
||||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
|
||||||
RowWithField(Tagged<ColumnPath>),
|
|
||||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IsEmpty;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct IsEmptyArgs {
|
|
||||||
rest: Vec<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for IsEmpty {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"empty?"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("empty?").rest(
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"the names of the columns to check emptiness followed by the replacement value.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
is_empty(args, registry).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn is_empty(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let name_tag = args.call_info.name_tag.clone();
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
if input.is_empty() {
|
|
||||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
|
||||||
UntaggedValue::boolean(true).into_value(name_tag),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(input
|
|
||||||
.map(move |value| {
|
|
||||||
let value_tag = value.tag();
|
|
||||||
|
|
||||||
let action = if rest.len() <= 2 {
|
|
||||||
let field = rest.get(0);
|
|
||||||
let replacement_if_true = rest.get(1);
|
|
||||||
|
|
||||||
match (field, replacement_if_true) {
|
|
||||||
(Some(field), Some(replacement_if_true)) => {
|
|
||||||
IsEmptyFor::RowWithFieldAndFallback(
|
|
||||||
Box::new(field.as_column_path()?),
|
|
||||||
replacement_if_true.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
|
||||||
(_, _) => IsEmptyFor::Value,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut arguments = rest.iter().rev();
|
|
||||||
let replacement_if_true = match arguments.next() {
|
|
||||||
Some(arg) => arg.clone(),
|
|
||||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
|
||||||
};
|
|
||||||
|
|
||||||
IsEmptyFor::RowWithFieldsAndFallback(
|
|
||||||
arguments
|
|
||||||
.map(|a| a.as_column_path())
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.collect(),
|
|
||||||
replacement_if_true,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
match action {
|
|
||||||
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
|
|
||||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
|
||||||
)),
|
|
||||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
|
||||||
let mut out = value;
|
|
||||||
|
|
||||||
for field in fields.iter() {
|
|
||||||
let val = crate::commands::get::get_column_path(&field, &out)?;
|
|
||||||
|
|
||||||
let emptiness_value = match out {
|
|
||||||
obj
|
|
||||||
@
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Row(_),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if val.is_empty() {
|
|
||||||
obj.replace_data_at_column_path(&field, default.clone())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ShellError::labeled_error(
|
|
||||||
"empty? could not find place to check emptiness",
|
|
||||||
"column name",
|
|
||||||
&field.tag,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::labeled_error(
|
|
||||||
"Unrecognized type in stream",
|
|
||||||
"original value",
|
|
||||||
&value_tag,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
out = emptiness_value?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ReturnSuccess::Value(out))
|
|
||||||
}
|
|
||||||
IsEmptyFor::RowWithField(field) => {
|
|
||||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
|
||||||
|
|
||||||
match &value {
|
|
||||||
obj
|
|
||||||
@
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Row(_),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if val.is_empty() {
|
|
||||||
match obj.replace_data_at_column_path(
|
|
||||||
&field,
|
|
||||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
|
||||||
) {
|
|
||||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
|
||||||
None => Err(ShellError::labeled_error(
|
|
||||||
"empty? could not find place to check emptiness",
|
|
||||||
"column name",
|
|
||||||
&field.tag,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(ReturnSuccess::Value(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::labeled_error(
|
|
||||||
"Unrecognized type in stream",
|
|
||||||
"original value",
|
|
||||||
&value_tag,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
|
||||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
|
||||||
|
|
||||||
match &value {
|
|
||||||
obj
|
|
||||||
@
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Row(_),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if val.is_empty() {
|
|
||||||
match obj.replace_data_at_column_path(&field, default) {
|
|
||||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
|
||||||
None => Err(ShellError::labeled_error(
|
|
||||||
"empty? could not find place to check emptiness",
|
|
||||||
"column name",
|
|
||||||
&field.tag,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(ReturnSuccess::Value(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::labeled_error(
|
|
||||||
"Unrecognized type in stream",
|
|
||||||
"original value",
|
|
||||||
&value_tag,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::IsEmpty;
|
|
||||||
use super::ShellError;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
Ok(test_examples(IsEmpty {})?)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -108,13 +108,16 @@ async fn process_row(
|
||||||
let result = if values.len() == 1 {
|
let result = if values.len() == 1 {
|
||||||
let value = values
|
let value = values
|
||||||
.get(0)
|
.get(0)
|
||||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
.ok_or_else(|| ShellError::unexpected("No value to update with."))?;
|
||||||
|
|
||||||
value.clone()
|
Value {
|
||||||
|
value: value.value.clone(),
|
||||||
|
tag: input.tag.clone(),
|
||||||
|
}
|
||||||
} else if values.is_empty() {
|
} else if values.is_empty() {
|
||||||
UntaggedValue::nothing().into_untagged_value()
|
UntaggedValue::nothing().into_value(&input.tag)
|
||||||
} else {
|
} else {
|
||||||
UntaggedValue::table(&values).into_untagged_value()
|
UntaggedValue::table(&values).into_value(&input.tag)
|
||||||
};
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::ClassifiedBlock;
|
use nu_protocol::hir::ClassifiedBlock;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ReturnSuccess, Scope, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
|
Primitive, ReturnSuccess, Scope, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::{AnchorLocation, TaggedItem};
|
use nu_source::{AnchorLocation, TaggedItem};
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::command::CommandArgs;
|
use crate::commands::command::CommandArgs;
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
whole_stream_command, BuildString, Command, Each, Echo, Get, Keep, StrCollect,
|
whole_stream_command, BuildString, Command, Each, Echo, Get, Keep, StrCollect,
|
||||||
WholeStreamCommand,
|
WholeStreamCommand, Wrap,
|
||||||
};
|
};
|
||||||
use crate::evaluation_context::EvaluationContext;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::stream::{InputStream, OutputStream};
|
use crate::stream::{InputStream, OutputStream};
|
||||||
|
@ -41,6 +41,7 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
|
||||||
whole_stream_command(Keep {}),
|
whole_stream_command(Keep {}),
|
||||||
whole_stream_command(Each {}),
|
whole_stream_command(Each {}),
|
||||||
whole_stream_command(StrCollect),
|
whole_stream_command(StrCollect),
|
||||||
|
whole_stream_command(Wrap),
|
||||||
cmd,
|
cmd,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -103,6 +104,7 @@ pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
|
||||||
whole_stream_command(Each {}),
|
whole_stream_command(Each {}),
|
||||||
whole_stream_command(cmd),
|
whole_stream_command(cmd),
|
||||||
whole_stream_command(StrCollect),
|
whole_stream_command(StrCollect),
|
||||||
|
whole_stream_command(Wrap),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for sample_pipeline in examples {
|
for sample_pipeline in examples {
|
||||||
|
@ -166,6 +168,7 @@ pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
||||||
whole_stream_command(Keep {}),
|
whole_stream_command(Keep {}),
|
||||||
whole_stream_command(Each {}),
|
whole_stream_command(Each {}),
|
||||||
whole_stream_command(StrCollect),
|
whole_stream_command(StrCollect),
|
||||||
|
whole_stream_command(Wrap),
|
||||||
cmd,
|
cmd,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -306,12 +309,13 @@ impl WholeStreamCommand for MockCommand {
|
||||||
|
|
||||||
if open_mock {
|
if open_mock {
|
||||||
if let Some(true) = mocked_path {
|
if let Some(true) = mocked_path {
|
||||||
let mocked_path = Some(mock_path());
|
return Ok(OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||||
let value = out.tagged(name_tag.span).map_anchored(&mocked_path);
|
value: out,
|
||||||
|
tag: Tag {
|
||||||
return Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
anchor: Some(mock_path()),
|
||||||
value.item.into_value(value.tag),
|
span: name_tag.span,
|
||||||
))));
|
},
|
||||||
|
}))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +328,7 @@ impl WholeStreamCommand for MockCommand {
|
||||||
struct MockEcho;
|
struct MockEcho;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct MockEchoArgs {
|
struct MockEchoArgs {
|
||||||
pub rest: Vec<Value>,
|
pub rest: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,9 +364,10 @@ impl WholeStreamCommand for MockEcho {
|
||||||
let stream = rest.into_iter().map(move |i| {
|
let stream = rest.into_iter().map(move |i| {
|
||||||
let base_value = base_value.clone();
|
let base_value = base_value.clone();
|
||||||
match i.as_string() {
|
match i.as_string() {
|
||||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||||
UntaggedValue::string(s).into_value(base_value.tag.clone()),
|
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||||
))),
|
tag: base_value.tag.clone(),
|
||||||
|
}))),
|
||||||
_ => match i {
|
_ => match i {
|
||||||
Value {
|
Value {
|
||||||
value: UntaggedValue::Table(table),
|
value: UntaggedValue::Table(table),
|
||||||
|
|
86
crates/nu-cli/tests/commands/empty.rs
Normal file
86
crates/nu-cli/tests/commands/empty.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reports_emptiness() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
echo [[are_empty];
|
||||||
|
[$(= [[check]; [[]] ])]
|
||||||
|
[$(= [[check]; [""] ])]
|
||||||
|
[$(= [[check]; [$(wrap)] ])]
|
||||||
|
]
|
||||||
|
| get are_empty
|
||||||
|
| empty? check
|
||||||
|
| where check
|
||||||
|
| count
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_block_run_value_for_an_empty_column() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
echo [
|
||||||
|
[ first_name, last_name, rusty_at, likes ];
|
||||||
|
[ Andrés, Robalino, 10/11/2013, 1 ]
|
||||||
|
[ Jonathan, Turner, 10/12/2013, 1 ]
|
||||||
|
[ Jason, Gedge, 10/11/2013, 1 ]
|
||||||
|
[ Yehuda, Katz, 10/11/2013, '' ]
|
||||||
|
]
|
||||||
|
| empty? likes { = 1 }
|
||||||
|
| get likes
|
||||||
|
| math sum
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_block_run_value_for_many_empty_columns() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
echo [
|
||||||
|
[ boost check ];
|
||||||
|
[ 1, [] ]
|
||||||
|
[ 1, "" ]
|
||||||
|
[ 1, $(wrap) ]
|
||||||
|
]
|
||||||
|
| empty? boost check { = 1 }
|
||||||
|
| get boost check
|
||||||
|
| math sum
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn passing_a_block_will_set_contents_on_empty_cells_and_leave_non_empty_ones_untouched() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
echo [
|
||||||
|
[ NAME, LVL, HP ];
|
||||||
|
[ Andrés, 30, 3000 ]
|
||||||
|
[ Alistair, 29, 2900 ]
|
||||||
|
[ Arepas, "", "" ]
|
||||||
|
[ Jorge, 30, 3000 ]
|
||||||
|
]
|
||||||
|
| empty? LVL { = 9 }
|
||||||
|
| empty? HP {
|
||||||
|
get LVL | = $it * 1000
|
||||||
|
}
|
||||||
|
| math sum
|
||||||
|
| get HP
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "17900");
|
||||||
|
}
|
|
@ -1,94 +0,0 @@
|
||||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
|
||||||
use nu_test_support::playground::Playground;
|
|
||||||
use nu_test_support::{nu, pipeline};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn adds_value_provided_if_column_is_empty() {
|
|
||||||
Playground::setup("is_empty_test_1", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
|
||||||
"likes.csv",
|
|
||||||
r#"
|
|
||||||
first_name,last_name,rusty_at,likes
|
|
||||||
Andrés,Robalino,10/11/2013,1
|
|
||||||
Jonathan,Turner,10/12/2013,1
|
|
||||||
Jason,Gedge,10/11/2013,1
|
|
||||||
Yehuda,Katz,10/11/2013,
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
r#"
|
|
||||||
open likes.csv
|
|
||||||
| empty? likes 1
|
|
||||||
| get likes
|
|
||||||
| math sum
|
|
||||||
| echo $it
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "4");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn adds_value_provided_for_columns_that_are_empty() {
|
|
||||||
Playground::setup("is_empty_test_2", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
|
||||||
"checks.json",
|
|
||||||
r#"
|
|
||||||
[
|
|
||||||
{"boost": 1, "check": []},
|
|
||||||
{"boost": 1, "check": ""},
|
|
||||||
{"boost": 1, "check": {}}
|
|
||||||
]
|
|
||||||
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
r#"
|
|
||||||
open checks.json
|
|
||||||
| empty? boost check 1
|
|
||||||
| get boost check
|
|
||||||
| math sum
|
|
||||||
| echo $it
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "6");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn value_emptiness_check() {
|
|
||||||
Playground::setup("is_empty_test_3", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
|
||||||
"checks.json",
|
|
||||||
r#"
|
|
||||||
{
|
|
||||||
"are_empty": [
|
|
||||||
{"check": []},
|
|
||||||
{"check": ""},
|
|
||||||
{"check": {}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
r#"
|
|
||||||
open checks.json
|
|
||||||
| get are_empty.check
|
|
||||||
| empty?
|
|
||||||
| where $it
|
|
||||||
| count
|
|
||||||
| echo $it
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "3");
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -12,6 +12,7 @@ mod default;
|
||||||
mod drop;
|
mod drop;
|
||||||
mod each;
|
mod each;
|
||||||
mod echo;
|
mod echo;
|
||||||
|
mod empty;
|
||||||
mod enter;
|
mod enter;
|
||||||
mod every;
|
mod every;
|
||||||
mod first;
|
mod first;
|
||||||
|
@ -22,7 +23,6 @@ mod headers;
|
||||||
mod histogram;
|
mod histogram;
|
||||||
mod insert;
|
mod insert;
|
||||||
mod into_int;
|
mod into_int;
|
||||||
mod is_empty;
|
|
||||||
mod keep;
|
mod keep;
|
||||||
mod last;
|
mod last;
|
||||||
mod lines;
|
mod lines;
|
||||||
|
|
|
@ -448,6 +448,18 @@ impl From<&str> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Value {
|
||||||
|
fn from(s: bool) -> Value {
|
||||||
|
Value {
|
||||||
|
value: s.into(),
|
||||||
|
tag: Tag {
|
||||||
|
anchor: None,
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> From<T> for UntaggedValue
|
impl<T> From<T> for UntaggedValue
|
||||||
where
|
where
|
||||||
T: Into<Primitive>,
|
T: Into<Primitive>,
|
||||||
|
|
76
docs/commands/empty.md
Normal file
76
docs/commands/empty.md
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# empty?
|
||||||
|
|
||||||
|
Check for empty values. Pass the column names to check emptiness. Optionally pass a block as the last parameter if setting contents to empty columns is wanted.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Check if a value is empty
|
||||||
|
```shell
|
||||||
|
> echo '' | empty?
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the following meals
|
||||||
|
```shell
|
||||||
|
> echo [[meal size]; [arepa small] [taco '']]
|
||||||
|
═══╦═══════╦═══════
|
||||||
|
# ║ meal ║ size
|
||||||
|
═══╬═══════╬═══════
|
||||||
|
0 ║ arepa ║ small
|
||||||
|
1 ║ taco ║
|
||||||
|
═══╩═══════╩═══════
|
||||||
|
```
|
||||||
|
|
||||||
|
Show the empty contents
|
||||||
|
```shell
|
||||||
|
> echo [[meal size]; [arepa small] [taco '']] | empty? meal size
|
||||||
|
═══╦══════╦══════
|
||||||
|
# ║ meal ║ size
|
||||||
|
═══╬══════╬══════
|
||||||
|
0 ║ No ║ No
|
||||||
|
1 ║ No ║ Yes
|
||||||
|
═══╩══════╩══════
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's assume we have a report of totals per day. For simplicity we show just for three days `2020/04/16`, `2020/07/10`, and `2020/11/16`. Like so
|
||||||
|
```shell
|
||||||
|
> echo [[2020/04/16 2020/07/10 2020/11/16]; ['' 27 37]]
|
||||||
|
═══╦════════════╦════════════╦════════════
|
||||||
|
# ║ 2020/04/16 ║ 2020/07/10 ║ 2020/11/16
|
||||||
|
═══╬════════════╬════════════╬════════════
|
||||||
|
0 ║ ║ 27 ║ 37
|
||||||
|
═══╩════════════╩════════════╩════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
In the future, the report now has many totals logged per day. In this example, we have 1 total for the day `2020/07/10` and `2020/11/16` like so
|
||||||
|
```shell
|
||||||
|
> echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]]
|
||||||
|
═══╦════════════╦════════════════╦════════════════
|
||||||
|
# ║ 2020/04/16 ║ 2020/07/10 ║ 2020/11/16
|
||||||
|
═══╬════════════╬════════════════╬════════════════
|
||||||
|
0 ║ ║ [table 1 rows] ║ [table 1 rows]
|
||||||
|
═══╩════════════╩════════════════╩════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
We want to add two totals (numbers `33` and `37`) for the day `2020/04/16`
|
||||||
|
|
||||||
|
Set a table with two numbers for the empty column
|
||||||
|
```shell
|
||||||
|
> echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] }
|
||||||
|
═══╦════════════════╦════════════════╦════════════════
|
||||||
|
# ║ 2020/04/16 ║ 2020/07/10 ║ 2020/11/16
|
||||||
|
═══╬════════════════╬════════════════╬════════════════
|
||||||
|
0 ║ [table 2 rows] ║ [table 1 rows] ║ [table 1 rows]
|
||||||
|
═══╩════════════════╩════════════════╩════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
Checking all the numbers
|
||||||
|
```shell
|
||||||
|
> echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] } | pivot _ totals | get totals
|
||||||
|
═══╦════
|
||||||
|
0 ║ 33
|
||||||
|
1 ║ 37
|
||||||
|
2 ║ 27
|
||||||
|
3 ║ 37
|
||||||
|
═══╩════
|
||||||
|
```
|
Loading…
Reference in a new issue