diff --git a/src/cli.rs b/src/cli.rs index 706b76702..cfd9d8de6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -210,6 +210,7 @@ pub async fn cli() -> Result<(), Box> { per_item_command(Open), per_item_command(Post), per_item_command(Where), + per_item_command(Echo), whole_stream_command(Config), whole_stream_command(SkipWhile), per_item_command(Enter), diff --git a/src/commands.rs b/src/commands.rs index e0e1c80e5..f2885f369 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,6 +11,7 @@ pub(crate) mod config; pub(crate) mod cp; pub(crate) mod date; pub(crate) mod debug; +pub(crate) mod echo; pub(crate) mod enter; pub(crate) mod exit; pub(crate) mod fetch; @@ -76,6 +77,7 @@ pub(crate) use config::Config; pub(crate) use cp::Cpy; pub(crate) use date::Date; pub(crate) use debug::Debug; +pub(crate) use echo::Echo; pub(crate) use enter::Enter; pub(crate) use exit::Exit; pub(crate) use fetch::Fetch; diff --git a/src/commands/autoview.rs b/src/commands/autoview.rs index f970d23e4..adce1df40 100644 --- a/src/commands/autoview.rs +++ b/src/commands/autoview.rs @@ -58,7 +58,7 @@ pub fn autoview( } } }; - } else if is_single_text_value(&input) { + } else if is_single_origined_text_value(&input) { let text = context.get_command("textview"); if let Some(text) = text { let result = text.run(raw.with_input(input), &context.commands); @@ -73,6 +73,15 @@ pub fn autoview( } } } + } else if is_single_text_value(&input) { + for i in input { + match i.item { + Value::Primitive(Primitive::String(s)) => { + println!("{}", s); + } + _ => {} + } + } } else { let table = context.expect_command("table"); let result = table.run(raw.with_input(input), &context.commands); @@ -96,3 +105,20 @@ fn is_single_text_value(input: &Vec>) -> bool { false } } + +fn is_single_origined_text_value(input: &Vec>) -> bool { + if input.len() != 1 { + return false; + } + if let Tagged { + item: Value::Primitive(Primitive::String(_)), + tag: Tag { + origin: Some(_), .. + }, + } = input[0] + { + true + } else { + false + } +} diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 959a8f612..277a92534 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -220,111 +220,60 @@ impl ExternalCommand { let mut process; - #[cfg(windows)] - { - process = Exec::shell(&self.name); + process = Exec::cmd(&self.name); - if arg_string.contains("$it") { - let mut first = true; - - for i in &inputs { - if i.as_string().is_err() { - let mut span = None; - for arg in &self.args { - if arg.item.contains("$it") { - span = Some(arg.span()); - } - } - if let Some(span) = span { - return Err(ShellError::labeled_error( - "External $it needs string data", - "given row instead of string data", - span, - )); - } else { - return Err(ShellError::string("Error: $it needs string data")); - } - } - if !first { - process = process.arg("&&"); - process = process.arg(&self.name); - } else { - first = false; - } + if arg_string.contains("$it") { + let mut first = true; + for i in &inputs { + if i.as_string().is_err() { + let mut span = None; for arg in &self.args { - if arg.chars().all(|c| c.is_whitespace()) { - continue; + if arg.item.contains("$it") { + span = Some(arg.span()); } - - process = process.arg(&arg.replace("$it", &i.as_string()?)); + } + if let Some(span) = span { + return Err(ShellError::labeled_error( + "External $it needs string data", + "given row instead of string data", + span, + )); + } else { + return Err(ShellError::string("Error: $it needs string data")); } } - } else { + if !first { + process = process.arg("&&"); + process = process.arg(&self.name); + } else { + first = false; + } + for arg in &self.args { - let arg_chars: Vec<_> = arg.chars().collect(); - if arg_chars.len() > 1 - && arg_chars[0] == '"' - && arg_chars[arg_chars.len() - 1] == '"' - { - // quoted string - let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect(); - process = process.arg(new_arg); - } else { - process = process.arg(arg.item.clone()); + if arg.chars().all(|c| c.is_whitespace()) { + continue; } + + process = process.arg(&arg.replace("$it", &i.as_string()?)); + } + } + } else { + for arg in &self.args { + let arg_chars: Vec<_> = arg.chars().collect(); + if arg_chars.len() > 1 + && arg_chars[0] == '"' + && arg_chars[arg_chars.len() - 1] == '"' + { + // quoted string + let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect(); + process = process.arg(new_arg); + } else { + process = process.arg(arg.item.clone()); } } } - #[cfg(not(windows))] - { - let mut new_arg_string = self.name.to_string(); - if arg_string.contains("$it") { - let mut first = true; - for i in &inputs { - let i = match i.as_string() { - Err(_err) => { - let mut span = name_span; - for arg in &self.args { - if arg.item.contains("$it") { - span = arg.span(); - } - } - return Err(ShellError::labeled_error( - "External $it needs string data", - "given row instead of string data", - span, - )); - } - Ok(val) => val, - }; - - if !first { - new_arg_string.push_str("&&"); - new_arg_string.push_str(&self.name); - } else { - first = false; - } - - for arg in &self.args { - if arg.chars().all(|c| c.is_whitespace()) { - continue; - } - - new_arg_string.push_str(" "); - new_arg_string.push_str(&arg.replace("$it", &i)); - } - } - } else { - for arg in &self.args { - new_arg_string.push_str(" "); - new_arg_string.push_str(&arg); - } - } - - process = Exec::shell(new_arg_string); - } process = process.cwd(context.shell_manager.path()); let mut process = match stream_next { diff --git a/src/commands/echo.rs b/src/commands/echo.rs new file mode 100644 index 000000000..f464630de --- /dev/null +++ b/src/commands/echo.rs @@ -0,0 +1,72 @@ +use crate::data::Value; +use crate::errors::ShellError; +use crate::prelude::*; + +use crate::parser::registry::Signature; + +pub struct Echo; + +impl PerItemCommand for Echo { + fn name(&self) -> &str { + "echo" + } + + fn signature(&self) -> Signature { + Signature::build("echo").rest(SyntaxType::Any) + } + + fn usage(&self) -> &str { + "Echo the argments back to the user." + } + + fn run( + &self, + call_info: &CallInfo, + registry: &CommandRegistry, + raw_args: &RawCommandArgs, + _input: Tagged, + ) -> Result { + run(call_info, registry, raw_args) + } +} + +fn run( + call_info: &CallInfo, + _registry: &CommandRegistry, + _raw_args: &RawCommandArgs, +) -> Result { + let name = call_info.name_span; + + let mut output = String::new(); + + let mut first = true; + + if let Some(ref positional) = call_info.args.positional { + for i in positional { + match i.as_string() { + Ok(s) => { + if !first { + output.push_str(" "); + } else { + first = false; + } + + output.push_str(&s); + } + _ => { + return Err(ShellError::labeled_error( + "Expect a string from pipeline", + "not a string-compatible value", + i.span(), + )); + } + } + } + } + + let stream = VecDeque::from(vec![Ok(ReturnSuccess::Value( + Value::string(output).simple_spanned(name), + ))]); + + Ok(stream.to_output_stream()) +} diff --git a/tests/filters_test.rs b/tests/filters_test.rs index b44ac5c47..b08115a9c 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -35,7 +35,7 @@ fn converts_structured_table_to_csv_text() { | to-csv | lines | nth 1 - | echo "$it" + | echo $it "# )); @@ -63,7 +63,7 @@ fn converts_structured_table_to_csv_text_skipping_headers_after_conversion() { | split-column "," a b c d origin | last 1 | to-csv --headerless - | echo "$it" + | echo $it "# )); @@ -261,7 +261,7 @@ fn converts_structured_table_to_tsv_text() { | to-tsv | lines | nth 1 - | echo "$it" + | echo $it "# )); @@ -289,7 +289,7 @@ fn converts_structured_table_to_tsv_text_skipping_headers_after_conversion() { | split-column "\t" a b c d origin | last 1 | to-tsv --headerless - | echo "$it" + | echo $it "# )); diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index c27738c79..6aa7cb67b 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -310,8 +310,7 @@ pub fn file_contents(full_path: impl AsRef) -> String { pub fn file_contents_binary(full_path: impl AsRef) -> Vec { let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); let mut contents = Vec::new(); - file.read_to_end(&mut contents) - .expect("can not read file"); + file.read_to_end(&mut contents).expect("can not read file"); contents } @@ -327,18 +326,6 @@ pub fn line_ending() -> String { } } -pub fn normalize_string(input: &str) -> String { - #[cfg(windows)] - { - input.to_string() - } - - #[cfg(not(windows))] - { - format!("\"{}\"", input) - } -} - pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { let full_path = full_path.as_ref(); @@ -377,13 +364,13 @@ pub fn in_directory(str: impl AsRef) -> String { str.as_ref().display().to_string() } - pub fn pipeline(commands: &str) -> String { - commands.lines() - .skip(1) - .map(|line| line.trim()) - .collect::>() - .join(" ") - .trim_end() - .to_string() + commands + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join(" ") + .trim_end() + .to_string() } diff --git a/tests/tests.rs b/tests/tests.rs index 4affb4422..25337edb0 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -12,9 +12,13 @@ fn pipeline_helper() { | str --to-int | sum | echo "$it" - "#); + "#, + ); - assert_eq!(actual, r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | sum | echo "$it""#); + assert_eq!( + actual, + r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | sum | echo "$it""# + ); } #[test] @@ -34,9 +38,7 @@ fn external_has_correct_quotes() { r#"echo "hello world""# ); - let actual = h::normalize_string(&actual); - - assert_eq!(actual, r#""hello world""#); + assert_eq!(actual, r#"hello world"#); } #[test]