From bbe7d68659c84fd138cf1476b60e6712d9e0288e Mon Sep 17 00:00:00 2001 From: Odin Dutton Date: Mon, 26 Aug 2019 15:32:45 +1000 Subject: [PATCH 01/12] Return version from clap This is what `nu --version` uses. --- src/commands/version.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/commands/version.rs b/src/commands/version.rs index 06653ae20..e13017ab5 100644 --- a/src/commands/version.rs +++ b/src/commands/version.rs @@ -5,8 +5,6 @@ use crate::parser::registry::Signature; use crate::prelude::*; use indexmap::IndexMap; -const VERSION: &'static str = env!("CARGO_PKG_VERSION"); - pub struct Version; impl WholeStreamCommand for Version { @@ -34,7 +32,7 @@ pub fn date(args: CommandArgs, registry: &CommandRegistry) -> Result Date: Mon, 26 Aug 2019 11:17:47 -0600 Subject: [PATCH 02/12] Permit use of Windows Batch files --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index a76ba2084..945e60754 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -91,7 +91,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Result<(), ShellError> { let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?; - let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?; + let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.(exe|bat)$")?; match std::fs::read_dir(path) { Ok(p) => { From b77effa43469e0cf0ec9302a525c3a0abb466428 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 26 Aug 2019 20:19:05 +0200 Subject: [PATCH 03/12] Fix formatting with cargo fmt --- src/commands.rs | 2 +- src/commands/from_bson.rs | 108 ++++++++++++++++++----------------- src/commands/last.rs | 2 +- src/commands/open.rs | 24 ++++---- src/commands/sort_by.rs | 4 +- src/git.rs | 14 ++--- src/object/base.rs | 8 +-- src/utils.rs | 8 ++- tests/command_mkdir_tests.rs | 2 +- tests/command_mv_tests.rs | 85 ++++++++------------------- tests/command_open_tests.rs | 2 +- tests/command_rm_tests.rs | 15 ++--- tests/filter_inc_tests.rs | 55 +++++++++--------- 13 files changed, 148 insertions(+), 181 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index bde49d804..9b17e9db9 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -35,8 +35,8 @@ crate mod pick; crate mod plugin; crate mod prev; crate mod ps; -crate mod reverse; crate mod reject; +crate mod reverse; crate mod rm; crate mod save; crate mod shells; diff --git a/src/commands/from_bson.rs b/src/commands/from_bson.rs index 0f0b8d7cb..2d98e1907 100644 --- a/src/commands/from_bson.rs +++ b/src/commands/from_bson.rs @@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand; use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; -use bson::{decode_document, Bson, spec::BinarySubtype}; +use bson::{decode_document, spec::BinarySubtype, Bson}; pub struct FromBSON; @@ -47,71 +47,72 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into) -> Tagged Value::Primitive(Primitive::Boolean(*b)).tagged(tag), Bson::Null => Value::Primitive(Primitive::String(String::from(""))).tagged(tag), Bson::RegExp(r, opts) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$regex".to_string(), - Value::Primitive(Primitive::String(String::from(r))).tagged(tag), - ); - collected.insert_tagged( - "$options".to_string(), - Value::Primitive(Primitive::String(String::from(opts))).tagged(tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$regex".to_string(), + Value::Primitive(Primitive::String(String::from(r))).tagged(tag), + ); + collected.insert_tagged( + "$options".to_string(), + Value::Primitive(Primitive::String(String::from(opts))).tagged(tag), + ); + collected.into_tagged_value() } Bson::I32(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), Bson::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), Bson::JavaScriptCode(js) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$javascript".to_string(), - Value::Primitive(Primitive::String(String::from(js))).tagged(tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$javascript".to_string(), + Value::Primitive(Primitive::String(String::from(js))).tagged(tag), + ); + collected.into_tagged_value() } Bson::JavaScriptCodeWithScope(js, doc) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$javascript".to_string(), - Value::Primitive(Primitive::String(String::from(js))).tagged(tag), - ); - collected.insert_tagged( - "$scope".to_string(), - convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$javascript".to_string(), + Value::Primitive(Primitive::String(String::from(js))).tagged(tag), + ); + collected.insert_tagged( + "$scope".to_string(), + convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag), + ); + collected.into_tagged_value() } Bson::TimeStamp(ts) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$timestamp".to_string(), - Value::Primitive(Primitive::Int(*ts as i64)).tagged(tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$timestamp".to_string(), + Value::Primitive(Primitive::Int(*ts as i64)).tagged(tag), + ); + collected.into_tagged_value() } Bson::Binary(bst, bytes) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$binary_subtype".to_string(), - match bst { - BinarySubtype::UserDefined(u) => Value::Primitive(Primitive::Int(*u as i64)), - _ => Value::Primitive(Primitive::String(binary_subtype_to_string(*bst))), - }.tagged(tag) - ); - collected.insert_tagged( - "$binary".to_string(), - Value::Binary(bytes.to_owned()).tagged(tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$binary_subtype".to_string(), + match bst { + BinarySubtype::UserDefined(u) => Value::Primitive(Primitive::Int(*u as i64)), + _ => Value::Primitive(Primitive::String(binary_subtype_to_string(*bst))), + } + .tagged(tag), + ); + collected.insert_tagged( + "$binary".to_string(), + Value::Binary(bytes.to_owned()).tagged(tag), + ); + collected.into_tagged_value() } Bson::ObjectId(obj_id) => Value::Primitive(Primitive::String(obj_id.to_hex())).tagged(tag), Bson::UtcDatetime(dt) => Value::Primitive(Primitive::Date(*dt)).tagged(tag), Bson::Symbol(s) => { - let mut collected = TaggedDictBuilder::new(tag); - collected.insert_tagged( - "$symbol".to_string(), - Value::Primitive(Primitive::String(String::from(s))).tagged(tag), - ); - collected.into_tagged_value() + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$symbol".to_string(), + Value::Primitive(Primitive::String(String::from(s))).tagged(tag), + ); + collected.into_tagged_value() } } } @@ -125,7 +126,8 @@ fn binary_subtype_to_string(bst: BinarySubtype) -> String { BinarySubtype::Uuid => "uuid", BinarySubtype::Md5 => "md5", _ => unreachable!(), - }.to_string() + } + .to_string() } #[derive(Debug)] diff --git a/src/commands/last.rs b/src/commands/last.rs index 18a2da96a..f776b7d65 100644 --- a/src/commands/last.rs +++ b/src/commands/last.rs @@ -44,7 +44,7 @@ fn last(args: CommandArgs, registry: &CommandRegistry) -> Result Result, ShellError> { match extension { - Some(x) if x == "csv" => crate::commands::from_csv::from_csv_string_to_value( - contents, - false, - contents_tag, - ) - .map_err(move |_| { - ShellError::labeled_error("Could not open as CSV", "could not open as CSV", name_span) - }), + Some(x) if x == "csv" => { + crate::commands::from_csv::from_csv_string_to_value(contents, false, contents_tag) + .map_err(move |_| { + ShellError::labeled_error( + "Could not open as CSV", + "could not open as CSV", + name_span, + ) + }) + } Some(x) if x == "toml" => { crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err( move |_| { @@ -507,9 +509,9 @@ pub fn parse_binary_as_value( crate::commands::from_bson::from_bson_bytes_to_value(contents, contents_tag).map_err( move |_| { ShellError::labeled_error( - "Could not open as BSON", - "could not open as BSON", - name_span, + "Could not open as BSON", + "could not open as BSON", + name_span, ) }, ) diff --git a/src/commands/sort_by.rs b/src/commands/sort_by.rs index 820e0a471..1a8d74b03 100644 --- a/src/commands/sort_by.rs +++ b/src/commands/sort_by.rs @@ -46,9 +46,7 @@ fn sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result>>>() }; if reverse { - vec.sort_by_cached_key(|item| { - std::cmp::Reverse(calc_key(item)) - }); + vec.sort_by_cached_key(|item| std::cmp::Reverse(calc_key(item))); } else { vec.sort_by_cached_key(calc_key); } diff --git a/src/git.rs b/src/git.rs index 4782765a2..11b37ab5d 100644 --- a/src/git.rs +++ b/src/git.rs @@ -7,15 +7,13 @@ pub fn current_branch() -> Option { Ok(repo) => { let r = repo.head(); match r { - Ok(r) => { - match r.shorthand() { - Some(s) => Some(s.to_string()), - None => None, - } + Ok(r) => match r.shorthand() { + Some(s) => Some(s.to_string()), + None => None, }, - _ => None + _ => None, } - }, - _ => None + } + _ => None, } } diff --git a/src/object/base.rs b/src/object/base.rs index 56c63d257..612b8f529 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -122,10 +122,8 @@ impl Primitive { pub fn style(&self) -> &'static str { match self { Primitive::Bytes(0) => "c", // centre 'missing' indicator - Primitive::Int(_) | - Primitive::Bytes(_) | - Primitive::Float(_) => "r", - _ => "" + Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Float(_) => "r", + _ => "", } } } @@ -472,7 +470,7 @@ impl Value { crate fn style_leaf(&self) -> &'static str { match self { Value::Primitive(p) => p.style(), - _ => "" + _ => "", } } diff --git a/src/utils.rs b/src/utils.rs index 99a59e850..7a0527565 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,7 +105,10 @@ impl FileStructure { self.root = path.to_path_buf(); } - pub fn paths_applying_with(&mut self, to: F) -> Result, Box> + pub fn paths_applying_with( + &mut self, + to: F, + ) -> Result, Box> where F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box>, { @@ -175,7 +178,8 @@ mod tests { fn prepares_and_decorates_source_files_for_copying() { let mut res = FileStructure::new(); - res.walk_decorate(fixtures().as_path()).expect("Can not decorate files traversal."); + res.walk_decorate(fixtures().as_path()) + .expect("Can not decorate files traversal."); assert_eq!( res.resources, diff --git a/tests/command_mkdir_tests.rs b/tests/command_mkdir_tests.rs index b8564a726..fcc88ee9f 100644 --- a/tests/command_mkdir_tests.rs +++ b/tests/command_mkdir_tests.rs @@ -25,7 +25,7 @@ fn accepts_and_creates_directories() { let full_path = format!("{}/{}", Playground::root(), sandbox); nu!(_output, cwd(&full_path), "mkdir dir_1 dir_2 dir_3"); - + assert!(h::files_exist_at( vec![Path::new("dir_1"), Path::new("dir_2"), Path::new("dir_3")], PathBuf::from(&full_path) diff --git a/tests/command_mv_tests.rs b/tests/command_mv_tests.rs index 62132c7f2..dc4c1a25f 100644 --- a/tests/command_mv_tests.rs +++ b/tests/command_mv_tests.rs @@ -8,15 +8,13 @@ use std::path::{Path, PathBuf}; #[test] fn moves_a_file() { let sandbox = Playground::setup_for("mv_test_1") - .with_files(vec![ - EmptyFile("andres.txt"), - ]) + .with_files(vec![EmptyFile("andres.txt")]) .mkdir("expected") .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); let original = format!("{}/{}", full_path, "andres.txt"); - let expected = format!("{}/{}", full_path, "expected/yehuda.txt"); + let expected = format!("{}/{}", full_path, "expected/yehuda.txt"); nu!( _output, @@ -31,21 +29,14 @@ fn moves_a_file() { #[test] fn overwrites_if_moving_to_existing_file() { let sandbox = Playground::setup_for("mv_test_2") - .with_files(vec![ - EmptyFile("andres.txt"), - EmptyFile("jonathan.txt"), - ]) + .with_files(vec![EmptyFile("andres.txt"), EmptyFile("jonathan.txt")]) .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); let original = format!("{}/{}", full_path, "andres.txt"); - let expected = format!("{}/{}", full_path, "jonathan.txt"); + let expected = format!("{}/{}", full_path, "jonathan.txt"); - nu!( - _output, - cwd(&full_path), - "mv andres.txt jonathan.txt" - ); + nu!(_output, cwd(&full_path), "mv andres.txt jonathan.txt"); assert!(!h::file_exists_at(PathBuf::from(original))); assert!(h::file_exists_at(PathBuf::from(expected))); @@ -58,14 +49,10 @@ fn moves_a_directory() { .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); - let original_dir = format!("{}/{}", full_path, "empty_dir"); - let expected = format!("{}/{}", full_path, "renamed_dir"); + let original_dir = format!("{}/{}", full_path, "empty_dir"); + let expected = format!("{}/{}", full_path, "renamed_dir"); - nu!( - _output, - cwd(&full_path), - "mv empty_dir renamed_dir" - ); + nu!(_output, cwd(&full_path), "mv empty_dir renamed_dir"); assert!(!h::dir_exists_at(PathBuf::from(original_dir))); assert!(h::dir_exists_at(PathBuf::from(expected))); @@ -74,22 +61,15 @@ fn moves_a_directory() { #[test] fn moves_the_file_inside_directory_if_path_to_move_is_existing_directory() { let sandbox = Playground::setup_for("mv_test_4") - .with_files(vec![ - EmptyFile("jonathan.txt"), - ]) + .with_files(vec![EmptyFile("jonathan.txt")]) .mkdir("expected") .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); - let original_dir = format!("{}/{}", full_path, "jonathan.txt"); - let expected = format!("{}/{}", full_path, "expected/jonathan.txt"); - - nu!( - _output, - cwd(&full_path), - "mv jonathan.txt expected" - ); + let original_dir = format!("{}/{}", full_path, "jonathan.txt"); + let expected = format!("{}/{}", full_path, "expected/jonathan.txt"); + nu!(_output, cwd(&full_path), "mv jonathan.txt expected"); assert!(!h::file_exists_at(PathBuf::from(original_dir))); assert!(h::file_exists_at(PathBuf::from(expected))); @@ -99,22 +79,15 @@ fn moves_the_file_inside_directory_if_path_to_move_is_existing_directory() { fn moves_the_directory_inside_directory_if_path_to_move_is_existing_directory() { let sandbox = Playground::setup_for("mv_test_5") .within("contributors") - .with_files(vec![ - EmptyFile("jonathan.txt"), - ]) + .with_files(vec![EmptyFile("jonathan.txt")]) .mkdir("expected") .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); - let original_dir = format!("{}/{}", full_path, "contributors"); - let expected = format!("{}/{}", full_path, "expected/contributors"); - - nu!( - _output, - cwd(&full_path), - "mv contributors expected" - ); + let original_dir = format!("{}/{}", full_path, "contributors"); + let expected = format!("{}/{}", full_path, "expected/contributors"); + nu!(_output, cwd(&full_path), "mv contributors expected"); assert!(!h::dir_exists_at(PathBuf::from(original_dir))); assert!(h::file_exists_at(PathBuf::from(expected))); @@ -124,14 +97,12 @@ fn moves_the_directory_inside_directory_if_path_to_move_is_existing_directory() fn moves_the_directory_inside_directory_if_path_to_move_is_nonexistent_directory() { let sandbox = Playground::setup_for("mv_test_6") .within("contributors") - .with_files(vec![ - EmptyFile("jonathan.txt"), - ]) + .with_files(vec![EmptyFile("jonathan.txt")]) .mkdir("expected") .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); - let original_dir = format!("{}/{}", full_path, "contributors"); + let original_dir = format!("{}/{}", full_path, "contributors"); nu!( _output, @@ -139,7 +110,10 @@ fn moves_the_directory_inside_directory_if_path_to_move_is_nonexistent_directory "mv contributors expected/this_dir_exists_now/los_tres_amigos" ); - let expected = format!("{}/{}", full_path, "expected/this_dir_exists_now/los_tres_amigos"); + let expected = format!( + "{}/{}", + full_path, "expected/this_dir_exists_now/los_tres_amigos" + ); assert!(!h::dir_exists_at(PathBuf::from(original_dir))); assert!(h::file_exists_at(PathBuf::from(expected))); @@ -168,11 +142,7 @@ fn moves_using_path_with_wildcard() { let work_dir = format!("{}/{}", full_path, "work_dir"); let expected_copies_path = format!("{}/{}", full_path, "expected"); - nu!( - _output, - cwd(&work_dir), - "mv ../originals/*.ini ../expected" - ); + nu!(_output, cwd(&work_dir), "mv ../originals/*.ini ../expected"); assert!(h::files_exist_at( vec![ @@ -185,7 +155,6 @@ fn moves_using_path_with_wildcard() { )); } - #[test] fn moves_using_a_glob() { let sandbox = Playground::setup_for("mv_test_8") @@ -204,11 +173,7 @@ fn moves_using_a_glob() { let work_dir = format!("{}/{}", full_path, "work_dir"); let expected_copies_path = format!("{}/{}", full_path, "expected"); - nu!( - _output, - cwd(&work_dir), - "mv ../meals/* ../expected" - ); + nu!(_output, cwd(&work_dir), "mv ../meals/* ../expected"); assert!(h::dir_exists_at(PathBuf::from(meal_dir))); assert!(h::files_exist_at( @@ -219,4 +184,4 @@ fn moves_using_a_glob() { ], PathBuf::from(&expected_copies_path) )); -} \ No newline at end of file +} diff --git a/tests/command_open_tests.rs b/tests/command_open_tests.rs index abcd216e1..bf33ec63f 100644 --- a/tests/command_open_tests.rs +++ b/tests/command_open_tests.rs @@ -111,4 +111,4 @@ fn errors_if_file_not_found() { ); assert!(output.contains("File could not be opened")); -} \ No newline at end of file +} diff --git a/tests/command_rm_tests.rs b/tests/command_rm_tests.rs index f53510083..76e712820 100644 --- a/tests/command_rm_tests.rs +++ b/tests/command_rm_tests.rs @@ -90,7 +90,7 @@ fn rm_removes_deeply_nested_directories_with_wildcard_and_recursive_flag() { .test_dir_name(); let full_path = format!("{}/{}", Playground::root(), sandbox); - + nu!( _output, cwd("tests/fixtures/nuplayground/rm_wildcard_test_2"), @@ -98,10 +98,7 @@ fn rm_removes_deeply_nested_directories_with_wildcard_and_recursive_flag() { ); assert!(!h::files_exist_at( - vec![ - Path::new("src/parser/parse"), - Path::new("src/parser/hir"), - ], + vec![Path::new("src/parser/parse"), Path::new("src/parser/hir"),], PathBuf::from(&full_path) )); } @@ -150,7 +147,11 @@ fn rm_errors_if_attempting_to_delete_a_directory_with_content_without_recursive_ let full_path = format!("{}/{}", Playground::root(), sandbox); - nu_error!(output, cwd(&Playground::root()), "rm rm_prevent_directory_removal_without_flag_test"); + nu_error!( + output, + cwd(&Playground::root()), + "rm rm_prevent_directory_removal_without_flag_test" + ); assert!(h::file_exists_at(PathBuf::from(full_path))); assert!(output.contains("is a directory")); @@ -168,4 +169,4 @@ fn rm_errors_if_attempting_to_delete_two_dot_as_argument() { nu_error!(output, cwd(&Playground::root()), "rm .."); assert!(output.contains("may not be removed")); -} \ No newline at end of file +} diff --git a/tests/filter_inc_tests.rs b/tests/filter_inc_tests.rs index 430c3076c..449380961 100644 --- a/tests/filter_inc_tests.rs +++ b/tests/filter_inc_tests.rs @@ -16,14 +16,15 @@ fn can_only_apply_one() { #[test] fn by_one_with_field_passed() { - Playground::setup_for("plugin_inc_by_one_with_field_passed_test") - .with_files(vec![FileWithContent( + Playground::setup_for("plugin_inc_by_one_with_field_passed_test").with_files(vec![ + FileWithContent( "sample.toml", r#" [package] edition = "2018" "#, - )]); + ), + ]); nu!( output, @@ -36,35 +37,34 @@ fn by_one_with_field_passed() { #[test] fn by_one_with_no_field_passed() { - Playground::setup_for("plugin_inc_by_one_with_no_field_passed_test") - .with_files(vec![FileWithContent( + Playground::setup_for("plugin_inc_by_one_with_no_field_passed_test").with_files(vec![ + FileWithContent( "sample.toml", r#" [package] contributors = "2" "#, - )]); - + ), + ]); + nu!( output, cwd("tests/fixtures/nuplayground/plugin_inc_by_one_with_no_field_passed_test"), "open sample.toml | get package.contributors | inc | echo $it" ); - + assert_eq!(output, "3"); } - #[test] fn semversion_major_inc() { - Playground::setup_for("plugin_inc_major_semversion_test") - .with_files(vec![FileWithContent( - "sample.toml", - r#" + Playground::setup_for("plugin_inc_major_semversion_test").with_files(vec![FileWithContent( + "sample.toml", + r#" [package] version = "0.1.3" "#, - )]); + )]); nu!( output, @@ -77,14 +77,13 @@ fn semversion_major_inc() { #[test] fn semversion_minor_inc() { - Playground::setup_for("plugin_inc_minor_semversion_test") - .with_files(vec![FileWithContent( - "sample.toml", - r#" + Playground::setup_for("plugin_inc_minor_semversion_test").with_files(vec![FileWithContent( + "sample.toml", + r#" [package] version = "0.1.3" "#, - )]); + )]); nu!( output, @@ -97,14 +96,13 @@ fn semversion_minor_inc() { #[test] fn semversion_patch_inc() { - Playground::setup_for("plugin_inc_patch_semversion_test") - .with_files(vec![FileWithContent( - "sample.toml", - r#" + Playground::setup_for("plugin_inc_patch_semversion_test").with_files(vec![FileWithContent( + "sample.toml", + r#" [package] version = "0.1.3" "#, - )]); + )]); nu!( output, @@ -117,14 +115,15 @@ fn semversion_patch_inc() { #[test] fn semversion_without_passing_field() { - Playground::setup_for("plugin_inc_semversion_without_passing_field_test") - .with_files(vec![FileWithContent( + Playground::setup_for("plugin_inc_semversion_without_passing_field_test").with_files(vec![ + FileWithContent( "sample.toml", r#" [package] version = "0.1.3" "#, - )]); + ), + ]); nu!( output, @@ -133,4 +132,4 @@ fn semversion_without_passing_field() { ); assert_eq!(output, "0.1.4"); -} \ No newline at end of file +} From ce0113eb19720d14dfeddc5fc2052c500858c3b9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 26 Aug 2019 21:26:10 +0200 Subject: [PATCH 04/12] Replace use of unstable Option::flatten() with and_then() --- src/commands/save.rs | 2 +- src/commands/tags.rs | 2 +- src/lib.rs | 1 - src/plugins/binaryview.rs | 3 +-- src/plugins/textview.rs | 4 +--- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/commands/save.rs b/src/commands/save.rs index 37882f429..da5c09350 100644 --- a/src/commands/save.rs +++ b/src/commands/save.rs @@ -59,7 +59,7 @@ fn save( // If there is no filename, check the metadata for the origin filename if input.len() > 0 { let origin = input[0].origin(); - match origin.map(|x| source_map.get(&x)).flatten() { + match origin.and_then(|x| source_map.get(&x)) { Some(path) => match path { SpanSource::File(file) => { full_path.push(Path::new(file)); diff --git a/src/commands/tags.rs b/src/commands/tags.rs index b916c03f8..01c7565d5 100644 --- a/src/commands/tags.rs +++ b/src/commands/tags.rs @@ -38,7 +38,7 @@ fn tags(args: CommandArgs, _registry: &CommandRegistry) -> Result { tags.insert("origin", Value::string(source)); } diff --git a/src/lib.rs b/src/lib.rs index a9ef8740f..cead5e2f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ #![feature(generators)] #![feature(try_trait)] #![feature(bind_by_move_pattern_guards)] -#![feature(option_flattening)] #![feature(specialization)] #![feature(proc_macro_hygiene)] diff --git a/src/plugins/binaryview.rs b/src/plugins/binaryview.rs index 5668fa869..58ae9756a 100644 --- a/src/plugins/binaryview.rs +++ b/src/plugins/binaryview.rs @@ -1,4 +1,3 @@ -#![feature(option_flattening)] use crossterm::{cursor, terminal, Attribute, RawScreen}; use indexmap::IndexMap; use nu::{ @@ -32,7 +31,7 @@ impl Plugin for BinaryView { let value_origin = v.origin(); match v.item { Value::Binary(b) => { - let source = value_origin.map(|x| call_info.source_map.get(&x)).flatten(); + let source = value_origin.and_then(|x| call_info.source_map.get(&x)); let _ = view_binary(&b, source, call_info.args.has("lores")); } _ => {} diff --git a/src/plugins/textview.rs b/src/plugins/textview.rs index 92d95c83e..238144cbe 100644 --- a/src/plugins/textview.rs +++ b/src/plugins/textview.rs @@ -1,5 +1,3 @@ -#![feature(option_flattening)] - use crossterm::{cursor, terminal, RawScreen}; use crossterm::{InputEvent, KeyEvent}; use indexmap::IndexMap; @@ -217,7 +215,7 @@ fn view_text_value(value: &Tagged, source_map: &SourceMap) { let value_origin = value.origin(); match value.item { Value::Primitive(Primitive::String(ref s)) => { - let source = value_origin.map(|x| source_map.get(&x)).flatten(); + let source = value_origin.and_then(|x| source_map.get(&x)); if let Some(source) = source { let extension: Option = match source { From 91093f2ab256a7d149098803b0c311b30ce18f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Mon, 26 Aug 2019 17:16:39 -0500 Subject: [PATCH 05/12] Avoid panicking if history can't be saved. --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 945e60754..fa3a275c2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -297,7 +297,7 @@ pub async fn cli() -> Result<(), Box> { } ctrlcbreak = false; } - rl.save_history("history.txt")?; + let _ = rl.save_history("history.txt"); Ok(()) } From 3e699db57c58ce722fd20ca3fc8492a5de6723f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Mon, 26 Aug 2019 17:41:57 -0500 Subject: [PATCH 06/12] Aviso. --- src/cli.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index fa3a275c2..614ad28f9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -217,6 +217,7 @@ pub async fn cli() -> Result<(), Box> { let _ = ansi_term::enable_ansi_support(); } + // we are ok if history does not exist let _ = rl.load_history("history.txt"); let ctrl_c = Arc::new(AtomicBool::new(false)); @@ -297,6 +298,8 @@ pub async fn cli() -> Result<(), Box> { } ctrlcbreak = false; } + + // we are ok if we can not save history let _ = rl.save_history("history.txt"); Ok(()) From 87a99bbabfdb4cc151c07857c999929df1fd57b5 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Mon, 26 Aug 2019 10:16:34 -0400 Subject: [PATCH 07/12] Implement to-bson --- src/cli.rs | 1 + src/commands.rs | 2 + src/commands/from_bson.rs | 10 +- src/commands/to_bson.rs | 231 ++++++++++++++++++++++++++++++++++++++ src/object/base.rs | 42 +++++++ tests/filters_test.rs | 11 ++ 6 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/commands/to_bson.rs diff --git a/src/cli.rs b/src/cli.rs index 614ad28f9..b59f5634a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -162,6 +162,7 @@ pub async fn cli() -> Result<(), Box> { whole_stream_command(Reverse), whole_stream_command(Trim), whole_stream_command(ToArray), + whole_stream_command(ToBSON), whole_stream_command(ToCSV), whole_stream_command(ToJSON), whole_stream_command(ToTOML), diff --git a/src/commands.rs b/src/commands.rs index 9b17e9db9..d1d9297fd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -48,6 +48,7 @@ crate mod split_row; crate mod table; crate mod tags; crate mod to_array; +crate mod to_bson; crate mod to_csv; crate mod to_json; crate mod to_toml; @@ -104,6 +105,7 @@ crate use split_row::SplitRow; crate use table::Table; crate use tags::Tags; crate use to_array::ToArray; +crate use to_bson::ToBSON; crate use to_csv::ToCSV; crate use to_json::ToJSON; crate use to_toml::ToTOML; diff --git a/src/commands/from_bson.rs b/src/commands/from_bson.rs index 2d98e1907..e244614cc 100644 --- a/src/commands/from_bson.rs +++ b/src/commands/from_bson.rs @@ -58,6 +58,7 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into) -> Tagged Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), Bson::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), Bson::JavaScriptCode(js) => { @@ -104,7 +105,14 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into) -> Tagged Value::Primitive(Primitive::String(obj_id.to_hex())).tagged(tag), + Bson::ObjectId(obj_id) => { + let mut collected = TaggedDictBuilder::new(tag); + collected.insert_tagged( + "$object_id".to_string(), + Value::Primitive(Primitive::String(obj_id.to_hex())).tagged(tag), + ); + collected.into_tagged_value() + } Bson::UtcDatetime(dt) => Value::Primitive(Primitive::Date(*dt)).tagged(tag), Bson::Symbol(s) => { let mut collected = TaggedDictBuilder::new(tag); diff --git a/src/commands/to_bson.rs b/src/commands/to_bson.rs new file mode 100644 index 000000000..60dc1cf2c --- /dev/null +++ b/src/commands/to_bson.rs @@ -0,0 +1,231 @@ +use crate::commands::WholeStreamCommand; +use crate::object::{Dictionary, Primitive, Value}; +use crate::prelude::*; +use bson::{encode_document, oid::ObjectId, spec::BinarySubtype, Bson, Document}; +use std::convert::TryInto; + +pub struct ToBSON; + +impl WholeStreamCommand for ToBSON { + fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + to_bson(args, registry) + } + + fn name(&self) -> &str { + "to-bson" + } + + fn signature(&self) -> Signature { + Signature::build("to-bson") + } +} + +pub fn value_to_bson_value(v: &Value) -> Bson { + match v { + Value::Primitive(Primitive::Boolean(b)) => Bson::Boolean(*b), + Value::Primitive(Primitive::Bytes(b)) => Bson::I64(*b as i64), + Value::Primitive(Primitive::Date(d)) => Bson::UtcDatetime(*d), + Value::Primitive(Primitive::EndOfStream) => Bson::Null, + Value::Primitive(Primitive::BeginningOfStream) => Bson::Null, + Value::Primitive(Primitive::Float(f)) => Bson::FloatingPoint(f.into_inner()), + Value::Primitive(Primitive::Int(i)) => Bson::I64(*i), + Value::Primitive(Primitive::Nothing) => Bson::Null, + Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()), + Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()), + Value::List(l) => Bson::Array(l.iter().map(|x| value_to_bson_value(x)).collect()), + Value::Block(_) => Bson::Null, + Value::Binary(b) => Bson::Binary(BinarySubtype::Generic, b.clone()), + Value::Object(o) => object_value_to_bson(o), + } +} + +// object_value_to_bson handles all Objects, even those that correspond to special +// types (things like regex or javascript code). +fn object_value_to_bson(o: &Dictionary) -> Bson { + let mut it = o.entries.iter(); + if it.len() > 2 { + return generic_object_value_to_bson(o); + } + match it.next() { + Some((regex, tagged_regex_value)) if regex == "$regex" => match it.next() { + Some((options, tagged_opts_value)) if options == "$options" => { + let r: Result = tagged_regex_value.try_into(); + let opts: Result = tagged_opts_value.try_into(); + if r.is_err() || opts.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::RegExp(r.unwrap(), opts.unwrap()) + } + } + _ => generic_object_value_to_bson(o), + }, + Some((javascript, tagged_javascript_value)) if javascript == "$javascript" => { + match it.next() { + Some((scope, tagged_scope_value)) if scope == "$scope" => { + let js: Result = tagged_javascript_value.try_into(); + let s: Result<&Dictionary, _> = tagged_scope_value.try_into(); + if js.is_err() || s.is_err() { + generic_object_value_to_bson(o) + } else { + if let Bson::Document(doc) = object_value_to_bson(s.unwrap()) { + Bson::JavaScriptCodeWithScope(js.unwrap(), doc) + } else { + generic_object_value_to_bson(o) + } + } + } + None => { + let js: Result = tagged_javascript_value.try_into(); + if js.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::JavaScriptCode(js.unwrap()) + } + } + _ => generic_object_value_to_bson(o), + } + } + Some((timestamp, tagged_timestamp_value)) if timestamp == "$timestamp" => { + let ts: Result = tagged_timestamp_value.try_into(); + if ts.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::TimeStamp(ts.unwrap()) + } + } + Some((binary_subtype, tagged_binary_subtype_value)) + if binary_subtype == "$binary_subtype" => + { + match it.next() { + Some((binary, tagged_bin_value)) if binary == "$binary" => { + let bst = get_binary_subtype(tagged_binary_subtype_value); + let bin: Result, _> = tagged_bin_value.try_into(); + if bst.is_none() || bin.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::Binary(bst.unwrap(), bin.unwrap()) + } + } + _ => generic_object_value_to_bson(o), + } + } + Some((object_id, tagged_object_id_value)) if object_id == "$object_id" => { + let obj_id: Result = tagged_object_id_value.try_into(); + if obj_id.is_err() { + generic_object_value_to_bson(o) + } else { + let obj_id = ObjectId::with_string(&obj_id.unwrap()); + if obj_id.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::ObjectId(obj_id.unwrap()) + } + } + } + Some((symbol, tagged_symbol_value)) if symbol == "$symbol" => { + let sym: Result = tagged_symbol_value.try_into(); + if sym.is_err() { + generic_object_value_to_bson(o) + } else { + Bson::Symbol(sym.unwrap()) + } + } + _ => generic_object_value_to_bson(o), + } +} + +fn get_binary_subtype<'a>(tagged_value: &'a Tagged) -> Option { + match tagged_value.item() { + Value::Primitive(Primitive::String(s)) => Some(match s.as_ref() { + "generic" => BinarySubtype::Generic, + "function" => BinarySubtype::Function, + "binary_old" => BinarySubtype::BinaryOld, + "uuid_old" => BinarySubtype::UuidOld, + "uuid" => BinarySubtype::Uuid, + "md5" => BinarySubtype::Md5, + _ => unreachable!(), + }), + Value::Primitive(Primitive::Int(i)) => Some(BinarySubtype::UserDefined(*i as u8)), + _ => None, + } +} + +// generic_object_value_bson handles any Object that does not +// correspond to a special bson type (things like regex or javascript code). +fn generic_object_value_to_bson(o: &Dictionary) -> Bson { + let mut doc = Document::new(); + for (k, v) in o.entries.iter() { + doc.insert(k.clone(), value_to_bson_value(v)); + } + Bson::Document(doc) +} + +fn shell_encode_document( + writer: &mut Vec, + doc: Document, + span: Span, +) -> Result<(), ShellError> { + match encode_document(writer, &doc) { + Err(e) => Err(ShellError::labeled_error( + format!("Failed to encode document due to: {:?}", e), + "requires BSON-compatible document", + span, + )), + _ => Ok(()), + } +} + +fn bson_value_to_bytes(bson: Bson, span: Span) -> Result, ShellError> { + let mut out = Vec::new(); + match bson { + Bson::Array(a) => { + for v in a.into_iter() { + match v { + Bson::Document(d) => shell_encode_document(&mut out, d, span)?, + _ => { + return Err(ShellError::labeled_error( + format!("All top level values must be Documents, got {:?}", v), + "requires BSON-compatible document", + span, + )) + } + } + } + } + Bson::Document(d) => shell_encode_document(&mut out, d, span)?, + _ => { + return Err(ShellError::labeled_error( + format!("All top level values must be Documents, got {:?}", bson), + "requires BSON-compatible document", + span, + )) + } + } + Ok(out) +} + +fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result { + let args = args.evaluate_once(registry)?; + let name_span = args.name_span(); + let out = args.input; + + Ok(out + .values + .map( + move |a| match bson_value_to_bytes(value_to_bson_value(&a), name_span) { + Ok(x) => ReturnSuccess::value(Value::Binary(x).simple_spanned(name_span)), + _ => Err(ShellError::labeled_error_with_secondary( + "Expected an object with BSON-compatible structure from pipeline", + "requires BSON-compatible input: Must be Array or Object", + name_span, + format!("{} originates from here", a.item.type_name()), + a.span(), + )), + }, + ) + .to_output_stream()) +} diff --git a/src/object/base.rs b/src/object/base.rs index 612b8f529..5b136b3d4 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -243,6 +243,48 @@ impl std::convert::TryFrom<&'a Tagged> for i64 { } } +impl std::convert::TryFrom<&'a Tagged> for String { + type Error = ShellError; + + fn try_from(value: &'a Tagged) -> Result { + match value.item() { + Value::Primitive(Primitive::String(s)) => Ok(s.clone()), + v => Err(ShellError::type_error( + "String", + value.copy_span(v.type_name()), + )), + } + } +} + +impl std::convert::TryFrom<&'a Tagged> for Vec { + type Error = ShellError; + + fn try_from(value: &'a Tagged) -> Result, ShellError> { + match value.item() { + Value::Binary(b) => Ok(b.clone()), + v => Err(ShellError::type_error( + "Binary", + value.copy_span(v.type_name()), + )), + } + } +} + +impl std::convert::TryFrom<&'a Tagged> for &'a crate::object::Dictionary { + type Error = ShellError; + + fn try_from(value: &'a Tagged) -> Result<&'a crate::object::Dictionary, ShellError> { + match value.item() { + Value::Object(d) => Ok(d), + v => Err(ShellError::type_error( + "Dictionary", + value.copy_span(v.type_name()), + )), + } + } +} + #[derive(Serialize, Deserialize)] pub enum Switch { Present, diff --git a/tests/filters_test.rs b/tests/filters_test.rs index 7baca493a..ac68ea5ff 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -106,6 +106,17 @@ fn can_convert_table_to_json_text_and_from_json_text_back_into_table() { assert_eq!(output, "markup"); } +#[test] +fn can_convert_json_text_to_bson_and_back_into_table() { + nu!( + output, + cwd("tests/fixtures/formats"), + "echo '{\"root\":[{\"x\": 2, \"y\": 4}, {\"z\": \"42\"}]}' | from-json | to-bson | from-bson | get root | nth 1 | get z | echo $it" + ); + + assert_eq!(output, "42"); +} + #[test] fn can_convert_table_to_toml_text_and_from_toml_text_back_into_table() { nu!( From 738675259e21f30d869bb033b88fae364b14db4d Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Mon, 26 Aug 2019 21:26:49 -0400 Subject: [PATCH 08/12] Improve test so that it should work on Windows --- tests/command_open_tests.rs | 4 ++-- tests/filters_test.rs | 4 ++-- tests/fixtures/formats/sample.bson | Bin 439 -> 521 bytes 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/command_open_tests.rs b/tests/command_open_tests.rs index bf33ec63f..3f6da5adf 100644 --- a/tests/command_open_tests.rs +++ b/tests/command_open_tests.rs @@ -28,7 +28,7 @@ fn open_can_parse_bson_1() { nu!( output, cwd("tests/fixtures/formats"), - "open sample.bson | nth 0 | get b | echo $it" + "open sample.bson | get root | nth 0 | get b | echo $it" ); assert_eq!(output, "hello"); @@ -39,7 +39,7 @@ fn open_can_parse_bson_2() { nu!( output, cwd("tests/fixtures/formats"), - "open sample.bson | nth 6 | get b | get '$binary_subtype' | echo $it " + "open sample.bson | get root | nth 6 | get b | get '$binary_subtype' | echo $it " ); assert_eq!(output, "function"); diff --git a/tests/filters_test.rs b/tests/filters_test.rs index ac68ea5ff..710f0b89a 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -111,10 +111,10 @@ fn can_convert_json_text_to_bson_and_back_into_table() { nu!( output, cwd("tests/fixtures/formats"), - "echo '{\"root\":[{\"x\": 2, \"y\": 4}, {\"z\": \"42\"}]}' | from-json | to-bson | from-bson | get root | nth 1 | get z | echo $it" + "open sample.bson | to-bson | from-bson | get root | nth 1 | get b | echo $it" ); - assert_eq!(output, "42"); + assert_eq!(output, "whel"); } #[test] diff --git a/tests/fixtures/formats/sample.bson b/tests/fixtures/formats/sample.bson index 95c98eb4e1f1f8ec1517944c23045ba72b098940..951c805065e7d736a2238be45ad4f23ff9734dd5 100644 GIT binary patch delta 227 zcmdna+{vQE$;80G9-o=Q5S!96@gT?9O|5o0Cs~T}^Gg`sGBPkQ8!$}BWMVd)m|@Mt zY&3C^hCj10gBJr#PvW`z#fBBN0&f^?nUfd{f#OWb`6;Ok+(1@NYKcOHf~`WOH3M^T za(+Q7g8)#Ju@XptfPe##U^bb|%BaM|Y&uzuQOTRxjKK{k2ysr5nfJ`N%aejRwFQ|I o8Ki*Xm_{30Gcd3wfo)^VNX^N~S18Xf%1L2hHlN(YXbSWh0B{#FoB#j- delta 144 zcmeBV+0HysDRW|<)x^{4);0_b4D9ilDGafR=k6C9R@4f-VYKB>Vo(N(a|3ZsYKcOH zf~`WOHG=?<%UB5{KtRBOVX`Bm(&RKoMJsC{A7Vz5nfJ`N%aejRwFP+-QS=*IGq5Is WMHn+ub8_+(%JYkIQYK$wGz9 Date: Tue, 27 Aug 2019 13:46:38 +1200 Subject: [PATCH 09/12] Fix having to clean directories when switching between release and debug --- src/cli.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 614ad28f9..9054bfd2d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -121,19 +121,24 @@ fn load_plugins(context: &mut Context) -> Result<(), ShellError> { None => println!("PATH is not defined in the environment."), } - // Also use our debug output for now - let mut path = std::path::PathBuf::from("."); - path.push("target"); - path.push("debug"); + #[cfg(debug_assertions)] + { + // Use our debug plugins in debug mode + let mut path = std::path::PathBuf::from("."); + path.push("target"); + path.push("debug"); + let _ = load_plugins_in_dir(&path, context); + } - let _ = load_plugins_in_dir(&path, context); + #[cfg(not(debug_assertions))] + { + // Use our release plugins in release mode + let mut path = std::path::PathBuf::from("."); + path.push("target"); + path.push("release"); - // Also use our release output for now - let mut path = std::path::PathBuf::from("."); - path.push("target"); - path.push("release"); - - let _ = load_plugins_in_dir(&path, context); + let _ = load_plugins_in_dir(&path, context); + } Ok(()) } From 34292b282a39238bf7deaf09bdadec06fb9473a9 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 26 Aug 2019 12:21:03 -0700 Subject: [PATCH 10/12] Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system. --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/cli.rs | 15 +++- src/commands/classified.rs | 3 +- src/commands/command.rs | 33 ++++++++- src/commands/get.rs | 36 ++++++---- src/commands/mkdir.rs | 2 +- src/commands/open.rs | 27 +++---- src/commands/pick.rs | 2 +- src/commands/plugin.rs | 24 ++++++- src/commands/reject.rs | 2 +- src/commands/sort_by.rs | 51 ++++++-------- src/commands/split_column.rs | 2 +- src/context.rs | 8 +-- src/errors.rs | 15 ++++ src/evaluate/evaluator.rs | 1 + src/format/table.rs | 2 +- src/lib.rs | 1 + src/object/base.rs | 17 +++++ src/object/types.rs | 5 +- src/parser/hir.rs | 21 +++++- src/parser/hir/baseline_parse.rs | 45 ++++++++++++ src/parser/hir/baseline_parse_tokens.rs | 93 ++++++++++++++++++++----- src/parser/hir/named.rs | 2 +- src/parser/parse/parser.rs | 4 ++ src/parser/parse_command.rs | 24 ++++--- src/parser/registry.rs | 12 ++-- src/plugins/add.rs | 20 ++---- src/plugins/binaryview.rs | 15 +--- src/plugins/edit.rs | 20 ++---- src/plugins/inc.rs | 24 +++---- src/plugins/skip.rs | 4 +- src/plugins/str.rs | 39 +++++------ src/plugins/sum.rs | 11 +-- src/plugins/sys.rs | 12 +--- src/plugins/textview.rs | 9 +-- src/plugins/tree.rs | 9 +-- src/prelude.rs | 19 +++++ src/shell/filesystem_shell.rs | 8 ++- src/shell/shell.rs | 2 + src/shell/shell_manager.rs | 9 +++ src/shell/value_shell.rs | 4 ++ src/utils.rs | 9 ++- tests/command_ls_tests.rs | 2 +- 44 files changed, 442 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0778faa5b..76aea16d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,6 +1733,7 @@ dependencies = [ "serde_ini 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "surf 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2646,6 +2647,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shellexpand" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slab" version = "0.4.2" @@ -3568,6 +3574,7 @@ dependencies = [ "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" "checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a" "checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" +"checksum shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de7a5b5a9142fd278a10e0209b021a1b85849352e6951f4f914735c976737564" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum sluice 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec70d7c3b17c262d4a18f7291c6ce62bf47170915f3b795434d3c5c49a4e59b7" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" diff --git a/Cargo.toml b/Cargo.toml index 4b30972a4..906e8e2b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ battery = "0.7.4" textwrap = {version = "0.11.0", features = ["term_size"]} rawkey = {version = "0.1.2", optional = true } clipboard = {version = "0.5", optional = true } +shellexpand = "1.0.0" [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/src/cli.rs b/src/cli.rs index a76ba2084..36d2e8b80 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -58,12 +58,17 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel let mut input = String::new(); match reader.read_line(&mut input) { - Ok(_) => { + Ok(count) => { + trace!("processing response ({} bytes)", count); + let response = serde_json::from_str::>>(&input); match response { Ok(jrpc) => match jrpc.params { Ok(params) => { let fname = path.to_string_lossy(); + + trace!("processing {:?}", params); + if params.is_filter { let fname = fname.to_string(); let name = params.name.clone(); @@ -93,14 +98,18 @@ fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Resu let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?; let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?; + trace!("Looking for plugins in {:?}", path); + match std::fs::read_dir(path) { Ok(p) => { for entry in p { let entry = entry?; let filename = entry.file_name(); let f_name = filename.to_string_lossy(); + if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) { let mut load_path = path.clone(); + trace!("Found {:?}", f_name); load_path.push(f_name.to_string()); load_plugin(&load_path, context)?; } @@ -504,7 +513,9 @@ fn classify_command( trace!(target: "nu::build_pipeline", "classifying {:?}", config); - let args: hir::Call = config.parse_args(call, context.registry(), source)?; + let args: hir::Call = config.parse_args(call, &context, source)?; + + trace!(target: "nu::build_pipeline", "args :: {}", args.debug(source)); Ok(ClassifiedCommand::Internal(InternalCommand { command, diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 28005ceb7..926b08355 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -122,10 +122,11 @@ impl InternalCommand { self.name_span.clone(), context.source_map.clone(), self.args, - source, + &source, objects, ); + let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result); let mut result = result.values; let mut stream = VecDeque::new(); diff --git a/src/commands/command.rs b/src/commands/command.rs index b498b5e66..b11c0a5be 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -422,6 +422,25 @@ pub enum CommandAction { LeaveShell, } +impl ToDebug for CommandAction { + fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result { + match self { + CommandAction::ChangePath(s) => write!(f, "action:change-path={}", s), + CommandAction::AddSpanSource(u, source) => { + write!(f, "action:add-span-source={}@{:?}", u, source) + } + CommandAction::Exit => write!(f, "action:exit"), + CommandAction::EnterShell(s) => write!(f, "action:enter-shell={}", s), + CommandAction::EnterValueShell(t) => { + write!(f, "action:enter-value-shell={:?}", t.debug()) + } + CommandAction::PreviousShell => write!(f, "action:previous-shell"), + CommandAction::NextShell => write!(f, "action:next-shell"), + CommandAction::LeaveShell => write!(f, "action:leave-shell"), + } + } +} + #[derive(Debug, Serialize, Deserialize)] pub enum ReturnSuccess { Value(Tagged), @@ -430,6 +449,16 @@ pub enum ReturnSuccess { pub type ReturnValue = Result; +impl ToDebug for ReturnValue { + fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { + match self { + Err(err) => write!(f, "{}", err.debug(source)), + Ok(ReturnSuccess::Value(v)) => write!(f, "{:?}", v.debug()), + Ok(ReturnSuccess::Action(a)) => write!(f, "{}", a.debug(source)), + } + } +} + impl From> for ReturnValue { fn from(input: Tagged) -> ReturnValue { Ok(ReturnSuccess::Value(input)) @@ -469,7 +498,7 @@ pub trait WholeStreamCommand: Send + Sync { Signature { name: self.name().to_string(), positional: vec![], - rest_positional: true, + rest_positional: None, named: indexmap::IndexMap::new(), is_filter: true, } @@ -491,7 +520,7 @@ pub trait PerItemCommand: Send + Sync { Signature { name: self.name().to_string(), positional: vec![], - rest_positional: true, + rest_positional: None, named: indexmap::IndexMap::new(), is_filter: true, } diff --git a/src/commands/get.rs b/src/commands/get.rs index 37dba702b..cd8db615a 100644 --- a/src/commands/get.rs +++ b/src/commands/get.rs @@ -22,33 +22,41 @@ impl WholeStreamCommand for Get { ) -> Result { args.process(registry, get)?.run() } + fn signature(&self) -> Signature { - Signature::build("get").rest() + Signature::build("get").rest(SyntaxType::Member) } } fn get_member(path: &Tagged, obj: &Tagged) -> Result, ShellError> { - let mut current = obj; + let mut current = Some(obj); for p in path.split(".") { - match current.get_data_by_key(p) { - Some(v) => current = v, - None => { + if let Some(obj) = current { + current = match obj.get_data_by_key(p) { + Some(v) => Some(v), + None => // Before we give up, see if they gave us a path that matches a field name by itself - match obj.get_data_by_key(&path.item) { - Some(v) => return Ok(v.clone()), - None => { - return Err(ShellError::labeled_error( - "Unknown column", - "table missing column", - path.span(), - )); + { + match obj.get_data_by_key(&path.item) { + Some(v) => return Ok(v.clone()), + None => { + return Err(ShellError::labeled_error( + "Unknown column", + "table missing column", + path.span(), + )); + } } } } } } - Ok(current.clone()) + match current { + Some(v) => Ok(v.clone()), + None => Ok(Value::nothing().tagged(obj.tag)), + } + // Ok(current.clone()) } pub fn get( diff --git a/src/commands/mkdir.rs b/src/commands/mkdir.rs index 895b46b32..5f92f1177 100644 --- a/src/commands/mkdir.rs +++ b/src/commands/mkdir.rs @@ -27,7 +27,7 @@ impl PerItemCommand for Mkdir { } fn signature(&self) -> Signature { - Signature::build("mkdir").rest() + Signature::build("mkdir").rest(SyntaxType::Path) } } diff --git a/src/commands/open.rs b/src/commands/open.rs index db5b1a478..4282da8ea 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -44,7 +44,8 @@ fn run(call_info: &CallInfo, shell_manager: &ShellManager) -> Result file, }; - let path_str = path.as_string()?; + let path_buf = path.as_path()?; + let path_str = path_buf.display().to_string(); let path_span = path.span(); let name_span = call_info.name_span; let has_raw = call_info.args.has("raw"); @@ -426,14 +427,16 @@ pub fn parse_string_as_value( name_span: Span, ) -> Result, ShellError> { match extension { - Some(x) if x == "csv" => crate::commands::from_csv::from_csv_string_to_value( - contents, - false, - contents_tag, - ) - .map_err(move |_| { - ShellError::labeled_error("Could not open as CSV", "could not open as CSV", name_span) - }), + Some(x) if x == "csv" => { + crate::commands::from_csv::from_csv_string_to_value(contents, false, contents_tag) + .map_err(move |_| { + ShellError::labeled_error( + "Could not open as CSV", + "could not open as CSV", + name_span, + ) + }) + } Some(x) if x == "toml" => { crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err( move |_| { @@ -507,9 +510,9 @@ pub fn parse_binary_as_value( crate::commands::from_bson::from_bson_bytes_to_value(contents, contents_tag).map_err( move |_| { ShellError::labeled_error( - "Could not open as BSON", - "could not open as BSON", - name_span, + "Could not open as BSON", + "could not open as BSON", + name_span, ) }, ) diff --git a/src/commands/pick.rs b/src/commands/pick.rs index bee63ecdf..6c7fca8f2 100644 --- a/src/commands/pick.rs +++ b/src/commands/pick.rs @@ -17,7 +17,7 @@ impl WholeStreamCommand for Pick { } fn signature(&self) -> Signature { - Signature::build("pick").rest() + Signature::build("pick").rest(SyntaxType::Any) } fn run( diff --git a/src/commands/plugin.rs b/src/commands/plugin.rs index 0b02b40f7..09eadc973 100644 --- a/src/commands/plugin.rs +++ b/src/commands/plugin.rs @@ -3,6 +3,7 @@ use crate::errors::ShellError; use crate::parser::registry; use crate::prelude::*; use derive_new::new; +use log::trace; use serde::{self, Deserialize, Serialize}; use std::io::prelude::*; use std::io::BufReader; @@ -64,6 +65,8 @@ pub fn filter_plugin( args: CommandArgs, registry: &CommandRegistry, ) -> Result { + trace!("filter_plugin :: {}", path); + let args = args.evaluate_once(registry)?; let mut child = std::process::Command::new(path) @@ -80,6 +83,8 @@ pub fn filter_plugin( let call_info = args.call_info.clone(); + trace!("filtering :: {:?}", call_info); + let stream = bos .chain(args.input.values) .chain(eos) @@ -95,7 +100,14 @@ pub fn filter_plugin( let request = JsonRpc::new("begin_filter", call_info.clone()); let request_raw = serde_json::to_string(&request).unwrap(); - let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error + match stdin.write(format!("{}\n", request_raw).as_bytes()) { + Ok(_) => {} + Err(err) => { + let mut result = VecDeque::new(); + result.push_back(Err(ShellError::unexpected(format!("{}", err)))); + return result; + } + } let mut input = String::new(); match reader.read_line(&mut input) { @@ -140,7 +152,15 @@ pub fn filter_plugin( let mut reader = BufReader::new(stdout); let request: JsonRpc> = JsonRpc::new("end_filter", vec![]); - let request_raw = serde_json::to_string(&request).unwrap(); + let request_raw = match serde_json::to_string(&request) { + Ok(req) => req, + Err(err) => { + let mut result = VecDeque::new(); + result.push_back(Err(ShellError::unexpected(format!("{}", err)))); + return result; + } + }; + let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error let mut input = String::new(); diff --git a/src/commands/reject.rs b/src/commands/reject.rs index 4fc60fecf..92aff1cb2 100644 --- a/src/commands/reject.rs +++ b/src/commands/reject.rs @@ -24,7 +24,7 @@ impl WholeStreamCommand for Reject { } fn signature(&self) -> Signature { - Signature::build("reject").rest() + Signature::build("reject").rest(SyntaxType::Member) } } diff --git a/src/commands/sort_by.rs b/src/commands/sort_by.rs index 820e0a471..c726dbedc 100644 --- a/src/commands/sort_by.rs +++ b/src/commands/sort_by.rs @@ -4,13 +4,19 @@ use crate::prelude::*; pub struct SortBy; +#[derive(Deserialize)] +pub struct SortByArgs { + rest: Vec>, + reverse: bool, +} + impl WholeStreamCommand for SortBy { fn run( &self, args: CommandArgs, registry: &CommandRegistry, ) -> Result { - sort_by(args, registry) + args.process(registry, sort_by)?.run() } fn name(&self) -> &str { @@ -18,43 +24,32 @@ impl WholeStreamCommand for SortBy { } fn signature(&self) -> Signature { - Signature::build("sort-by").switch("reverse") + Signature::build("sort-by") + .rest(SyntaxType::String) + .switch("reverse") } } -fn sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result { - let args = args.evaluate_once(registry)?; - let (input, args) = args.parts(); +fn sort_by( + SortByArgs { reverse, rest }: SortByArgs, + mut context: RunnableContext, +) -> Result { + Ok(OutputStream::new(async_stream_block! { + let mut vec = context.input.drain_vec().await; - let fields: Result, _> = args - .positional - .iter() - .flatten() - .map(|a| a.as_string()) - .collect(); - - let fields = fields?; - - let output = input.values.collect::>(); - - let reverse = args.has("reverse"); - let output = output.map(move |mut vec| { let calc_key = |item: &Tagged| { - fields - .iter() + rest.iter() .map(|f| item.get_data_by_key(f).map(|i| i.clone())) .collect::>>>() }; if reverse { - vec.sort_by_cached_key(|item| { - std::cmp::Reverse(calc_key(item)) - }); + vec.sort_by_cached_key(|item| std::cmp::Reverse(calc_key(item))); } else { vec.sort_by_cached_key(calc_key); + }; + + for item in vec { + yield item.into(); } - - vec.into_iter().collect::>() - }); - - Ok(output.flatten_stream().from_input_stream()) + })) } diff --git a/src/commands/split_column.rs b/src/commands/split_column.rs index fbccc89c6..612d58ef6 100644 --- a/src/commands/split_column.rs +++ b/src/commands/split_column.rs @@ -28,7 +28,7 @@ impl WholeStreamCommand for SplitColumn { fn signature(&self) -> Signature { Signature::build("split-column") .required("separator", SyntaxType::Any) - .rest() + .rest(SyntaxType::Member) } } diff --git a/src/context.rs b/src/context.rs index 48cc44be5..0a69979d0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -121,7 +121,7 @@ impl Context { name_span: Span, source_map: SourceMap, args: hir::Call, - source: Text, + source: &Text, input: InputStream, ) -> OutputStream { let command_args = self.command_args(args, input, source, source_map, name_span); @@ -131,13 +131,13 @@ impl Context { fn call_info( &self, args: hir::Call, - source: Text, + source: &Text, source_map: SourceMap, name_span: Span, ) -> UnevaluatedCallInfo { UnevaluatedCallInfo { args, - source, + source: source.clone(), source_map, name_span, } @@ -147,7 +147,7 @@ impl Context { &self, args: hir::Call, input: InputStream, - source: Text, + source: &Text, source_map: SourceMap, name_span: Span, ) -> CommandArgs { diff --git a/src/errors.rs b/src/errors.rs index cd1120dad..84bb96f03 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,6 +5,7 @@ use ansi_term::Color; use derive_new::new; use language_reporting::{Diagnostic, Label, Severity}; use serde::{Deserialize, Serialize}; +use std::fmt; #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum Description { @@ -56,6 +57,12 @@ pub struct ShellError { cause: Option>, } +impl ToDebug for ShellError { + fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { + self.error.fmt_debug(f, source) + } +} + impl serde::de::Error for ShellError { fn custom(msg: T) -> Self where @@ -335,6 +342,7 @@ pub enum ProximateShellError { right: Tagged, }, } + impl ProximateShellError { fn start(self) -> ShellError { ShellError { @@ -344,6 +352,13 @@ impl ProximateShellError { } } +impl ToDebug for ProximateShellError { + fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result { + // TODO: Custom debug for inner spans + write!(f, "{:?}", self) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShellDiagnostic { crate diagnostic: Diagnostic, diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index b58eac220..9d768a46d 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -39,6 +39,7 @@ crate fn evaluate_baseline_expr( ) -> Result, ShellError> { match &expr.item { RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)), + RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())), RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()), RawExpression::Variable(var) => evaluate_reference(var, scope, source), RawExpression::ExternalCommand(external) => evaluate_external(external, scope, source), diff --git a/src/format/table.rs b/src/format/table.rs index ec641bff0..25c41b47c 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -68,7 +68,7 @@ impl TableView { for head in 0..headers.len() { let mut current_row_max = 0; for row in 0..values.len() { - if entries[row][head].len() > current_row_max { + if head > entries[row].len() && entries[row][head].len() > current_row_max { current_row_max = entries[row][head].len(); } } diff --git a/src/lib.rs b/src/lib.rs index a9ef8740f..9f1b24526 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue}; pub use crate::context::{SourceMap, SpanSource}; pub use crate::env::host::BasicHost; pub use crate::object::base::OF64; +pub use crate::parser::hir::SyntaxType; pub use crate::plugin::{serve_plugin, Plugin}; pub use crate::utils::{AbsolutePath, RelativePath}; pub use cli::cli; diff --git a/src/object/base.rs b/src/object/base.rs index ea86386c6..4f4b4f6ba 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -285,6 +285,7 @@ impl Value { } } + // TODO: This is basically a legacy construct, I think pub fn data_descriptors(&self) -> Vec { match self { Value::Primitive(_) => vec![], @@ -542,6 +543,10 @@ impl Value { Value::Primitive(Primitive::String(s.into())) } + pub fn path(s: impl Into) -> Value { + Value::Primitive(Primitive::Path(s.into())) + } + pub fn bytes(s: impl Into) -> Value { Value::Primitive(Primitive::Bytes(s.into())) } @@ -577,6 +582,18 @@ impl Value { } } +impl Tagged { + crate fn as_path(&self) -> Result { + match self.item() { + Value::Primitive(Primitive::Path(path)) => Ok(path.clone()), + other => Err(ShellError::type_error( + "Path", + other.type_name().tagged(self.span()), + )), + } + } +} + crate fn select_fields(obj: &Value, fields: &[String], tag: impl Into) -> Tagged { let mut out = TaggedDictBuilder::new(tag); diff --git a/src/object/types.rs b/src/object/types.rs index 719a990ad..9a29284cb 100644 --- a/src/object/types.rs +++ b/src/object/types.rs @@ -2,7 +2,6 @@ use crate::object::base as value; use crate::parser::hir; use crate::prelude::*; use log::trace; -use std::path::PathBuf; pub trait ExtractType: Sized { fn extract(value: &Tagged) -> Result; @@ -196,9 +195,9 @@ impl ExtractType for std::path::PathBuf { match &value { Tagged { - item: Value::Primitive(Primitive::String(p)), + item: Value::Primitive(Primitive::Path(p)), .. - } => Ok(PathBuf::from(p)), + } => Ok(p.clone()), other => Err(ShellError::type_error("Path", other.tagged_type_name())), } } diff --git a/src/parser/hir.rs b/src/parser/hir.rs index e323812d1..2148a179f 100644 --- a/src/parser/hir.rs +++ b/src/parser/hir.rs @@ -11,16 +11,22 @@ use derive_new::new; use getset::Getters; use serde::{Deserialize, Serialize}; use std::fmt; +use std::path::PathBuf; use crate::evaluate::Scope; -crate use self::baseline_parse::{baseline_parse_single_token, baseline_parse_token_as_string}; -crate use self::baseline_parse_tokens::{baseline_parse_next_expr, SyntaxType, TokensIterator}; +crate use self::baseline_parse::{ + baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, + baseline_parse_token_as_string, +}; +crate use self::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator}; crate use self::binary::Binary; crate use self::external_command::ExternalCommand; crate use self::named::NamedArguments; crate use self::path::Path; +pub use self::baseline_parse_tokens::SyntaxType; + pub fn path(head: impl Into, tail: Vec>>) -> Path { Path::new( head.into(), @@ -68,6 +74,8 @@ impl ToDebug for Call { write!(f, "{}", named.debug(source))?; } + write!(f, ")")?; + Ok(()) } } @@ -81,6 +89,7 @@ pub enum RawExpression { Block(Vec), List(Vec), Path(Box), + FilePath(PathBuf), ExternalCommand(ExternalCommand), #[allow(unused)] @@ -105,6 +114,7 @@ impl RawExpression { match self { RawExpression::Literal(literal) => literal.type_name(), RawExpression::Synthetic(synthetic) => synthetic.type_name(), + RawExpression::FilePath(..) => "filepath", RawExpression::Variable(..) => "variable", RawExpression::List(..) => "list", RawExpression::Binary(..) => "binary", @@ -141,6 +151,10 @@ impl Expression { ) } + crate fn file_path(path: impl Into, outer: impl Into) -> Expression { + Tagged::from_simple_spanned_item(RawExpression::FilePath(path.into()), outer.into()) + } + crate fn bare(span: impl Into) -> Expression { Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into()) } @@ -170,7 +184,8 @@ impl Expression { impl ToDebug for Expression { fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { match self.item() { - RawExpression::Literal(l) => write!(f, "{:?}", l), + RawExpression::Literal(l) => l.tagged(self.span()).fmt_debug(f, source), + RawExpression::FilePath(p) => write!(f, "{}", p.display()), RawExpression::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s), RawExpression::Variable(Variable::It(_)) => write!(f, "$it"), RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)), diff --git a/src/parser/hir/baseline_parse.rs b/src/parser/hir/baseline_parse.rs index 681347064..a894f2bd7 100644 --- a/src/parser/hir/baseline_parse.rs +++ b/src/parser/hir/baseline_parse.rs @@ -1,5 +1,7 @@ +use crate::context::Context; use crate::parser::{hir, RawToken, Token}; use crate::Text; +use std::path::PathBuf; pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression { match *token.item() { @@ -15,6 +17,20 @@ pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Express } } +pub fn baseline_parse_token_as_number(token: &Token, source: &Text) -> hir::Expression { + match *token.item() { + RawToken::Variable(span) if span.slice(source) == "it" => { + hir::Expression::it_variable(span, token.span()) + } + RawToken::External(span) => hir::Expression::external_command(span, token.span()), + RawToken::Variable(span) => hir::Expression::variable(span, token.span()), + RawToken::Integer(int) => hir::Expression::int(int, token.span()), + RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()), + RawToken::Bare => hir::Expression::bare(token.span()), + RawToken::String(span) => hir::Expression::string(span, token.span()), + } +} + pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expression { match *token.item() { RawToken::Variable(span) if span.slice(source) == "it" => { @@ -28,3 +44,32 @@ pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expr RawToken::String(span) => hir::Expression::string(span, token.span()), } } + +pub fn baseline_parse_token_as_path( + token: &Token, + context: &Context, + source: &Text, +) -> hir::Expression { + match *token.item() { + RawToken::Variable(span) if span.slice(source) == "it" => { + hir::Expression::it_variable(span, token.span()) + } + RawToken::External(span) => hir::Expression::external_command(span, token.span()), + RawToken::Variable(span) => hir::Expression::variable(span, token.span()), + RawToken::Integer(_) => hir::Expression::bare(token.span()), + RawToken::Size(_, _) => hir::Expression::bare(token.span()), + RawToken::Bare => hir::Expression::file_path( + expand_path(token.span().slice(source), context), + token.span(), + ), + RawToken::String(span) => { + hir::Expression::file_path(expand_path(span.slice(source), context), token.span()) + } + } +} + +pub fn expand_path(string: &str, context: &Context) -> PathBuf { + let expanded = shellexpand::tilde_with_context(string, || context.shell_manager.homedir()); + + PathBuf::from(expanded.as_ref()) +} diff --git a/src/parser/hir/baseline_parse_tokens.rs b/src/parser/hir/baseline_parse_tokens.rs index d9891b2fc..248169bbb 100644 --- a/src/parser/hir/baseline_parse_tokens.rs +++ b/src/parser/hir/baseline_parse_tokens.rs @@ -1,8 +1,11 @@ +use crate::context::Context; use crate::errors::ShellError; -use crate::parser::registry::CommandRegistry; use crate::parser::{ hir, - hir::{baseline_parse_single_token, baseline_parse_token_as_string}, + hir::{ + baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, + baseline_parse_token_as_string, + }, DelimitedNode, Delimiter, PathNode, RawToken, TokenNode, }; use crate::{Span, Tag, Tagged, TaggedItem, Text}; @@ -12,8 +15,9 @@ use serde::{Deserialize, Serialize}; pub fn baseline_parse_tokens( token_nodes: &mut TokensIterator<'_>, - registry: &CommandRegistry, + context: &Context, source: &Text, + syntax_type: SyntaxType, ) -> Result, ShellError> { let mut exprs: Vec = vec![]; @@ -22,7 +26,7 @@ pub fn baseline_parse_tokens( break; } - let expr = baseline_parse_next_expr(token_nodes, registry, source, SyntaxType::Any)?; + let expr = baseline_parse_next_expr(token_nodes, context, source, syntax_type)?; exprs.push(expr); } @@ -35,7 +39,10 @@ pub enum SyntaxType { Any, List, Literal, + String, + Member, Variable, + Number, Path, Binary, Block, @@ -44,7 +51,7 @@ pub enum SyntaxType { pub fn baseline_parse_next_expr( tokens: &mut TokensIterator, - registry: &CommandRegistry, + context: &Context, source: &Text, syntax_type: SyntaxType, ) -> Result { @@ -56,7 +63,7 @@ pub fn baseline_parse_next_expr( match (syntax_type, next) { (SyntaxType::Path, TokenNode::Token(token)) => { - return Ok(baseline_parse_token_as_string(token, source)) + return Ok(baseline_parse_token_as_path(token, context, source)) } (SyntaxType::Path, token) => { @@ -66,10 +73,50 @@ pub fn baseline_parse_next_expr( )) } - _ => {} + (SyntaxType::String, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_string(token, source)); + } + + (SyntaxType::String, token) => { + return Err(ShellError::type_error( + "String", + token.type_name().simple_spanned(token.span()), + )) + } + + (SyntaxType::Number, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_number(token, source)); + } + + (SyntaxType::Number, token) => { + return Err(ShellError::type_error( + "Numeric", + token.type_name().simple_spanned(token.span()), + )) + } + + // TODO: More legit member processing + (SyntaxType::Member, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_string(token, source)); + } + + (SyntaxType::Member, token) => { + return Err(ShellError::type_error( + "member", + token.type_name().simple_spanned(token.span()), + )) + } + + (SyntaxType::Any, _) => {} + (SyntaxType::List, _) => {} + (SyntaxType::Literal, _) => {} + (SyntaxType::Variable, _) => {} + (SyntaxType::Binary, _) => {} + (SyntaxType::Block, _) => {} + (SyntaxType::Boolean, _) => {} }; - let first = baseline_parse_semantic_token(next, registry, source)?; + let first = baseline_parse_semantic_token(next, context, source)?; let possible_op = tokens.peek(); @@ -88,7 +135,7 @@ pub fn baseline_parse_next_expr( op.span(), )) } - Some(token) => baseline_parse_semantic_token(token, registry, source)?, + Some(token) => baseline_parse_semantic_token(token, context, source)?, }; // We definitely have a binary expression here -- let's see if we should coerce it into a block @@ -176,13 +223,13 @@ pub fn baseline_parse_next_expr( pub fn baseline_parse_semantic_token( token: &TokenNode, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { match token { TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)), TokenNode::Call(_call) => unimplemented!(), - TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, registry, source), + TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, context, source), TokenNode::Pipeline(_pipeline) => unimplemented!(), TokenNode::Operator(_op) => unreachable!(), TokenNode::Flag(_flag) => Err(ShellError::unimplemented( @@ -191,20 +238,24 @@ pub fn baseline_parse_semantic_token( TokenNode::Member(_span) => unreachable!(), TokenNode::Whitespace(_span) => unreachable!(), TokenNode::Error(error) => Err(*error.item.clone()), - TokenNode::Path(path) => baseline_parse_path(path, registry, source), + TokenNode::Path(path) => baseline_parse_path(path, context, source), } } pub fn baseline_parse_delimited( token: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { match token.delimiter() { Delimiter::Brace => { let children = token.children(); - let exprs = - baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; + let exprs = baseline_parse_tokens( + &mut TokensIterator::new(children), + context, + source, + SyntaxType::Any, + )?; let expr = hir::RawExpression::Block(exprs); Ok(Tagged::from_simple_spanned_item(expr, token.span())) @@ -212,8 +263,12 @@ pub fn baseline_parse_delimited( Delimiter::Paren => unimplemented!(), Delimiter::Square => { let children = token.children(); - let exprs = - baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; + let exprs = baseline_parse_tokens( + &mut TokensIterator::new(children), + context, + source, + SyntaxType::Any, + )?; let expr = hir::RawExpression::List(exprs); Ok(expr.tagged(Tag::unknown_origin(token.span()))) @@ -223,10 +278,10 @@ pub fn baseline_parse_delimited( pub fn baseline_parse_path( token: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { - let head = baseline_parse_semantic_token(token.head(), registry, source)?; + let head = baseline_parse_semantic_token(token.head(), context, source)?; let mut tail = vec![]; diff --git a/src/parser/hir/named.rs b/src/parser/hir/named.rs index 5ce06fe6f..f95896cbf 100644 --- a/src/parser/hir/named.rs +++ b/src/parser/hir/named.rs @@ -27,7 +27,7 @@ impl ToDebug for NamedArguments { for (name, value) in &self.named { match value { NamedValue::AbsentSwitch => continue, - NamedValue::PresentSwitch(span) => write!(f, " {}", span.slice(source))?, + NamedValue::PresentSwitch(span) => write!(f, " --{}", span.slice(source))?, NamedValue::AbsentValue => continue, NamedValue::Value(expr) => write!(f, " --{} {}", name, expr.debug(source))?, } diff --git a/src/parser/parse/parser.rs b/src/parser/parse/parser.rs index 2f060ca29..a5afac756 100644 --- a/src/parser/parse/parser.rs +++ b/src/parser/parse/parser.rs @@ -540,6 +540,8 @@ fn is_start_bare_char(c: char) -> bool { '@' => true, '*' => true, '?' => true, + '~' => true, + '+' => true, _ => false, } } @@ -557,6 +559,8 @@ fn is_bare_char(c: char) -> bool { '*' => true, '?' => true, '=' => true, + '~' => true, + '+' => true, _ => false, } } diff --git a/src/parser/parse_command.rs b/src/parser/parse_command.rs index 9ae920d2c..4d8204f16 100644 --- a/src/parser/parse_command.rs +++ b/src/parser/parse_command.rs @@ -1,5 +1,6 @@ +use crate::context::Context; use crate::errors::{ArgumentError, ShellError}; -use crate::parser::registry::{CommandRegistry, NamedType, PositionalType, Signature}; +use crate::parser::registry::{NamedType, PositionalType, Signature}; use crate::parser::{baseline_parse_tokens, CallNode}; use crate::parser::{ hir::{self, NamedArguments}, @@ -10,7 +11,7 @@ use log::trace; pub fn parse_command( config: &Signature, - registry: &CommandRegistry, + context: &Context, call: &Tagged, source: &Text, ) -> Result { @@ -31,7 +32,7 @@ pub fn parse_command( .collect() }); - match parse_command_tail(&config, registry, children, source, call.span())? { + match parse_command_tail(&config, context, children, source, call.span())? { None => Ok(hir::Call::new(Box::new(head), None, None)), Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)), } @@ -63,7 +64,7 @@ fn parse_command_head(head: &TokenNode) -> Result { fn parse_command_tail( config: &Signature, - registry: &CommandRegistry, + context: &Context, tail: Option>, source: &Text, command_span: Span, @@ -101,7 +102,7 @@ fn parse_command_tail( } let expr = - hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?; + hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?; tail.restart(); named.insert_mandatory(name, expr); @@ -121,7 +122,7 @@ fn parse_command_tail( )); } - let expr = hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?; + let expr = hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?; tail.restart(); named.insert_optional(name, Some(expr)); @@ -160,16 +161,17 @@ fn parse_command_tail( } } - let result = hir::baseline_parse_next_expr(tail, registry, source, arg.syntax_type())?; + let result = hir::baseline_parse_next_expr(tail, context, source, arg.syntax_type())?; positional.push(result); } trace_remaining("after positional", tail.clone(), source); - // TODO: Only do this if rest params are specified - let remainder = baseline_parse_tokens(tail, registry, source)?; - positional.extend(remainder); + if let Some(syntax_type) = config.rest_positional { + let remainder = baseline_parse_tokens(tail, context, source, syntax_type)?; + positional.extend(remainder); + } trace_remaining("after rest", tail.clone(), source); @@ -180,6 +182,8 @@ fn parse_command_tail( positional => Some(positional), }; + // TODO: Error if extra unconsumed positional arguments + let named = match named { named if named.named.is_empty() => None, named => Some(named), diff --git a/src/parser/registry.rs b/src/parser/registry.rs index d7f41d436..3d24e9921 100644 --- a/src/parser/registry.rs +++ b/src/parser/registry.rs @@ -74,8 +74,8 @@ pub struct Signature { pub name: String, #[new(default)] pub positional: Vec, - #[new(value = "false")] - pub rest_positional: bool, + #[new(value = "None")] + pub rest_positional: Option, #[new(default)] pub named: IndexMap, #[new(value = "false")] @@ -130,8 +130,8 @@ impl Signature { self } - pub fn rest(mut self) -> Signature { - self.rest_positional = true; + pub fn rest(mut self, ty: SyntaxType) -> Signature { + self.rest_positional = Some(ty); self } } @@ -279,10 +279,10 @@ impl Signature { crate fn parse_args( &self, call: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { - let args = parse_command(self, registry, call, source)?; + let args = parse_command(self, context, call, source)?; trace!("parsed args: {:?}", args); diff --git a/src/plugins/add.rs b/src/plugins/add.rs index 2634c1497..93c519212 100644 --- a/src/plugins/add.rs +++ b/src/plugins/add.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, - ShellError, Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; struct Add { @@ -43,17 +42,12 @@ impl Add { impl Plugin for Add { fn config(&mut self) -> Result { - Ok(Signature { - name: "add".to_string(), - positional: vec![ - PositionalType::mandatory_any("Field"), - PositionalType::mandatory_any("Value"), - ], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("add") + .required("Field", SyntaxType::String) + .required("Value", SyntaxType::String) + .rest(SyntaxType::String).filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if let Some(args) = call_info.args.positional { match &args[0] { diff --git a/src/plugins/binaryview.rs b/src/plugins/binaryview.rs index 5668fa869..a032a9dbf 100644 --- a/src/plugins/binaryview.rs +++ b/src/plugins/binaryview.rs @@ -1,9 +1,6 @@ #![feature(option_flattening)] use crossterm::{cursor, terminal, Attribute, RawScreen}; -use indexmap::IndexMap; -use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, ShellError, Signature, SpanSource, Tagged, Value, -}; +use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, SpanSource, Tagged, Value}; use pretty_hex::*; struct BinaryView; @@ -16,15 +13,7 @@ impl BinaryView { impl Plugin for BinaryView { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("lores".to_string(), NamedType::Switch); - Ok(Signature { - name: "binaryview".to_string(), - positional: vec![], - is_filter: false, - named, - rest_positional: false, - }) + Ok(Signature::build("binaryview").switch("lores")) } fn sink(&mut self, call_info: CallInfo, input: Vec>) { diff --git a/src/plugins/edit.rs b/src/plugins/edit.rs index 6e94c1c83..902c038bc 100644 --- a/src/plugins/edit.rs +++ b/src/plugins/edit.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, - ShellError, Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; struct Edit { @@ -42,17 +41,12 @@ impl Edit { impl Plugin for Edit { fn config(&mut self) -> Result { - Ok(Signature { - name: "edit".to_string(), - positional: vec![ - PositionalType::mandatory_any("Field"), - PositionalType::mandatory_any("Value"), - ], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("edit") + .required("Field", SyntaxType::String) + .required("Value", SyntaxType::String) + .filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if let Some(args) = call_info.args.positional { match &args[0] { diff --git a/src/plugins/inc.rs b/src/plugins/inc.rs index 173ced2fe..ece83b7f2 100644 --- a/src/plugins/inc.rs +++ b/src/plugins/inc.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, - Signature, Tagged, TaggedItem, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, TaggedItem, Value, }; enum Action { @@ -116,19 +115,14 @@ impl Inc { impl Plugin for Inc { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("major".to_string(), NamedType::Switch); - named.insert("minor".to_string(), NamedType::Switch); - named.insert("patch".to_string(), NamedType::Switch); - - Ok(Signature { - name: "inc".to_string(), - positional: vec![], - is_filter: true, - named, - rest_positional: true, - }) + Ok(Signature::build("inc") + .switch("major") + .switch("minor") + .switch("patch") + .rest(SyntaxType::String) + .filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if call_info.args.has("major") { self.for_semver(SemVerAction::Major); diff --git a/src/plugins/skip.rs b/src/plugins/skip.rs index 8633c5e5d..6f78351fe 100644 --- a/src/plugins/skip.rs +++ b/src/plugins/skip.rs @@ -1,7 +1,7 @@ use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, - Tagged, Value, + SyntaxType, Tagged, Value, }; struct Skip { @@ -20,7 +20,7 @@ impl Plugin for Skip { positional: vec![], is_filter: true, named: IndexMap::new(), - rest_positional: true, + rest_positional: Some(SyntaxType::Number), }) } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 679ede80a..0656d5f78 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, - Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; use regex::Regex; @@ -40,9 +39,11 @@ impl Str { let applied = match self.action.as_ref() { Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()), Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()), - Some(Action::ToInteger) => match input.trim().parse::() { - Ok(v) => Value::int(v), - Err(_) => Value::string(input), + Some(Action::ToInteger) => match input.trim() { + other => match other.parse::() { + Ok(v) => Value::int(v), + Err(_) => Value::string(input), + }, }, Some(Action::Replace(ref mode)) => match mode { ReplaceAction::Direct => Value::string(self.first_param()), @@ -138,9 +139,7 @@ impl Str { Some(ref f) => { let replacement = match value.item.get_data_by_path(value.tag(), f) { Some(result) => self.strutils(result.map(|x| x.clone()))?, - None => { - return Err(ShellError::string("str could not find field to replace")) - } + None => return Ok(Tagged::from_item(Value::nothing(), value.tag)), }; match value .item @@ -168,20 +167,14 @@ impl Str { impl Plugin for Str { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("downcase".to_string(), NamedType::Switch); - named.insert("upcase".to_string(), NamedType::Switch); - named.insert("to-int".to_string(), NamedType::Switch); - named.insert("replace".to_string(), NamedType::Switch); - named.insert("find-replace".to_string(), NamedType::Switch); - - Ok(Signature { - name: "str".to_string(), - positional: vec![], - is_filter: true, - named, - rest_positional: true, - }) + Ok(Signature::build("str") + .switch("downcase") + .switch("upcase") + .switch("to-int") + .switch("replace") + .switch("find-replace") + .rest(SyntaxType::Member) + .filter()) } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { diff --git a/src/plugins/sum.rs b/src/plugins/sum.rs index 34cc59ec7..1c983b1ad 100644 --- a/src/plugins/sum.rs +++ b/src/plugins/sum.rs @@ -1,4 +1,3 @@ -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, Tag, Tagged, Value, @@ -14,6 +13,7 @@ impl Sum { fn sum(&mut self, value: Tagged) -> Result<(), ShellError> { match value.item { + Value::Primitive(Primitive::Nothing) => Ok(()), Value::Primitive(Primitive::Int(i)) => { match self.total { Some(Tagged { @@ -64,14 +64,9 @@ impl Sum { impl Plugin for Sum { fn config(&mut self) -> Result { - Ok(Signature { - name: "sum".to_string(), - positional: vec![], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("sum").filter()) } + fn begin_filter(&mut self, _: CallInfo) -> Result, ShellError> { Ok(vec![]) } diff --git a/src/plugins/sys.rs b/src/plugins/sys.rs index c442f39f9..00e4ca47d 100644 --- a/src/plugins/sys.rs +++ b/src/plugins/sys.rs @@ -1,10 +1,9 @@ use futures::executor::block_on; use futures::stream::StreamExt; use heim::{disk, memory, net}; -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, - Tag, Tagged, TaggedDictBuilder, Value, + SyntaxType, Tag, Tagged, TaggedDictBuilder, Value, }; use std::ffi::OsStr; @@ -251,14 +250,9 @@ async fn sysinfo(tag: Tag) -> Vec> { impl Plugin for Sys { fn config(&mut self) -> Result { - Ok(Signature { - name: "sys".to_string(), - positional: vec![], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("sys").rest(SyntaxType::Any)) } + fn begin_filter(&mut self, callinfo: CallInfo) -> Result, ShellError> { Ok(block_on(sysinfo(Tag::unknown_origin(callinfo.name_span))) .into_iter() diff --git a/src/plugins/textview.rs b/src/plugins/textview.rs index 92d95c83e..f82127cd8 100644 --- a/src/plugins/textview.rs +++ b/src/plugins/textview.rs @@ -2,7 +2,6 @@ use crossterm::{cursor, terminal, RawScreen}; use crossterm::{InputEvent, KeyEvent}; -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ShellError, Signature, SourceMap, SpanSource, Tagged, Value, @@ -29,13 +28,7 @@ impl TextView { impl Plugin for TextView { fn config(&mut self) -> Result { - Ok(Signature { - name: "textview".to_string(), - positional: vec![], - is_filter: false, - named: IndexMap::new(), - rest_positional: false, - }) + Ok(Signature::build("textview")) } fn sink(&mut self, call_info: CallInfo, input: Vec>) { diff --git a/src/plugins/tree.rs b/src/plugins/tree.rs index dcffa48eb..9784e854c 100644 --- a/src/plugins/tree.rs +++ b/src/plugins/tree.rs @@ -1,5 +1,4 @@ use derive_new::new; -use indexmap::IndexMap; use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, Tagged, Value}; use ptree::item::StringItem; use ptree::output::print_tree_with; @@ -81,13 +80,7 @@ struct TreeViewer; impl Plugin for TreeViewer { fn config(&mut self) -> Result { - Ok(Signature { - name: "tree".to_string(), - positional: vec![], - is_filter: false, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("tree")) } fn sink(&mut self, _call_info: CallInfo, input: Vec>) { diff --git a/src/prelude.rs b/src/prelude.rs index 9e1f7322f..481d89757 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -32,6 +32,25 @@ macro_rules! trace_stream { }}; } +#[macro_export] +macro_rules! trace_out_stream { + (target: $target:tt, source: $source:expr, $desc:tt = $expr:expr) => {{ + if log::log_enabled!(target: $target, log::Level::Trace) { + use futures::stream::StreamExt; + + let source = $source.clone(); + + let objects = $expr.values.inspect(move |o| { + trace!(target: $target, "{} = {}", $desc, o.debug(&source)); + }); + + $crate::stream::OutputStream::new(objects) + } else { + $expr + } + }}; +} + crate use crate::cli::MaybeOwned; crate use crate::commands::command::{ CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext, diff --git a/src/shell/filesystem_shell.rs b/src/shell/filesystem_shell.rs index adeb551fa..1f2f94551 100644 --- a/src/shell/filesystem_shell.rs +++ b/src/shell/filesystem_shell.rs @@ -72,12 +72,16 @@ impl Shell for FilesystemShell { "filesystem".to_string() } + fn homedir(&self) -> Option { + dirs::home_dir() + } + fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let cwd = self.path(); let mut full_path = PathBuf::from(self.path()); match &args.nth(0) { - Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)), + Some(value) => full_path.push(Path::new(&value.as_path()?)), _ => {} } @@ -176,7 +180,7 @@ impl Shell for FilesystemShell { } }, Some(v) => { - let target = v.as_string()?; + let target = v.as_path()?; let path = PathBuf::from(self.path()); match dunce::canonicalize(path.join(target).as_path()) { Ok(p) => p, diff --git a/src/shell/shell.rs b/src/shell/shell.rs index 1db76d699..4157cacb6 100644 --- a/src/shell/shell.rs +++ b/src/shell/shell.rs @@ -7,9 +7,11 @@ use crate::context::SourceMap; use crate::errors::ShellError; use crate::prelude::*; use crate::stream::OutputStream; +use std::path::PathBuf; pub trait Shell: std::fmt::Debug { fn name(&self, source_map: &SourceMap) -> String; + fn homedir(&self) -> Option; fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result; fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result; fn cp(&self, args: CopyArgs, name: Span, path: &str) -> Result; diff --git a/src/shell/shell_manager.rs b/src/shell/shell_manager.rs index 49e8484ee..1d58738fe 100644 --- a/src/shell/shell_manager.rs +++ b/src/shell/shell_manager.rs @@ -9,6 +9,7 @@ use crate::shell::filesystem_shell::FilesystemShell; use crate::shell::shell::Shell; use crate::stream::OutputStream; use std::error::Error; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; #[derive(Clone, Debug)] @@ -102,16 +103,24 @@ impl ShellManager { self.set_path(self.path()); } + pub fn homedir(&self) -> Option { + let env = self.shells.lock().unwrap(); + + env[self.current_shell].homedir() + } + pub fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let env = self.shells.lock().unwrap(); env[self.current_shell].ls(args) } + pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let env = self.shells.lock().unwrap(); env[self.current_shell].cd(args) } + pub fn cp( &self, args: CopyArgs, diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs index c65a8833c..3c4e78c8b 100644 --- a/src/shell/value_shell.rs +++ b/src/shell/value_shell.rs @@ -69,6 +69,10 @@ impl Shell for ValueShell { ) } + fn homedir(&self) -> Option { + dirs::home_dir() + } + fn ls(&self, _args: EvaluatedWholeStreamCommandArgs) -> Result { Ok(self .members() diff --git a/src/utils.rs b/src/utils.rs index 99a59e850..6b4fc33f3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,7 +105,10 @@ impl FileStructure { self.root = path.to_path_buf(); } - pub fn paths_applying_with(&mut self, to: F) -> Result, Box> + pub fn paths_applying_with( + &mut self, + to: F, + ) -> Result, Box> where F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box>, { @@ -155,6 +158,7 @@ impl FileStructure { #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use super::{FileStructure, Res}; use std::path::PathBuf; @@ -175,7 +179,8 @@ mod tests { fn prepares_and_decorates_source_files_for_copying() { let mut res = FileStructure::new(); - res.walk_decorate(fixtures().as_path()).expect("Can not decorate files traversal."); + res.walk_decorate(fixtures().as_path()) + .expect("Can not decorate files traversal."); assert_eq!( res.resources, diff --git a/tests/command_ls_tests.rs b/tests/command_ls_tests.rs index 0cda5c93d..622370980 100644 --- a/tests/command_ls_tests.rs +++ b/tests/command_ls_tests.rs @@ -18,7 +18,7 @@ fn ls_lists_regular_files() { nu!( output, cwd(&full_path), - "ls | get name | lines| split-column \".\" | get Column2 | str Column2 --to-int | sum | echo $it" + r#"ls | get name | lines | split-column "." | get Column2 | str Column2 --to-int | sum | echo $it"# ); assert_eq!(output, "30"); From 570a0ac275a43b41c253cf1e015ce2ba082648de Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 27 Aug 2019 17:15:05 +1200 Subject: [PATCH 11/12] Fix path-related parts of value shell --- src/shell/value_shell.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs index 3c4e78c8b..5e1e8c223 100644 --- a/src/shell/value_shell.rs +++ b/src/shell/value_shell.rs @@ -84,19 +84,20 @@ impl Shell for ValueShell { let path = match args.nth(0) { None => "/".to_string(), Some(v) => { - let target = v.as_string()?; + let target = v.as_path()?; let mut cwd = PathBuf::from(&self.path); - match target { - x if x == ".." => { - cwd.pop(); + + if target == PathBuf::from("..") { + cwd.pop(); + } else { + match target.to_str() { + Some(target) => match target.chars().nth(0) { + Some(x) if x == '/' => cwd = PathBuf::from(target), + _ => cwd.push(target), + }, + None => cwd.push(target), } - _ => match target.chars().nth(0) { - Some(x) if x == '/' => cwd = PathBuf::from(target), - _ => { - cwd.push(target); - } - }, } cwd.to_string_lossy().to_string() } From 1cdfe358c274f9340bab4aedb1f4a0672914aff6 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 27 Aug 2019 18:06:30 +1200 Subject: [PATCH 12/12] Fix the utf-8 width calculation --- src/format/table.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/format/table.rs b/src/format/table.rs index 78a270897..37ed51668 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -78,13 +78,16 @@ impl TableView { for head in 0..headers.len() { let mut current_col_max = 0; for row in 0..values.len() { - let value_length = entries[row][head].0.len(); - if head > entries[row].len() && value_length > current_col_max { + let value_length = entries[row][head].0.chars().count(); + if value_length > current_col_max { current_col_max = value_length; } } - max_per_column.push(std::cmp::max(current_col_max, headers[head].len())); + max_per_column.push(std::cmp::max( + current_col_max, + headers[head].chars().count(), + )); } // Different platforms want different amounts of buffer, not sure why