mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +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(EachGroup),
|
||||
whole_stream_command(EachWindow),
|
||||
whole_stream_command(IsEmpty),
|
||||
whole_stream_command(Empty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Merge),
|
||||
|
|
|
@ -36,6 +36,7 @@ pub(crate) mod drop;
|
|||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod empty;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
|
@ -67,7 +68,6 @@ pub(crate) mod history;
|
|||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
|
@ -161,8 +161,8 @@ pub(crate) use each::Each;
|
|||
pub(crate) use each::EachGroup;
|
||||
pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use empty::Command as Empty;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
|
@ -280,12 +280,11 @@ mod tests {
|
|||
|
||||
fn commands() -> Vec<Command> {
|
||||
vec![
|
||||
// Table operations
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(GroupBy),
|
||||
// Row specific operations
|
||||
whole_stream_command(Insert),
|
||||
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 value = values
|
||||
.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() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
UntaggedValue::nothing().into_value(&input.tag)
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
UntaggedValue::table(&values).into_value(&input.tag)
|
||||
};
|
||||
|
||||
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 value = values
|
||||
.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() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
UntaggedValue::nothing().into_value(&input.tag)
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
UntaggedValue::table(&values).into_value(&input.tag)
|
||||
};
|
||||
|
||||
match input {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ClassifiedBlock;
|
||||
use nu_protocol::{
|
||||
ReturnSuccess, Scope, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
Primitive, ReturnSuccess, Scope, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{AnchorLocation, TaggedItem};
|
||||
|
||||
|
@ -17,7 +17,7 @@ use crate::commands::classified::block::run_block;
|
|||
use crate::commands::command::CommandArgs;
|
||||
use crate::commands::{
|
||||
whole_stream_command, BuildString, Command, Each, Echo, Get, Keep, StrCollect,
|
||||
WholeStreamCommand,
|
||||
WholeStreamCommand, Wrap,
|
||||
};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::stream::{InputStream, OutputStream};
|
||||
|
@ -41,6 +41,7 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
|
|||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
]);
|
||||
|
||||
|
@ -103,6 +104,7 @@ pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
|
|||
whole_stream_command(Each {}),
|
||||
whole_stream_command(cmd),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
]);
|
||||
|
||||
for sample_pipeline in examples {
|
||||
|
@ -166,6 +168,7 @@ pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
|||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
]);
|
||||
|
||||
|
@ -306,12 +309,13 @@ impl WholeStreamCommand for MockCommand {
|
|||
|
||||
if open_mock {
|
||||
if let Some(true) = mocked_path {
|
||||
let mocked_path = Some(mock_path());
|
||||
let value = out.tagged(name_tag.span).map_anchored(&mocked_path);
|
||||
|
||||
return Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
value.item.into_value(value.tag),
|
||||
))));
|
||||
return Ok(OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: out,
|
||||
tag: Tag {
|
||||
anchor: Some(mock_path()),
|
||||
span: name_tag.span,
|
||||
},
|
||||
}))));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +328,7 @@ impl WholeStreamCommand for MockCommand {
|
|||
struct MockEcho;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MockEchoArgs {
|
||||
struct MockEchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
|
@ -360,9 +364,10 @@ impl WholeStreamCommand for MockEcho {
|
|||
let stream = rest.into_iter().map(move |i| {
|
||||
let base_value = base_value.clone();
|
||||
match i.as_string() {
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(base_value.tag.clone()),
|
||||
))),
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
tag: base_value.tag.clone(),
|
||||
}))),
|
||||
_ => match i {
|
||||
Value {
|
||||
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 each;
|
||||
mod echo;
|
||||
mod empty;
|
||||
mod enter;
|
||||
mod every;
|
||||
mod first;
|
||||
|
@ -22,7 +23,6 @@ mod headers;
|
|||
mod histogram;
|
||||
mod insert;
|
||||
mod into_int;
|
||||
mod is_empty;
|
||||
mod keep;
|
||||
mod last;
|
||||
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
|
||||
where
|
||||
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