From cf5326443898a058683dad939daeb9c6225e7296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Wed, 29 Apr 2020 23:18:24 -0500 Subject: [PATCH] Table operating commands. (#1686) * Table operating commands. * Updated merge test for clarity. * More clarity. * Better like this.. --- crates/nu-cli/src/cli.rs | 7 +- crates/nu-cli/src/commands.rs | 10 ++ crates/nu-cli/src/commands/edit.rs | 115 ++++++++++++++++----- crates/nu-cli/src/commands/keep.rs | 49 +++++++++ crates/nu-cli/src/commands/keep_until.rs | 98 ++++++++++++++++++ crates/nu-cli/src/commands/keep_while.rs | 98 ++++++++++++++++++ crates/nu-cli/src/commands/merge.rs | 95 +++++++++++++++++ crates/nu-cli/src/commands/skip_until.rs | 98 ++++++++++++++++++ crates/nu-cli/src/data/value.rs | 40 +++++++ crates/nu-cli/tests/commands/edit.rs | 21 +++- crates/nu-cli/tests/commands/keep.rs | 32 ++++++ crates/nu-cli/tests/commands/keep_until.rs | 51 +++++++++ crates/nu-cli/tests/commands/keep_while.rs | 51 +++++++++ crates/nu-cli/tests/commands/merge.rs | 43 ++++++++ crates/nu-cli/tests/commands/mod.rs | 5 + crates/nu-cli/tests/commands/skip_until.rs | 50 +++++++++ crates/nu-protocol/src/value/dict.rs | 21 +++- 17 files changed, 854 insertions(+), 30 deletions(-) create mode 100644 crates/nu-cli/src/commands/keep.rs create mode 100644 crates/nu-cli/src/commands/keep_until.rs create mode 100644 crates/nu-cli/src/commands/keep_while.rs create mode 100644 crates/nu-cli/src/commands/merge.rs create mode 100644 crates/nu-cli/src/commands/skip_until.rs create mode 100644 crates/nu-cli/tests/commands/keep.rs create mode 100644 crates/nu-cli/tests/commands/keep_until.rs create mode 100644 crates/nu-cli/tests/commands/keep_while.rs create mode 100644 crates/nu-cli/tests/commands/merge.rs create mode 100644 crates/nu-cli/tests/commands/skip_until.rs diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 1560000332..c4862d5a00 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -297,20 +297,25 @@ pub fn create_default_context( whole_stream_command(GroupBy), whole_stream_command(First), whole_stream_command(Last), - whole_stream_command(Skip), whole_stream_command(Nth), whole_stream_command(Drop), whole_stream_command(Format), whole_stream_command(Where), whole_stream_command(Compact), whole_stream_command(Default), + whole_stream_command(Skip), + whole_stream_command(SkipUntil), whole_stream_command(SkipWhile), + whole_stream_command(Keep), + whole_stream_command(KeepUntil), + whole_stream_command(KeepWhile), whole_stream_command(Range), whole_stream_command(Rename), whole_stream_command(Uniq), whole_stream_command(Each), whole_stream_command(IsEmpty), // Table manipulation + whole_stream_command(Merge), whole_stream_command(Shuffle), whole_stream_command(Wrap), whole_stream_command(Pivot), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 6083ab7ed2..eabb5af3a6 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -55,11 +55,15 @@ pub(crate) mod histogram; pub(crate) mod history; pub(crate) mod insert; pub(crate) mod is_empty; +pub(crate) mod keep; +pub(crate) mod keep_until; +pub(crate) mod keep_while; pub(crate) mod last; pub(crate) mod lines; pub(crate) mod ls; #[allow(unused)] pub(crate) mod map_max_by; +pub(crate) mod merge; pub(crate) mod mkdir; pub(crate) mod mv; pub(crate) mod next; @@ -86,6 +90,7 @@ pub(crate) mod shells; pub(crate) mod shuffle; pub(crate) mod size; pub(crate) mod skip; +pub(crate) mod skip_until; pub(crate) mod skip_while; pub(crate) mod sort_by; pub(crate) mod split_by; @@ -170,11 +175,15 @@ pub(crate) use help::Help; pub(crate) use histogram::Histogram; pub(crate) use history::History; pub(crate) use insert::Insert; +pub(crate) use keep::Keep; +pub(crate) use keep_until::KeepUntil; +pub(crate) use keep_while::KeepWhile; pub(crate) use last::Last; pub(crate) use lines::Lines; pub(crate) use ls::Ls; #[allow(unused_imports)] pub(crate) use map_max_by::MapMaxBy; +pub(crate) use merge::Merge; pub(crate) use mkdir::Mkdir; pub(crate) use mv::Move; pub(crate) use next::Next; @@ -199,6 +208,7 @@ pub(crate) use shells::Shells; pub(crate) use shuffle::Shuffle; pub(crate) use size::Size; pub(crate) use skip::Skip; +pub(crate) use skip_until::SkipUntil; pub(crate) use skip_while::SkipWhile; pub(crate) use sort_by::SortBy; pub(crate) use split_by::SplitBy; diff --git a/crates/nu-cli/src/commands/edit.rs b/crates/nu-cli/src/commands/edit.rs index 3533159f22..863c37a71c 100644 --- a/crates/nu-cli/src/commands/edit.rs +++ b/crates/nu-cli/src/commands/edit.rs @@ -1,3 +1,4 @@ +use crate::commands::classified::block::run_block; use crate::commands::WholeStreamCommand; use crate::context::CommandRegistry; use crate::prelude::*; @@ -5,6 +6,7 @@ use nu_errors::ShellError; use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_value_ext::ValueExt; +use futures::stream::once; pub struct Edit; #[derive(Deserialize)] @@ -41,41 +43,104 @@ impl WholeStreamCommand for Edit { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - args.process(registry, edit)?.run() + Ok(args.process_raw(registry, edit)?.run()) } } fn edit( EditArgs { field, replacement }: EditArgs, - RunnableContext { input, .. }: RunnableContext, + context: RunnableContext, + raw_args: RawCommandArgs, ) -> Result { - let mut input = input; + let scope = raw_args.call_info.scope.clone(); + let registry = context.registry.clone(); + let mut input_stream = context.input; let stream = async_stream! { - match input.next().await { - Some(obj @ Value { - value: UntaggedValue::Row(_), - .. - }) => match obj.replace_data_at_column_path(&field, replacement.clone()) { - Some(v) => yield Ok(ReturnSuccess::Value(v)), - None => { - yield Err(ShellError::labeled_error( - "edit could not find place to insert column", - "column name", - obj.tag, - )) - } - }, + while let Some(input) = input_stream.next().await { + let replacement = replacement.clone(); + match replacement { + Value { + value: UntaggedValue::Block(block), + tag, + } => { + let mut context = Context::from_raw(&raw_args, ®istry); + let for_block = input.clone(); + let input_clone = input.clone(); + let input_stream = once(async { Ok(for_block) }).to_input_stream(); - Some(Value { tag, ..}) => { - yield Err(ShellError::labeled_error( - "Unrecognized type in stream", - "original value", - tag, - )) + let result = run_block( + &block, + &mut context, + input_stream, + &scope.clone().set_it(input_clone), + ).await; + + match result { + Ok(mut stream) => { + let errors = context.get_errors(); + if let Some(error) = errors.first() { + yield Err(error.clone()); + } + + match input { + obj @ Value { + value: UntaggedValue::Row(_), + .. + } => { + if let Some(result) = stream.next().await { + match obj.replace_data_at_column_path(&field, result.clone()) { + Some(v) => yield Ok(ReturnSuccess::Value(v)), + None => { + yield Err(ShellError::labeled_error( + "edit could not find place to insert column", + "column name", + obj.tag, + )) + } + } + } + } + Value { tag, ..} => { + yield Err(ShellError::labeled_error( + "Unrecognized type in stream", + "original value", + tag, + )) + } + } + } + Err(e) => { + yield Err(e); + } + } } - _ => {} - } + _ => { + match input { + obj @ Value { + value: UntaggedValue::Row(_), + .. + } => match obj.replace_data_at_column_path(&field, replacement.clone()) { + Some(v) => yield Ok(ReturnSuccess::Value(v)), + None => { + yield Err(ShellError::labeled_error( + "edit could not find place to insert column", + "column name", + obj.tag, + )) + } + }, + Value { tag, ..} => { + yield Err(ShellError::labeled_error( + "Unrecognized type in stream", + "original value", + tag, + )) + } + _ => {} + } + } + }} }; Ok(stream.to_output_stream()) diff --git a/crates/nu-cli/src/commands/keep.rs b/crates/nu-cli/src/commands/keep.rs new file mode 100644 index 0000000000..73e0cbc93c --- /dev/null +++ b/crates/nu-cli/src/commands/keep.rs @@ -0,0 +1,49 @@ +use crate::commands::WholeStreamCommand; +use crate::context::CommandRegistry; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape}; +use nu_source::Tagged; + +pub struct Keep; + +#[derive(Deserialize)] +pub struct KeepArgs { + rows: Option>, +} + +impl WholeStreamCommand for Keep { + fn name(&self) -> &str { + "keep" + } + + fn signature(&self) -> Signature { + Signature::build("keep").optional( + "rows", + SyntaxShape::Int, + "starting from the front, the number of rows to keep", + ) + } + + fn usage(&self) -> &str { + "Keep the number of rows only" + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, keep)?.run() + } +} + +fn keep(KeepArgs { rows }: KeepArgs, context: RunnableContext) -> Result { + let rows_desired = if let Some(quantity) = rows { + *quantity + } else { + 1 + }; + + Ok(OutputStream::from_input(context.input.take(rows_desired))) +} diff --git a/crates/nu-cli/src/commands/keep_until.rs b/crates/nu-cli/src/commands/keep_until.rs new file mode 100644 index 0000000000..3ede81b107 --- /dev/null +++ b/crates/nu-cli/src/commands/keep_until.rs @@ -0,0 +1,98 @@ +use crate::commands::WholeStreamCommand; +use crate::evaluate::evaluate_baseline_expr; +use crate::prelude::*; +use log::trace; +use nu_errors::ShellError; +use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value}; + +pub struct KeepUntil; + +impl WholeStreamCommand for KeepUntil { + fn name(&self) -> &str { + "keep-until" + } + + fn signature(&self) -> Signature { + Signature::build("keep-until") + .required( + "condition", + SyntaxShape::Math, + "the condition that must be met to stop keeping rows", + ) + .filter() + } + + fn usage(&self) -> &str { + "Keeps rows until the condition matches." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + let registry = registry.clone(); + let scope = args.call_info.scope.clone(); + let call_info = args.evaluate_once(®istry)?; + + let block = call_info.args.expect_nth(0)?.clone(); + + let condition = match block { + Value { + value: UntaggedValue::Block(block), + tag, + } => { + if block.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match block.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, + )); + } + } + } + Value { tag, .. } => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }; + + let objects = call_info.input.take_while(move |item| { + let condition = condition.clone(); + trace!("ITEM = {:?}", item); + let result = + evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone())); + trace!("RESULT = {:?}", result); + + let return_value = match result { + Ok(ref v) if v.is_true() => false, + _ => true, + }; + + futures::future::ready(return_value) + }); + + Ok(objects.from_input_stream()) + } +} diff --git a/crates/nu-cli/src/commands/keep_while.rs b/crates/nu-cli/src/commands/keep_while.rs new file mode 100644 index 0000000000..5b8ee693b2 --- /dev/null +++ b/crates/nu-cli/src/commands/keep_while.rs @@ -0,0 +1,98 @@ +use crate::commands::WholeStreamCommand; +use crate::evaluate::evaluate_baseline_expr; +use crate::prelude::*; +use log::trace; +use nu_errors::ShellError; +use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value}; + +pub struct KeepWhile; + +impl WholeStreamCommand for KeepWhile { + fn name(&self) -> &str { + "keep-while" + } + + fn signature(&self) -> Signature { + Signature::build("keep-while") + .required( + "condition", + SyntaxShape::Math, + "the condition that must be met to keep rows", + ) + .filter() + } + + fn usage(&self) -> &str { + "Keeps rows while the condition matches." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + let registry = registry.clone(); + let scope = args.call_info.scope.clone(); + let call_info = args.evaluate_once(®istry)?; + + let block = call_info.args.expect_nth(0)?.clone(); + + let condition = match block { + Value { + value: UntaggedValue::Block(block), + tag, + } => { + if block.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match block.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, + )); + } + } + } + Value { tag, .. } => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }; + + let objects = call_info.input.take_while(move |item| { + let condition = condition.clone(); + trace!("ITEM = {:?}", item); + let result = + evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone())); + trace!("RESULT = {:?}", result); + + let return_value = match result { + Ok(ref v) if v.is_true() => true, + _ => false, + }; + + futures::future::ready(return_value) + }); + + Ok(objects.from_input_stream()) + } +} diff --git a/crates/nu-cli/src/commands/merge.rs b/crates/nu-cli/src/commands/merge.rs new file mode 100644 index 0000000000..f14604132e --- /dev/null +++ b/crates/nu-cli/src/commands/merge.rs @@ -0,0 +1,95 @@ +use crate::commands::classified::block::run_block; +use crate::commands::WholeStreamCommand; +use crate::context::CommandRegistry; +use crate::data::value::merge_values; +use crate::prelude::*; + +use indexmap::IndexMap; +use nu_errors::ShellError; +use nu_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value}; +pub struct Merge; + +#[derive(Deserialize)] +pub struct MergeArgs { + block: Block, +} + +impl WholeStreamCommand for Merge { + fn name(&self) -> &str { + "merge" + } + + fn signature(&self) -> Signature { + Signature::build("merge").required( + "block", + SyntaxShape::Block, + "the block to run and merge into the table", + ) + } + + fn usage(&self) -> &str { + "Merge a table." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + Ok(args.process_raw(registry, merge)?.run()) + } +} + +fn merge( + merge_args: MergeArgs, + context: RunnableContext, + raw_args: RawCommandArgs, +) -> Result { + let block = merge_args.block; + let registry = context.registry.clone(); + let mut input = context.input; + + let mut context = Context::from_raw(&raw_args, ®istry); + + let stream = async_stream! { + let table: Option> = match run_block(&block, + &mut context, + InputStream::empty(), + &Scope::empty()).await { + Ok(mut stream) => Some(stream.drain_vec().await), + Err(err) => { + yield Err(err); + return; + } + }; + + + let table = table.unwrap_or_else(|| vec![Value { + value: UntaggedValue::row(IndexMap::default()), + tag: raw_args.call_info.name_tag, + }]); + + let mut idx = 0; + + while let Some(value) = input.next().await { + let other = table.get(idx); + + match other { + Some(replacement) => { + match merge_values(&value.value, &replacement.value) { + Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)), + Err(err) => { + let message = format!("The row at {:?} types mismatch", idx); + yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag)); + } + } + } + None => yield ReturnSuccess::value(value), + } + + idx += 1; + } + }; + + Ok(stream.to_output_stream()) +} diff --git a/crates/nu-cli/src/commands/skip_until.rs b/crates/nu-cli/src/commands/skip_until.rs new file mode 100644 index 0000000000..ce6201bb7a --- /dev/null +++ b/crates/nu-cli/src/commands/skip_until.rs @@ -0,0 +1,98 @@ +use crate::commands::WholeStreamCommand; +use crate::evaluate::evaluate_baseline_expr; +use crate::prelude::*; +use log::trace; +use nu_errors::ShellError; +use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value}; + +pub struct SkipUntil; + +impl WholeStreamCommand for SkipUntil { + fn name(&self) -> &str { + "skip-until" + } + + fn signature(&self) -> Signature { + Signature::build("skip-until") + .required( + "condition", + SyntaxShape::Math, + "the condition that must be met to stop skipping", + ) + .filter() + } + + fn usage(&self) -> &str { + "Skips rows until the condition matches." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + let registry = registry.clone(); + let scope = args.call_info.scope.clone(); + let call_info = args.evaluate_once(®istry)?; + + let block = call_info.args.expect_nth(0)?.clone(); + + let condition = match block { + Value { + value: UntaggedValue::Block(block), + tag, + } => { + if block.block.len() != 1 { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + match block.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, + )); + } + } + } + Value { tag, .. } => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + }; + + let objects = call_info.input.skip_while(move |item| { + let condition = condition.clone(); + trace!("ITEM = {:?}", item); + let result = + evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone())); + trace!("RESULT = {:?}", result); + + let return_value = match result { + Ok(ref v) if v.is_true() => false, + _ => true, + }; + + futures::future::ready(return_value) + }); + + Ok(objects.from_input_stream()) + } +} diff --git a/crates/nu-cli/src/data/value.rs b/crates/nu-cli/src/data/value.rs index a0c0d0bec8..6b35108c6d 100644 --- a/crates/nu-cli/src/data/value.rs +++ b/crates/nu-cli/src/data/value.rs @@ -22,6 +22,18 @@ pub fn date_from_str(s: Tagged<&str>) -> Result { Ok(UntaggedValue::Primitive(Primitive::Date(date))) } +pub fn merge_values( + left: &UntaggedValue, + right: &UntaggedValue, +) -> Result { + match (left, right) { + (UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => { + Ok(UntaggedValue::Row(columns.merge_from(columns_b))) + } + (left, right) => Err((left.type_name(), right.type_name())), + } +} + pub fn compute_values( operator: Operator, left: &UntaggedValue, @@ -153,3 +165,31 @@ pub fn format_for_column<'a>( .format_for_column(column) .pretty() } + +#[cfg(test)] +mod tests { + use super::UntaggedValue as v; + use indexmap::indexmap; + + use super::merge_values; + + #[test] + fn merges_tables() { + let table_author_row = v::row(indexmap! { + "name".into() => v::string("Andrés").into_untagged_value(), + "country".into() => v::string("EC").into_untagged_value(), + "date".into() => v::string("April 29-2020").into_untagged_value() + }); + + let other_table_author_row = v::row(indexmap! { + "name".into() => v::string("YK").into_untagged_value(), + "country".into() => v::string("US").into_untagged_value(), + "date".into() => v::string("October 10-2019").into_untagged_value() + }); + + assert_eq!( + other_table_author_row, + merge_values(&table_author_row, &other_table_author_row).unwrap() + ); + } +} diff --git a/crates/nu-cli/tests/commands/edit.rs b/crates/nu-cli/tests/commands/edit.rs index f68e83efea..f8c91a0cfc 100644 --- a/crates/nu-cli/tests/commands/edit.rs +++ b/crates/nu-cli/tests/commands/edit.rs @@ -1,16 +1,31 @@ use nu_test_support::{nu, pipeline}; #[test] -fn creates_a_new_table_with_the_new_row_given() { +fn sets_the_column() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" open cargo_sample.toml - | edit dev-dependencies.pretty_assertions "7" + | edit dev-dependencies.pretty_assertions "0.7.0" | get dev-dependencies.pretty_assertions | echo $it "# )); - assert_eq!(actual, "7"); + assert_eq!(actual, "0.7.0"); +} + +#[test] +fn sets_the_column_from_a_block_run_output() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | edit dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor } + | get dev-dependencies.pretty_assertions + | echo $it + "# + )); + + assert_eq!(actual, "0.7.0"); } diff --git a/crates/nu-cli/tests/commands/keep.rs b/crates/nu-cli/tests/commands/keep.rs new file mode 100644 index 0000000000..17bcc46ecf --- /dev/null +++ b/crates/nu-cli/tests/commands/keep.rs @@ -0,0 +1,32 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn rows() { + Playground::setup("keep_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.csv", + r#" + name,lucky_code + Andrés,1 + Jonathan,1 + Jason,2 + Yehuda,1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open caballeros.csv + | keep 3 + | get lucky_code + | sum + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }) +} diff --git a/crates/nu-cli/tests/commands/keep_until.rs b/crates/nu-cli/tests/commands/keep_until.rs new file mode 100644 index 0000000000..878d6df69a --- /dev/null +++ b/crates/nu-cli/tests/commands/keep_until.rs @@ -0,0 +1,51 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("keep-until_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020, + Yellow Chickens,,, + Andrés,1,1,1 + Jonathan,1,1,1 + Jason,1,1,1 + Yehuda,1,1,1 + Blue Chickens,,, + Andrés,1,1,2 + Jonathan,1,1,2 + Jason,1,1,2 + Yehuda,1,1,2 + Red Chickens,,, + Andrés,1,1,3 + Jonathan,1,1,3 + Jason,1,1,3 + Yehuda,1,1,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | split-column ',' + | headers + | skip-while "Chickens Collction" != "Blue Chickens" + | keep-until "Chicken Collection" == "Red Chickens" + | str "31/04/2020" --to-int + | get "31/04/2020" + | sum + | echo $it + "# + )); + + assert_eq!(actual, "8"); + }) +} diff --git a/crates/nu-cli/tests/commands/keep_while.rs b/crates/nu-cli/tests/commands/keep_while.rs new file mode 100644 index 0000000000..89c12b8a38 --- /dev/null +++ b/crates/nu-cli/tests/commands/keep_while.rs @@ -0,0 +1,51 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("keep-while_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020, + Yellow Chickens,,, + Andrés,1,1,1 + Jonathan,1,1,1 + Jason,1,1,1 + Yehuda,1,1,1 + Blue Chickens,,, + Andrés,1,1,2 + Jonathan,1,1,2 + Jason,1,1,2 + Yehuda,1,1,2 + Red Chickens,,, + Andrés,1,1,3 + Jonathan,1,1,3 + Jason,1,1,3 + Yehuda,1,1,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | split-column ',' + | headers + | skip 1 + | keep-while "Chicken Collection" != "Blue Chickens" + | str "31/04/2020" --to-int + | get "31/04/2020" + | sum + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }) +} diff --git a/crates/nu-cli/tests/commands/merge.rs b/crates/nu-cli/tests/commands/merge.rs new file mode 100644 index 0000000000..de2796adf4 --- /dev/null +++ b/crates/nu-cli/tests/commands/merge.rs @@ -0,0 +1,43 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn row() { + Playground::setup("merge_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + FileWithContentToBeTrimmed( + "caballeros.csv", + r#" + name,country,luck + Andrés,Ecuador,0 + Jonathan,USA,0 + Jason,Canada,0 + Yehuda,USA,0 + "#, + ), + FileWithContentToBeTrimmed( + "new_caballeros.csv", + r#" + name,country,luck + Andrés Robalino,Guayaquil Ecuador,1 + Jonathan Turner,New Zealand,1 + "#, + ), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open caballeros.csv + | merge { open new_caballeros.csv } + | where country in: ["Guayaquil Ecuador" "New Zealand"] + | get luck + | sum + | echo $it + "# + )); + + assert_eq!(actual, "2"); + }) +} diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index 0303275c1f..6bc9c33162 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -17,10 +17,14 @@ mod headers; mod histogram; mod insert; mod is_empty; +mod keep; +mod keep_until; +mod keep_while; mod last; mod lines; mod ls; mod math; +mod merge; mod mkdir; mod mv; mod open; @@ -33,6 +37,7 @@ mod reverse; mod rm; mod save; mod semicolon; +mod skip_until; mod sort_by; mod split_by; mod split_column; diff --git a/crates/nu-cli/tests/commands/skip_until.rs b/crates/nu-cli/tests/commands/skip_until.rs new file mode 100644 index 0000000000..7c709f734b --- /dev/null +++ b/crates/nu-cli/tests/commands/skip_until.rs @@ -0,0 +1,50 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("skip-until_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020, + Yellow Chickens,,, + Andrés,1,1,1 + Jonathan,1,1,1 + Jason,1,1,1 + Yehuda,1,1,1 + Blue Chickens,,, + Andrés,1,1,2 + Jonathan,1,1,2 + Jason,1,1,2 + Yehuda,1,1,2 + Red Chickens,,, + Andrés,1,1,3 + Jonathan,1,1,3 + Jason,1,1,3 + Yehuda,1,1,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | split-column ',' + | headers + | skip-until "Chicken Collection" != "Red Chickens" + | str "31/04/2020" --to-int + | get "31/04/2020" + | sum + | echo $it + "# + )); + + assert_eq!(actual, "12"); + }) +} diff --git a/crates/nu-protocol/src/value/dict.rs b/crates/nu-protocol/src/value/dict.rs index 4181923303..b66307decf 100644 --- a/crates/nu-protocol/src/value/dict.rs +++ b/crates/nu-protocol/src/value/dict.rs @@ -4,7 +4,7 @@ use crate::value::{UntaggedValue, Value}; use derive_new::new; use getset::Getters; use indexmap::IndexMap; -use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, Tag}; +use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, SpannedItem, Tag}; use serde::{Deserialize, Serialize}; use std::cmp::{Ord, Ordering, PartialOrd}; use std::hash::{Hash, Hasher}; @@ -125,6 +125,25 @@ impl Dictionary { } } + pub fn merge_from(&self, other: &Dictionary) -> Dictionary { + let mut obj = self.clone(); + + for column in other.keys() { + let key = column.clone(); + let value_key = key.as_str(); + let value_spanned_key = value_key.spanned_unknown(); + + let other_column = match other.get_data_by_key(value_spanned_key) { + Some(value) => value, + None => UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(), + }; + + obj.entries.insert(key, other_column); + } + + obj + } + /// Iterate the keys in the Dictionary pub fn keys(&self) -> impl Iterator { self.entries.keys()