From e75c44c95be1608168e57da85a89b2fdf5afd788 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Sat, 4 Jul 2020 12:40:04 -0700 Subject: [PATCH] If command and touchups (#2106) --- crates/nu-cli/src/cli.rs | 1 + crates/nu-cli/src/commands.rs | 2 + crates/nu-cli/src/commands/if_.rs | 188 +++++++++++++++++++++++++++ crates/nu-cli/src/commands/nth.rs | 14 +- crates/nu-cli/src/commands/open.rs | 22 +++- crates/nu-cli/src/data/base/shape.rs | 6 +- crates/nu-parser/src/parse.rs | 12 +- 7 files changed, 227 insertions(+), 18 deletions(-) create mode 100644 crates/nu-cli/src/commands/if_.rs diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index fd6f0f3e63..00a4013ff0 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -330,6 +330,7 @@ pub fn create_default_context( whole_stream_command(Drop), whole_stream_command(Format), whole_stream_command(Where), + whole_stream_command(If), whole_stream_command(Compact), whole_stream_command(Default), whole_stream_command(Skip), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 55d21dc4df..d106f23171 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -62,6 +62,7 @@ pub(crate) mod headers; pub(crate) mod help; pub(crate) mod histogram; pub(crate) mod history; +pub(crate) mod if_; pub(crate) mod insert; pub(crate) mod is_empty; pub(crate) mod keep; @@ -157,6 +158,7 @@ pub(crate) use drop::Drop; pub(crate) use du::Du; pub(crate) use each::Each; pub(crate) use echo::Echo; +pub(crate) use if_::If; pub(crate) use is_empty::IsEmpty; pub(crate) use update::Update; pub(crate) mod kill; diff --git a/crates/nu-cli/src/commands/if_.rs b/crates/nu-cli/src/commands/if_.rs new file mode 100644 index 0000000000..0dc2eff9ce --- /dev/null +++ b/crates/nu-cli/src/commands/if_.rs @@ -0,0 +1,188 @@ +use crate::commands::classified::block::run_block; +use crate::commands::WholeStreamCommand; +use crate::context::CommandRegistry; +use crate::evaluate::evaluate_baseline_expr; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue}; + +pub struct If; + +#[derive(Deserialize)] +pub struct IfArgs { + condition: Block, + then_case: Block, + else_case: Block, +} + +#[async_trait] +impl WholeStreamCommand for If { + fn name(&self) -> &str { + "if" + } + + fn signature(&self) -> Signature { + Signature::build("if") + .required( + "condition", + SyntaxShape::Math, + "the condition that must match", + ) + .required( + "then_case", + SyntaxShape::Block, + "block to run if condition is true", + ) + .required( + "else_case", + SyntaxShape::Block, + "block to run if condition is false", + ) + } + + fn usage(&self) -> &str { + "Filter table to match the condition." + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + if_command(args, registry).await + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Run a block if a condition is true", + example: "echo 10 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }", + result: Some(vec![UntaggedValue::string("greater than 5").into()]), + }, + Example { + description: "Run a block if a condition is false", + example: "echo 1 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }", + result: Some(vec![UntaggedValue::string("less than or equal to 5").into()]), + }, + ] + } +} +async fn if_command( + raw_args: CommandArgs, + registry: &CommandRegistry, +) -> Result { + let registry = Arc::new(registry.clone()); + let scope = Arc::new(raw_args.call_info.scope.clone()); + let tag = raw_args.call_info.name_tag.clone(); + let context = Arc::new(Context::from_raw(&raw_args, ®istry)); + + let ( + IfArgs { + condition, + then_case, + else_case, + }, + input, + ) = raw_args.process(®istry).await?; + let condition = { + if condition.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match condition.block[0].list.get(0) { + Some(item) => match item { + ClassifiedCommand::Expr(expr) => expr.clone(), + _ => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }, + None => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + } + }; + + Ok(input + .then(move |input| { + let condition = condition.clone(); + let then_case = then_case.clone(); + let else_case = else_case.clone(); + let registry = registry.clone(); + let scope = scope.clone(); + let mut context = context.clone(); + + async move { + //FIXME: should we use the scope that's brought in as well? + let condition = + evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env) + .await; + + match condition { + Ok(condition) => match condition.as_bool() { + Ok(b) => { + if b { + match run_block( + &then_case, + Arc::make_mut(&mut context), + InputStream::empty(), + &input, + &scope.vars, + &scope.env, + ) + .await + { + Ok(stream) => stream.to_output_stream(), + Err(e) => futures::stream::iter(vec![Err(e)].into_iter()) + .to_output_stream(), + } + } else { + match run_block( + &else_case, + Arc::make_mut(&mut context), + InputStream::empty(), + &input, + &scope.vars, + &scope.env, + ) + .await + { + Ok(stream) => stream.to_output_stream(), + Err(e) => futures::stream::iter(vec![Err(e)].into_iter()) + .to_output_stream(), + } + } + } + Err(e) => { + futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream() + } + }, + Err(e) => futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream(), + } + } + }) + .flatten() + .to_output_stream()) +} + +#[cfg(test)] +mod tests { + use super::If; + + #[test] + fn examples_work_as_expected() { + use crate::examples::test as test_examples; + + test_examples(If {}) + } +} diff --git a/crates/nu-cli/src/commands/nth.rs b/crates/nu-cli/src/commands/nth.rs index 9e10531e10..a7415eac3b 100644 --- a/crates/nu-cli/src/commands/nth.rs +++ b/crates/nu-cli/src/commands/nth.rs @@ -70,16 +70,20 @@ async fn nth(args: CommandArgs, registry: &CommandRegistry) -> Result>>(); + .map(|x| x.item) + .collect::>(); + + let max_row_number = row_numbers + .iter() + .max() + .expect("Internal error: should be > 0 row numbers"); Ok(input + .take(*max_row_number as usize + 1) .enumerate() .filter_map(move |(idx, item)| { futures::future::ready( - if row_numbers - .iter() - .any(|requested| requested.item == idx as u64) - { + if row_numbers.iter().any(|requested| *requested == idx as u64) { Some(ReturnSuccess::value(item)) } else { None diff --git a/crates/nu-cli/src/commands/open.rs b/crates/nu-cli/src/commands/open.rs index 957f70c293..d3ee1bb4df 100644 --- a/crates/nu-cli/src/commands/open.rs +++ b/crates/nu-cli/src/commands/open.rs @@ -171,14 +171,22 @@ async fn open(args: CommandArgs, registry: &CommandRegistry) -> Result { - ReturnSuccess::value(UntaggedValue::string(s).into_untagged_value()) + let final_stream = sob_stream.map(move |x| { + // The tag that will used when returning a Value + let file_tag = Tag { + span: path.tag.span, + anchor: Some(AnchorLocation::File(path.to_string_lossy().to_string())), + }; + + match x { + Ok(StringOrBinary::String(s)) => { + ReturnSuccess::value(UntaggedValue::string(s).into_value(file_tag)) + } + Ok(StringOrBinary::Binary(b)) => ReturnSuccess::value( + UntaggedValue::binary(b.into_iter().collect()).into_value(file_tag), + ), + Err(se) => Err(se), } - Ok(StringOrBinary::Binary(b)) => ReturnSuccess::value( - UntaggedValue::binary(b.into_iter().collect()).into_untagged_value(), - ), - Err(se) => Err(se), }); Ok(OutputStream::new(final_stream)) diff --git a/crates/nu-cli/src/data/base/shape.rs b/crates/nu-cli/src/data/base/shape.rs index 7a8281597d..27e23eea01 100644 --- a/crates/nu-cli/src/data/base/shape.rs +++ b/crates/nu-cli/src/data/base/shape.rs @@ -29,7 +29,7 @@ pub enum InlineShape { Date(DateTime), Duration(i64), Path(PathBuf), - Binary, + Binary(usize), Row(BTreeMap), Table(Vec), @@ -73,7 +73,7 @@ impl InlineShape { Primitive::Date(date) => InlineShape::Date(*date), Primitive::Duration(duration) => InlineShape::Duration(*duration), Primitive::Path(path) => InlineShape::Path(path.clone()), - Primitive::Binary(_) => InlineShape::Binary, + Primitive::Binary(b) => InlineShape::Binary(b.len()), Primitive::BeginningOfStream => InlineShape::BeginningOfStream, Primitive::EndOfStream => InlineShape::EndOfStream, } @@ -182,7 +182,7 @@ impl PrettyDebug for FormatInlineShape { b::description(format_primitive(&Primitive::Duration(*duration), None)) } InlineShape::Path(path) => b::primitive(path.display()), - InlineShape::Binary => b::opaque(""), + InlineShape::Binary(length) => b::opaque(format!("", length)), InlineShape::Row(row) => b::delimit( "[", b::kind("row") diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index ca5b22fae9..9c527c04df 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -1019,6 +1019,7 @@ fn parse_positional_argument( idx: usize, lite_cmd: &LiteCommand, positional_type: &PositionalType, + remaining_positionals: usize, registry: &dyn SignatureRegistry, ) -> (usize, SpannedExpression, Option) { let mut idx = idx; @@ -1038,8 +1039,12 @@ fn parse_positional_argument( } arg } else { - let (new_idx, arg, err) = - parse_math_expression(idx, &lite_cmd.args[idx..], registry, true); + let (new_idx, arg, err) = parse_math_expression( + idx, + &lite_cmd.args[idx..(lite_cmd.args.len() - remaining_positionals)], + registry, + true, + ); let span = arg.span; let mut commands = hir::Commands::new(span); @@ -1049,7 +1054,7 @@ fn parse_positional_argument( let arg = SpannedExpression::new(Expression::Block(block), span); - idx = new_idx; + idx = new_idx - 1; if error.is_none() { error = err; } @@ -1165,6 +1170,7 @@ fn parse_internal_command( idx, &lite_cmd, &signature.positional[current_positional].0, + signature.positional.len() - current_positional - 1, registry, ); idx = new_idx;