diff --git a/README.md b/README.md index 733eff4403..75bfe7d56a 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | command | description | | ------------- | ------------- | | append row-data | Append a row to the end of the table | +| compact ...columns | Remove rows where given columns are empty | | count | Show the total number of rows | | edit column-or-column-path value | Edit an existing column to have a new value | | embed column | Creates a new table of one column with the given name, and places the current table inside of it | @@ -286,7 +287,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | reject ...columns | Remove the given columns from the table | | reverse | Reverses the table. | | skip amount | Skip a number of rows | -| skip-while condition | Skips rows while the condition matches. | +| skip-while condition | Skips rows while the condition matches | | split-by column | Creates a new table with the data from the inner tables splitted by the column given | | sort-by ...columns | Sort by the given columns | | str (column) | Apply string function. Optionally use the column of a table | diff --git a/src/cli.rs b/src/cli.rs index 153cd0abdc..0354b642e8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -309,6 +309,7 @@ pub async fn cli() -> Result<(), Box> { per_item_command(Where), per_item_command(Echo), whole_stream_command(Config), + whole_stream_command(Compact), whole_stream_command(SkipWhile), per_item_command(Enter), per_item_command(Help), diff --git a/src/commands.rs b/src/commands.rs index 5c06be2a88..0d8c0c8986 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,6 +11,7 @@ pub(crate) mod cd; pub(crate) mod classified; pub(crate) mod clip; pub(crate) mod command; +pub(crate) mod compact; pub(crate) mod config; pub(crate) mod count; pub(crate) mod cp; @@ -98,6 +99,7 @@ pub(crate) use command::{ pub(crate) use append::Append; pub(crate) use classified::ClassifiedCommand; +pub(crate) use compact::Compact; pub(crate) use config::Config; pub(crate) use count::Count; pub(crate) use cp::Cpy; diff --git a/src/commands/compact.rs b/src/commands/compact.rs new file mode 100644 index 0000000000..be18b8ce42 --- /dev/null +++ b/src/commands/compact.rs @@ -0,0 +1,53 @@ +use crate::commands::WholeStreamCommand; +use crate::errors::ShellError; +use crate::parser::registry::{CommandRegistry, Signature}; +use crate::prelude::*; +use futures::stream::StreamExt; + +pub struct Compact; + +#[derive(Deserialize)] +pub struct CompactArgs { + rest: Vec>, +} + +impl WholeStreamCommand for Compact { + fn name(&self) -> &str { + "compact" + } + + fn signature(&self) -> Signature { + Signature::build("compact").rest(SyntaxShape::Any, "the columns to compact from the table") + } + + fn usage(&self) -> &str { + "Creates a table with non-empty rows" + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, compact)?.run() + } +} + +pub fn compact( + CompactArgs { rest: columns }: CompactArgs, + RunnableContext { input, .. }: RunnableContext, +) -> Result { + let objects = input.values.take_while(move |item| { + let keep = if columns.is_empty() { + item.is_some() + } else { + columns + .iter() + .all(|field| item.get_data(field).borrow().is_some()) + }; + + futures::future::ready(keep) + }); + + Ok(objects.from_input_stream()) +} diff --git a/src/data/base.rs b/src/data/base.rs index 57b920bc44..d4fa51e5e4 100644 --- a/src/data/base.rs +++ b/src/data/base.rs @@ -498,6 +498,17 @@ impl Value { } } + pub(crate) fn is_some(&self) -> bool { + !self.is_none() + } + + pub(crate) fn is_none(&self) -> bool { + match self { + Value::Primitive(Primitive::Nothing) => true, + _ => false, + } + } + pub(crate) fn is_error(&self) -> bool { match self { Value::Error(_err) => true, diff --git a/tests/commands_test.rs b/tests/commands_test.rs index db0121a9c1..646cf57cb8 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -3,6 +3,54 @@ mod helpers; use helpers as h; use helpers::{Playground, Stub::*}; +#[test] +fn compact_rows_where_given_column_is_empty() { + Playground::setup("compact_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.json", + r#" + { + "amigos": [ + {"name": "Yehuda", "rusty_luck": 1}, + {"name": "Jonathan", "rusty_luck": 1}, + {"name": "Andres", "rusty_luck": 1}, + {"name":"GorbyPuff"} + ] + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + open los_tres_amigos.json + | get amigos + | compact rusty_luck + | count + | echo $it + "# + )); + + assert_eq!(actual, "3"); + }); +} +#[test] +fn compact_empty_rows_by_default() { + Playground::setup("compact_test_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + echo "[1,2,3,14,null]" + | from-json + | compact + | count + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }); +} #[test] fn group_by() { Playground::setup("group_by_test_1", |dirs, sandbox| {