diff --git a/src/cli.rs b/src/cli.rs index 0182ad1002..ad3eb8d39b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -258,7 +258,6 @@ pub async fn cli() -> Result<(), Box> { whole_stream_command(Next), whole_stream_command(Previous), whole_stream_command(Debug), - whole_stream_command(Lines), whole_stream_command(Shells), whole_stream_command(SplitColumn), whole_stream_command(SplitRow), @@ -277,6 +276,7 @@ pub async fn cli() -> Result<(), Box> { whole_stream_command(ToYAML), whole_stream_command(SortBy), whole_stream_command(Tags), + whole_stream_command(Count), whole_stream_command(First), whole_stream_command(Last), whole_stream_command(Env), diff --git a/src/commands.rs b/src/commands.rs index 61a45dbb3a..0b155891cc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -8,6 +8,7 @@ pub(crate) mod classified; pub(crate) mod clip; pub(crate) mod command; pub(crate) mod config; +pub(crate) mod count; pub(crate) mod cp; pub(crate) mod date; pub(crate) mod debug; @@ -78,6 +79,7 @@ pub(crate) use command::{ pub(crate) use classified::ClassifiedCommand; pub(crate) use config::Config; +pub(crate) use count::Count; pub(crate) use cp::Cpy; pub(crate) use date::Date; pub(crate) use debug::Debug; diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 440413ddd4..7204af77c6 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -251,7 +251,7 @@ impl ExternalCommand { ) } else { ShellError::labeled_error( - "Error: $it needs string data", + "$it needs string data", "given something else", self.name_tag.clone(), ) diff --git a/src/commands/config.rs b/src/commands/config.rs index 82fbbf1db6..9cde5213de 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -70,9 +70,9 @@ pub fn config( if let Some(v) = get { let key = v.to_string(); - let value = result.get(&key).ok_or_else(|| { - ShellError::labeled_error(&format!("Missing key in config"), "key", v.tag()) - })?; + let value = result + .get(&key) + .ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", v.tag()))?; let mut results = VecDeque::new(); @@ -121,7 +121,7 @@ pub fn config( config::write(&result, &configuration)?; } else { return Err(ShellError::labeled_error( - "{} does not exist in config", + "Key does not exist in config", "key", v.tag(), )); diff --git a/src/commands/count.rs b/src/commands/count.rs new file mode 100644 index 0000000000..5e44283737 --- /dev/null +++ b/src/commands/count.rs @@ -0,0 +1,46 @@ +use crate::commands::WholeStreamCommand; +use crate::data::Value; +use crate::errors::ShellError; +use crate::parser::CommandRegistry; +use crate::prelude::*; +use futures::stream::StreamExt; + +pub struct Count; + +#[derive(Deserialize)] +pub struct CountArgs {} + +impl WholeStreamCommand for Count { + fn name(&self) -> &str { + "count" + } + + fn signature(&self) -> Signature { + Signature::build("count") + } + + fn usage(&self) -> &str { + "Show the total number of rows." + } + + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + args.process(registry, count)?.run() + } +} + +pub fn count( + CountArgs {}: CountArgs, + RunnableContext { input, name, .. }: RunnableContext, +) -> Result { + let stream = async_stream! { + let rows: Vec> = input.values.collect().await; + + yield ReturnSuccess::value(Value::int(rows.len()).tagged(name)) + }; + + Ok(stream.to_output_stream()) +} diff --git a/src/commands/first.rs b/src/commands/first.rs index 71d05be7e1..4c1c3b8c35 100644 --- a/src/commands/first.rs +++ b/src/commands/first.rs @@ -7,7 +7,7 @@ pub struct First; #[derive(Deserialize)] pub struct FirstArgs { - amount: Tagged, + rows: Option>, } impl WholeStreamCommand for First { @@ -16,7 +16,7 @@ impl WholeStreamCommand for First { } fn signature(&self) -> Signature { - Signature::build("first").required("amount", SyntaxShape::Int) + Signature::build("first").optional("rows", SyntaxShape::Int) } fn usage(&self) -> &str { @@ -33,8 +33,16 @@ impl WholeStreamCommand for First { } fn first( - FirstArgs { amount }: FirstArgs, + FirstArgs { rows }: FirstArgs, context: RunnableContext, ) -> Result { - Ok(OutputStream::from_input(context.input.values.take(*amount))) + let rows_desired = if let Some(quantity) = rows { + *quantity + } else { + 1 + }; + + Ok(OutputStream::from_input( + context.input.values.take(rows_desired), + )) } diff --git a/src/commands/last.rs b/src/commands/last.rs index 321506846b..04db0f4c48 100644 --- a/src/commands/last.rs +++ b/src/commands/last.rs @@ -7,7 +7,7 @@ pub struct Last; #[derive(Deserialize)] pub struct LastArgs { - amount: Tagged, + rows: Option>, } impl WholeStreamCommand for Last { @@ -16,7 +16,7 @@ impl WholeStreamCommand for Last { } fn signature(&self) -> Signature { - Signature::build("last").required("amount", SyntaxShape::Number) + Signature::build("last").optional("rows", SyntaxShape::Number) } fn usage(&self) -> &str { @@ -32,13 +32,17 @@ impl WholeStreamCommand for Last { } } -fn last( - LastArgs { amount }: LastArgs, - context: RunnableContext, -) -> Result { +fn last(LastArgs { rows }: LastArgs, context: RunnableContext) -> Result { let stream = async_stream! { let v: Vec<_> = context.input.into_vec().await; - let count = (*amount as usize); + + let rows_desired = if let Some(quantity) = rows { + *quantity + } else { + 1 + }; + + let count = (rows_desired as usize); if count < v.len() { let k = v.len() - count; for x in v[k..].iter() { diff --git a/src/plugins/docker.rs b/src/plugins/docker.rs index 9cb8a52e80..e0a06ab3d4 100644 --- a/src/plugins/docker.rs +++ b/src/plugins/docker.rs @@ -21,8 +21,8 @@ async fn docker(sub_command: &String, name: Tag) -> Result>, S "images" => docker_images(name), _ => Err(ShellError::labeled_error( "Unsupported Docker command", - format!("'{}'?", sub_command), - name.span, + "unknown docker command", + name, )), } } @@ -46,7 +46,7 @@ fn process_docker_output(cmd_output: &str, tag: Tag) -> Result .filter(|s| s.trim() != "") .collect(); - let mut dict = TaggedDictBuilder::new(tag); + let mut dict = TaggedDictBuilder::new(&tag); for (i, v) in values.iter().enumerate() { dict.insert(header[i].to_string(), Value::string(v.trim().to_string())); } @@ -92,18 +92,13 @@ impl Plugin for Docker { if let Some(args) = callinfo.args.positional { match &args[0] { Tagged { - item: Value::Primitive(Primitive::String(s)), + item: Value::Primitive(Primitive::String(command)), .. - } => match block_on(docker(&s, callinfo.name_tag)) { + } => match block_on(docker(&command, args[0].tag())) { Ok(v) => return Ok(v.into_iter().map(ReturnSuccess::value).collect()), Err(e) => return Err(e), }, - _ => { - return Err(ShellError::string(format!( - "Unrecognized type in params: {:?}", - args[0] - ))) - } + _ => return Err(ShellError::type_error("string", args[0].tagged_type_name())), } } diff --git a/tests/command_ls_tests.rs b/tests/command_ls_tests.rs index f6f5f39f86..a0ae959e12 100644 --- a/tests/command_ls_tests.rs +++ b/tests/command_ls_tests.rs @@ -7,26 +7,21 @@ use helpers::{Playground, Stub::*}; fn ls_lists_regular_files() { Playground::setup("ls_test_1", |dirs, sandbox| { sandbox.with_files(vec![ - EmptyFile("yehuda.10.txt"), - EmptyFile("jonathan.10.txt"), - EmptyFile("andres.10.txt"), + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), ]); let actual = nu!( cwd: dirs.test(), h::pipeline( r#" ls - | get name - | lines - | split-column "." - | get Column2 - | str --to-int - | sum + | count | echo $it "# )); - assert_eq!(actual, "30"); + assert_eq!(actual, "3"); }) } @@ -34,22 +29,17 @@ fn ls_lists_regular_files() { fn ls_lists_regular_files_using_asterisk_wildcard() { Playground::setup("ls_test_2", |dirs, sandbox| { sandbox.with_files(vec![ - EmptyFile("los.1.txt"), - EmptyFile("tres.1.txt"), - EmptyFile("amigos.1.txt"), - EmptyFile("arepas.1.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), ]); let actual = nu!( cwd: dirs.test(), h::pipeline( r#" ls *.txt - | get name - | lines - | split-column "." - | get Column2 - | str --to-int - | sum + | count | echo $it "# )); @@ -72,16 +62,11 @@ fn ls_lists_regular_files_using_question_mark_wildcard() { cwd: dirs.test(), h::pipeline( r#" ls *.??.txt - | get name - | lines - | split-column "." - | get Column2 - | str --to-int - | sum + | count | echo $it "# )); - assert_eq!(actual, "30"); + assert_eq!(actual, "3"); }) } diff --git a/tests/commands_test.rs b/tests/commands_test.rs index cfa6f74334..4d6fa84a65 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -7,38 +7,109 @@ use helpers::{Playground, Stub::*}; fn first_gets_first_rows_by_amount() { Playground::setup("first_test_1", |dirs, sandbox| { sandbox.with_files(vec![ - EmptyFile("los.1.txt"), - EmptyFile("tres.1.txt"), - EmptyFile("amigos.1.txt"), - EmptyFile("arepas.1.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), ]); let actual = nu!( cwd: dirs.test(), h::pipeline( r#" ls - | get name - | first 2 - | split-column "." - | get Column2 - | str --to-int - | sum + | first 3 + | count | echo $it "# )); - assert_eq!(actual, "2"); + assert_eq!(actual, "3"); }) } #[test] -fn first_requires_an_amount() { - Playground::setup("first_test_2", |dirs, _| { - let actual = nu_error!( - cwd: dirs.test(), "ls | first" - ); +fn first_gets_all_rows_if_amount_higher_than_all_rows() { + Playground::setup("first_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); - assert!(actual.contains("requires amount parameter")); + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + ls + | first 99 + | count + | echo $it + "# + )); + + assert_eq!(actual, "4"); + }) +} + +#[test] +fn first_gets_first_row_when_no_amount_given() { + Playground::setup("first_test_3", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("caballeros.txt"), EmptyFile("arepas.clu")]); + + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + ls + | first + | count + | echo $it + "# + )); + + assert_eq!(actual, "1"); + }) +} + +#[test] +fn last_gets_last_rows_by_amount() { + Playground::setup("last_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + ls + | last 3 + | count + | echo $it + "# + )); + + assert_eq!(actual, "3"); + }) +} + +#[test] +fn last_gets_last_row_when_no_amount_given() { + Playground::setup("last_test_2", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("caballeros.txt"), EmptyFile("arepas.clu")]); + + let actual = nu!( + cwd: dirs.test(), h::pipeline( + r#" + ls + | last + | count + | echo $it + "# + )); + + assert_eq!(actual, "1"); }) }