From e325fd114d3c16d382d47dd4da799e626d730a64 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 04:32:08 -0700 Subject: [PATCH 01/25] port the mv command --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/mv.rs | 151 +++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 crates/nu-command/src/filesystem/mv.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 46639151de..8418b2a384 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -35,6 +35,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Lines)); working_set.add_decl(Box::new(Ls)); working_set.add_decl(Box::new(Module)); + working_set.add_decl(Box::new(Mv)); working_set.add_decl(Box::new(Ps)); working_set.add_decl(Box::new(Select)); working_set.add_decl(Box::new(Sys)); diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 13148b5358..90b697fcd8 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,5 +1,7 @@ mod cd; mod ls; +mod mv; pub use cd::Cd; pub use ls::Ls; +pub use mv::Mv; diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs new file mode 100644 index 0000000000..3b909cb0fe --- /dev/null +++ b/crates/nu-command/src/filesystem/mv.rs @@ -0,0 +1,151 @@ +use std::env::current_dir; +use std::path::{Path, PathBuf}; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +pub struct Mv; + +impl Command for Mv { + fn name(&self) -> &str { + "mv" + } + + fn usage(&self) -> &str { + "Move files or directories." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("mv") + .optional( + "source", + SyntaxShape::GlobPattern, + "the location to move files/directories from", + ) + .optional( + "destination", + SyntaxShape::FilePath, + "the location to move files/directories to", + ) + // .required( + // "source", + // SyntaxShape::GlobPattern, + // "the location to move files/directories from", + // ) + // .required( + // "destination", + // SyntaxShape::FilePath, + // "the location to move files/directories to", + // ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + // TODO: handle invalid directory or insufficient permissions + let source: String = call.req(context, 0)?; + let destination: String = call.req(context, 1)?; + + let path: PathBuf = current_dir().unwrap(); + let source = path.join(source.as_str()); + let destination = path.join(destination.as_str()); + + let mut sources = + glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); + + if sources.is_empty() { + return Err(ShellError::InternalError(format!( + "source \"{:?}\" does not exist", + source + ))); + } + + if (destination.exists() && !destination.is_dir() && sources.len() > 1) + || (!destination.exists() && sources.len() > 1) + { + return Err(ShellError::InternalError( + "can only move multiple sources if destination is a directory".to_string(), + )); + } + + let some_if_source_is_destination = sources + .iter() + .find(|f| matches!(f, Ok(f) if destination.starts_with(f))); + if destination.exists() && destination.is_dir() && sources.len() == 1 { + if let Some(Ok(filename)) = some_if_source_is_destination { + return Err(ShellError::InternalError(format!( + "Not possible to move {:?} to itself", + filename.file_name().expect("Invalid file name") + ))); + } + } + + if let Some(Ok(_filename)) = some_if_source_is_destination { + sources = sources + .into_iter() + .filter(|f| matches!(f, Ok(f) if !destination.starts_with(f))) + .collect(); + } + + for entry in sources.into_iter().flatten() { + move_file(&entry, &destination)? + } + + Ok(Value::Nothing { span: call.head }) + } +} + +fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { + if to.exists() && from.is_dir() && to.is_file() { + return Err(ShellError::InternalError(format!( + "Cannot rename {:?} to a file", + from.file_name().expect("Invalid directory name") + ))); + } + + let destination_dir_exists = if to.is_dir() { + true + } else { + to.parent().map(Path::exists).unwrap_or(true) + }; + + if !destination_dir_exists { + return Err(ShellError::InternalError(format!( + "{:?} does not exist", + to.file_name().expect("Invalid directory name") + ))); + } + + let mut to = to.clone(); + if to.is_dir() { + let from_file_name = match from.file_name() { + Some(name) => name, + None => { + return Err(ShellError::InternalError(format!( + "{:?} is not a valid entry", + from.file_name().expect("Invalid directory name") + ))) + } + }; + + to.push(from_file_name); + } + + move_item(&from, &to) +} + +fn move_item(from: &Path, to: &Path) -> Result<(), ShellError> { + // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy + // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. + std::fs::rename(&from, &to).or_else(|_| { + Err(ShellError::InternalError(format!( + "Could not move {:?} to {:?}", + from, to, + ))) + }) +} From b2148e32b85ad5afb13a36613b4a0e554c46fe7b Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 05:13:47 -0700 Subject: [PATCH 02/25] make mv parameters required --- crates/nu-command/src/filesystem/mv.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 3b909cb0fe..b3d9ec2f07 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -19,26 +19,16 @@ impl Command for Mv { fn signature(&self) -> nu_protocol::Signature { Signature::build("mv") - .optional( + .required( "source", SyntaxShape::GlobPattern, "the location to move files/directories from", ) - .optional( + .required( "destination", SyntaxShape::FilePath, "the location to move files/directories to", ) - // .required( - // "source", - // SyntaxShape::GlobPattern, - // "the location to move files/directories from", - // ) - // .required( - // "destination", - // SyntaxShape::FilePath, - // "the location to move files/directories to", - // ) } fn run( From 4dacfaa44a863e242d2bc74eba3ae9d9701476b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 4 Oct 2021 20:08:24 +0300 Subject: [PATCH 03/25] Add import pattern support to 'hide' --- crates/nu-parser/src/parse_keywords.rs | 88 ++++++++++++++++++++++++-- src/tests.rs | 40 ++++++++++++ 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 033a21e738..820427f3e2 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -600,12 +600,92 @@ pub fn parse_hide( let (name_expr, err) = parse_string(working_set, spans[1]); error = error.or(err); - let name_bytes: Vec = working_set.get_span_contents(spans[1]).into(); + let (import_pattern, err) = parse_import_pattern(working_set, spans[1]); + error = error.or(err); - // TODO: Do the import pattern stuff for bulk-hiding + let exported_names: Vec> = + if let Some(block_id) = working_set.find_module(&import_pattern.head) { + working_set + .get_block(block_id) + .exports + .iter() + .map(|(name, _)| name.clone()) + .collect() + } else { + if import_pattern.members.is_empty() { + // The pattern head can be e.g. a function name, not just a module + vec![import_pattern.head.clone()] + } else { + return ( + garbage_statement(spans), + Some(ParseError::ModuleNotFound(spans[1])), + ); + } + }; - if working_set.hide_decl(&name_bytes).is_none() { - error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1]))); + // This kind of inverts the import pattern matching found in parse_use() + let names_to_hide = if import_pattern.members.is_empty() { + exported_names + } else { + match &import_pattern.members[0] { + ImportPatternMember::Glob { .. } => exported_names + .into_iter() + .map(|name| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(&name); + new_name + }) + .collect(), + ImportPatternMember::Name { name, span } => { + let new_exports: Vec> = exported_names + .into_iter() + .filter(|n| n == name) + .map(|n| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(&n); + new_name + }) + .collect(); + + if new_exports.is_empty() { + error = error.or(Some(ParseError::ExportNotFound(*span))) + } + + new_exports + } + ImportPatternMember::List { names } => { + let mut output = vec![]; + + for (name, span) in names { + let mut new_exports: Vec> = exported_names + .iter() + .filter_map(|n| if n == name { Some(n.clone()) } else { None }) + .map(|n| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(n); + new_name + }) + .collect(); + + if new_exports.is_empty() { + error = error.or(Some(ParseError::ExportNotFound(*span))) + } else { + output.append(&mut new_exports) + } + } + + output + } + } + }; + + for name in names_to_hide { + if working_set.hide_decl(&name).is_none() { + error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1]))); + } } // Create the Hide command call diff --git a/src/tests.rs b/src/tests.rs index 7416aecb6f..db8fde4bd8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -441,6 +441,46 @@ fn hide_twice_not_allowed() -> TestResult { ) } +#[test] +fn hides_import_1() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.foo; foo"#, + "not found" + ) +} + +#[test] +fn hides_import_2() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.*; foo"#, + "not found" + ) +} + +#[test] +fn hides_import_3() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.[foo]; foo"#, + "not found" + ) +} + +#[test] +fn hides_import_4() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; foo"#, + "not found" + ) +} + +#[test] +fn hides_import_5() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam.*; hide foo; foo"#, + "not found" + ) +} + #[test] fn def_twice_should_fail() -> TestResult { fail_test( From 0fe525de872d4622a0b7d9c8ed66f9b986e95c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 4 Oct 2021 20:16:43 +0300 Subject: [PATCH 04/25] Add test with TODO note --- src/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index db8fde4bd8..173c2f7ba1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -489,6 +489,15 @@ fn def_twice_should_fail() -> TestResult { ) } +// TODO: This test fails if executed each command on a separate line in REPL +#[test] +fn use_import_after_hide() -> TestResult { + run_test( + r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; use spam.foo; foo"#, + "foo" + ) +} + #[test] fn from_json_1() -> TestResult { run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred") From 9737d4a614a49754eeda0f7ef3f375863c9cdcb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 4 Oct 2021 20:33:27 +0300 Subject: [PATCH 05/25] Change comments --- crates/nu-protocol/src/engine/engine_state.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index bbe6bdb1c1..c5cd679860 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -375,7 +375,7 @@ impl<'a> StateWorkingSet<'a> { if let Some(decl_id) = scope.decls.get(name) { if !hiding.contains(decl_id) { - // Do not hide already hidden decl + // Hide decl only if it's not already hidden last_scope_frame.hiding.insert(*decl_id); return Some(*decl_id); } @@ -409,8 +409,6 @@ impl<'a> StateWorkingSet<'a> { } pub fn activate_overlay(&mut self, overlay: Vec<(Vec, DeclId)>) { - // TODO: This will overwrite all existing definitions in a scope. When we add deactivate, - // we need to re-think how make it recoverable. let scope_frame = self .delta .scope From 1e1e12b027718ea9cd8b0528f91c0bd0860123d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 4 Oct 2021 22:17:18 +0300 Subject: [PATCH 06/25] Fmt --- src/tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 173c2f7ba1..0efd0b513a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -445,7 +445,7 @@ fn hide_twice_not_allowed() -> TestResult { fn hides_import_1() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.foo; foo"#, - "not found" + "not found", ) } @@ -453,7 +453,7 @@ fn hides_import_1() -> TestResult { fn hides_import_2() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.*; foo"#, - "not found" + "not found", ) } @@ -461,7 +461,7 @@ fn hides_import_2() -> TestResult { fn hides_import_3() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.[foo]; foo"#, - "not found" + "not found", ) } @@ -469,7 +469,7 @@ fn hides_import_3() -> TestResult { fn hides_import_4() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; foo"#, - "not found" + "not found", ) } @@ -477,7 +477,7 @@ fn hides_import_4() -> TestResult { fn hides_import_5() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam.*; hide foo; foo"#, - "not found" + "not found", ) } @@ -494,7 +494,7 @@ fn def_twice_should_fail() -> TestResult { fn use_import_after_hide() -> TestResult { run_test( r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; use spam.foo; foo"#, - "foo" + "foo", ) } From a88058006a770678f55c59a13c7bdbb0e3b27e59 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 5 Oct 2021 08:21:31 +1300 Subject: [PATCH 07/25] Add path completions --- Cargo.lock | 1 + crates/nu-cli/Cargo.toml | 2 + crates/nu-cli/src/completions.rs | 76 ++++++++++++++++++++++++++ crates/nu-cli/src/syntax_highlight.rs | 8 +++ crates/nu-command/src/filesystem/cd.rs | 2 +- crates/nu-command/src/filesystem/ls.rs | 9 ++- crates/nu-engine/src/eval.rs | 8 +++ crates/nu-parser/src/flatten.rs | 9 ++- crates/nu-parser/src/parser.rs | 70 ++++++++++++++++++++++-- crates/nu-protocol/src/ast/expr.rs | 2 + crates/nu-protocol/src/syntax_shape.rs | 4 +- crates/nu-protocol/src/ty.rs | 2 - 12 files changed, 182 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf1052648a..af84ca6676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,6 +487,7 @@ dependencies = [ "nu-ansi-term", "nu-engine", "nu-parser", + "nu-path", "nu-protocol", "reedline", "thiserror", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 117fe77d98..5b47189196 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,8 +5,10 @@ edition = "2018" [dependencies] nu-engine = { path = "../nu-engine" } +nu-path = { path = "../nu-path" } nu-parser = { path = "../nu-parser" } nu-protocol = { path = "../nu-protocol" } + miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" nu-ansi-term = "0.36.0" diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 2140ca0da9..f0820f0807 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -8,6 +8,8 @@ use nu_protocol::{ }; use reedline::Completer; +const SEP: char = std::path::MAIN_SEPARATOR; + pub struct NuCompleter { engine_state: Rc>, } @@ -28,6 +30,8 @@ impl Completer for NuCompleter { let flattened = flatten_block(&working_set, &output); + // println!("flattened: {:?}", flattened); + for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { match &flat.1 { @@ -80,6 +84,25 @@ impl Completer for NuCompleter { }) .collect(); } + nu_parser::FlatShape::Filepath | nu_parser::FlatShape::GlobPattern => { + let prefix = working_set.get_span_contents(flat.0); + let prefix = String::from_utf8_lossy(prefix).to_string(); + + let results = file_path_completion(flat.0, &prefix); + + return results + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }) + .collect(); + } _ => {} } } @@ -88,3 +111,56 @@ impl Completer for NuCompleter { vec![] } } + +fn file_path_completion( + span: nu_protocol::Span, + partial: &str, +) -> Vec<(nu_protocol::Span, String)> { + use std::path::{is_separator, Path}; + + let (base_dir_name, partial) = { + // If partial is only a word we want to search in the current dir + let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial)); + // On windows, this standardizes paths to use \ + let mut base = base.replace(is_separator, &SEP.to_string()); + + // rsplit_once removes the separator + base.push(SEP); + (base, rest) + }; + + let base_dir = nu_path::expand_path(&base_dir_name); + // This check is here as base_dir.read_dir() with base_dir == "" will open the current dir + // which we don't want in this case (if we did, base_dir would already be ".") + if base_dir == Path::new("") { + return Vec::new(); + } + + if let Ok(result) = base_dir.read_dir() { + result + .filter_map(|entry| { + entry.ok().and_then(|entry| { + let mut file_name = entry.file_name().to_string_lossy().into_owned(); + if matches(partial, &file_name) { + let mut path = format!("{}{}", base_dir_name, file_name); + if entry.path().is_dir() { + path.push(SEP); + file_name.push(SEP); + } + + Some((span, path)) + } else { + None + } + }) + }) + .collect() + } else { + Vec::new() + } +} + +fn matches(partial: &str, from: &str) -> bool { + from.to_ascii_lowercase() + .starts_with(&partial.to_ascii_lowercase()) +} diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 87ec535ed0..10e997054d 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -81,6 +81,14 @@ impl Highlighter for NuHighlighter { Style::new().fg(nu_ansi_term::Color::Yellow).bold(), next_token, )), + FlatShape::Filepath => output.push(( + Style::new().fg(nu_ansi_term::Color::Yellow).bold(), + next_token, + )), + FlatShape::GlobPattern => output.push(( + Style::new().fg(nu_ansi_term::Color::Yellow).bold(), + next_token, + )), FlatShape::Variable => output.push(( Style::new().fg(nu_ansi_term::Color::Blue).bold(), next_token, diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 94282c75e2..c7a5ce0b80 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -15,7 +15,7 @@ impl Command for Cd { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("cd").optional("path", SyntaxShape::FilePath, "the path to change to") + Signature::build("cd").optional("path", SyntaxShape::Filepath, "the path to change to") } fn run( diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 2e243e05f4..77b9f1c602 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -31,7 +31,14 @@ impl Command for Ls { ) -> Result { let pattern = if let Some(expr) = call.positional.get(0) { let result = eval_expression(context, expr)?; - result.as_string()? + let mut result = result.as_string()?; + + let path = std::path::Path::new(&result); + if path.is_dir() { + result.push('*'); + } + + result } else { "*".into() }; diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 04057e540e..5882ca2445 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -240,6 +240,14 @@ pub fn eval_expression( val: s.clone(), span: expr.span, }), + Expr::Filepath(s) => Ok(Value::String { + val: s.clone(), + span: expr.span, + }), + Expr::GlobPattern(s) => Ok(Value::String { + val: s.clone(), + span: expr.span, + }), Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }), Expr::Garbage => Ok(Value::Nothing { span: expr.span }), } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index fffb5626ec..c2d3ade9a4 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -15,6 +15,8 @@ pub enum FlatShape { Operator, Signature, String, + Filepath, + GlobPattern, Variable, Custom(String), } @@ -118,7 +120,12 @@ pub fn flatten_expression( Expr::Bool(_) => { vec![(expr.span, FlatShape::Bool)] } - + Expr::Filepath(_) => { + vec![(expr.span, FlatShape::Filepath)] + } + Expr::GlobPattern(_) => { + vec![(expr.span, FlatShape::GlobPattern)] + } Expr::List(list) => { let mut output = vec![]; for l in list { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index fa9703a73f..925cc76bb9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1320,6 +1320,68 @@ pub fn parse_full_cell_path( } } +pub fn parse_filepath( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) + || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) + { + &bytes[1..(bytes.len() - 1)] + } else { + bytes + }; + + if let Ok(token) = String::from_utf8(bytes.into()) { + ( + Expression { + expr: Expr::Filepath(token), + span, + ty: Type::String, + custom_completion: None, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Expected("string".into(), span)), + ) + } +} + +pub fn parse_glob_pattern( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) + || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) + { + &bytes[1..(bytes.len() - 1)] + } else { + bytes + }; + + if let Ok(token) = String::from_utf8(bytes.into()) { + ( + Expression { + expr: Expr::GlobPattern(token), + span, + ty: Type::String, + custom_completion: None, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Expected("string".into(), span)), + ) + } +} + pub fn parse_string( working_set: &mut StateWorkingSet, span: Span, @@ -1364,7 +1426,7 @@ pub fn parse_shape_name( b"number" => SyntaxShape::Number, b"range" => SyntaxShape::Range, b"int" => SyntaxShape::Int, - b"path" => SyntaxShape::FilePath, + b"path" => SyntaxShape::Filepath, b"glob" => SyntaxShape::GlobPattern, b"block" => SyntaxShape::Block(None), //FIXME b"cond" => SyntaxShape::RowCondition, @@ -2320,9 +2382,9 @@ pub fn parse_value( SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Int => parse_int(bytes, span), SyntaxShape::Range => parse_range(working_set, span), - SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { - parse_string(working_set, span) - } + SyntaxShape::Filepath => parse_filepath(working_set, span), + SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span), + SyntaxShape::String => parse_string(working_set, span), SyntaxShape::Block(_) => { if bytes.starts_with(b"{") { parse_block_expression(working_set, shape, span) diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 449f4687df..45955da946 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -23,6 +23,8 @@ pub enum Expr { List(Vec), Table(Vec, Vec>), Keyword(Vec, Span, Box), + Filepath(String), + GlobPattern(String), String(String), // FIXME: improve this in the future? CellPath(CellPath), FullCellPath(Box), diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 8b752461e7..d1202528ac 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -28,7 +28,7 @@ pub enum SyntaxShape { Int, /// A filepath is allowed - FilePath, + Filepath, /// A glob pattern is allowed, eg `foo*` GlobPattern, @@ -86,7 +86,7 @@ impl SyntaxShape { SyntaxShape::Custom(custom, _) => custom.to_type(), SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, - SyntaxShape::FilePath => Type::String, + SyntaxShape::Filepath => Type::String, SyntaxShape::Filesize => Type::Filesize, SyntaxShape::FullCellPath => Type::Unknown, SyntaxShape::GlobPattern => Type::String, diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index c11bafaca8..e3015b7c05 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -12,7 +12,6 @@ pub enum Type { Block, CellPath, Duration, - FilePath, Filesize, List(Box), Number, @@ -32,7 +31,6 @@ impl Display for Type { Type::Bool => write!(f, "bool"), Type::CellPath => write!(f, "cell path"), Type::Duration => write!(f, "duration"), - Type::FilePath => write!(f, "filepath"), Type::Filesize => write!(f, "filesize"), Type::Float => write!(f, "float"), Type::Int => write!(f, "int"), From 6f5f1fa43a5612bfae690448d5136bdae61533ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 4 Oct 2021 22:37:43 +0300 Subject: [PATCH 08/25] Clippy --- crates/nu-parser/src/parse_keywords.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 820427f3e2..d093cd88ce 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -611,16 +611,14 @@ pub fn parse_hide( .iter() .map(|(name, _)| name.clone()) .collect() + } else if import_pattern.members.is_empty() { + // The pattern head can be e.g. a function name, not just a module + vec![import_pattern.head.clone()] } else { - if import_pattern.members.is_empty() { - // The pattern head can be e.g. a function name, not just a module - vec![import_pattern.head.clone()] - } else { - return ( - garbage_statement(spans), - Some(ParseError::ModuleNotFound(spans[1])), - ); - } + return ( + garbage_statement(spans), + Some(ParseError::ModuleNotFound(spans[1])), + ); }; // This kind of inverts the import pattern matching found in parse_use() From 7c2bf68d459396f01cee8599ce09681724eef98a Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 5 Oct 2021 10:37:32 +1300 Subject: [PATCH 09/25] Use list completions and better expansion --- Cargo.lock | 26 +++++++++++++------------- crates/nu-command/src/filesystem/cd.rs | 2 +- src/main.rs | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af84ca6676..696490da9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "miette" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b98aebb9d23c72cb22c089834ea59be059c6f462e844fd9fd18dd0168ad149c" +checksum = "4786c5b04c6f73e96d88444e7f37e241d99479ea5dd88a4887363ab2e03b4e53" dependencies = [ "atty", "backtrace", @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7cd3347eb52480d4ba59d71ce2b48b8b128034e17068c7e2ff3766c2e87a3" +checksum = "0ee63a981bc9cde5f26665ffd756b624963bf0b5956e0df51e52ef8f6b5466d6" dependencies = [ "proc-macro2", "quote", @@ -804,7 +804,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/jntrnr/reedline?branch=main#93c2146fcf4257c40426bc2f0c6903d4115caaf1" +source = "git+https://github.com/jntrnr/reedline?branch=main#88bded3417e7f6c1242b444f403448de583357f0" dependencies = [ "chrono", "crossterm", @@ -936,9 +936,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smawk" @@ -948,9 +948,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "supports-color" -version = "1.1.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f5b0f9e689dd52e27228469dd68b7416b60d75b7571ae9060a5f4c50048fee" +checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" dependencies = [ "atty", "is_ci", @@ -967,18 +967,18 @@ dependencies = [ [[package]] name = "supports-unicode" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5fa283a620b255940913bd962cda2e6320e3799041f96ac0d7191ff2b4622f" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" dependencies = [ "atty", ] [[package]] name = "syn" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +checksum = "a4eac2e6c19f5c3abc0c229bea31ff0b9b091c7b14990e8924b92902a303a0c0" dependencies = [ "proc-macro2", "quote", diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index c7a5ce0b80..919e11e021 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -28,7 +28,7 @@ impl Command for Cd { let path = match path { Some(path) => { - let path = nu_path::expand_tilde(path); + let path = nu_path::expand_path(path); path.to_string_lossy().to_string() } None => { diff --git a/src/main.rs b/src/main.rs index c821e30e8c..f767a89536 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ fn main() -> Result<()> { Ok(()) } else { - use reedline::{DefaultCompletionActionHandler, FileBackedHistory, Reedline, Signal}; + use reedline::{FileBackedHistory, ListCompletionHandler, Reedline, Signal}; let completer = NuCompleter::new(engine_state.clone()); let mut entry_num = 0; @@ -81,7 +81,7 @@ fn main() -> Result<()> { engine_state: engine_state.clone(), })) .with_completion_action_handler(Box::new( - DefaultCompletionActionHandler::default().with_completer(Box::new(completer)), + ListCompletionHandler::default().with_completer(Box::new(completer)), )) .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), From c884d5ca31fee63bf784f607fb8fa40e9e47ea26 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 5 Oct 2021 10:50:46 +1300 Subject: [PATCH 10/25] Better completions for external args --- crates/nu-cli/src/completions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index f0820f0807..27ff57a6fe 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -30,8 +30,6 @@ impl Completer for NuCompleter { let flattened = flatten_block(&working_set, &output); - // println!("flattened: {:?}", flattened); - for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { match &flat.1 { @@ -84,7 +82,9 @@ impl Completer for NuCompleter { }) .collect(); } - nu_parser::FlatShape::Filepath | nu_parser::FlatShape::GlobPattern => { + nu_parser::FlatShape::Filepath + | nu_parser::FlatShape::GlobPattern + | nu_parser::FlatShape::ExternalArg => { let prefix = working_set.get_span_contents(flat.0); let prefix = String::from_utf8_lossy(prefix).to_string(); From 535ece4e763faeb036a04f5954b85fe50820fe18 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 5 Oct 2021 15:27:39 +1300 Subject: [PATCH 11/25] Add unit parsing and eval support --- Cargo.lock | 29 ++++ crates/nu-command/Cargo.toml | 3 +- crates/nu-command/src/filesystem/ls.rs | 51 ++++--- crates/nu-command/src/system/ps.rs | 4 +- crates/nu-command/src/system/sys.rs | 18 +-- crates/nu-engine/src/eval.rs | 83 ++++++++++- crates/nu-parser/src/flatten.rs | 6 + crates/nu-parser/src/parser.rs | 191 ++++++++++++++++++++++++- crates/nu-parser/src/type_check.rs | 26 ++++ crates/nu-protocol/Cargo.toml | 3 + crates/nu-protocol/src/ast/expr.rs | 3 +- crates/nu-protocol/src/span.rs | 6 +- crates/nu-protocol/src/ty.rs | 2 + crates/nu-protocol/src/value/mod.rs | 184 +++++++++++++++++++++++- crates/nu-protocol/src/value/unit.rs | 27 ++++ 15 files changed, 598 insertions(+), 38 deletions(-) create mode 100644 crates/nu-protocol/src/value/unit.rs diff --git a/Cargo.lock b/Cargo.lock index 696490da9f..5aa2a2c69f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,15 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "byte-unit" +version = "4.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +dependencies = [ + "utf8-width", +] + [[package]] name = "cc" version = "1.0.70" @@ -119,10 +128,20 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] +[[package]] +name = "chrono-humanize" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" +dependencies = [ + "chrono", +] + [[package]] name = "core-foundation-sys" version = "0.8.2" @@ -497,6 +516,7 @@ dependencies = [ name = "nu-command" version = "0.1.0" dependencies = [ + "chrono", "glob", "nu-engine", "nu-json", @@ -550,6 +570,9 @@ dependencies = [ name = "nu-protocol" version = "0.1.0" dependencies = [ + "byte-unit", + "chrono", + "chrono-humanize", "miette", "serde", "thiserror", @@ -1099,6 +1122,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "utf8-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c6bcd32f9b..f58ebd5795 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,4 +15,5 @@ nu-table = { path = "../nu-table" } # Potential dependencies for extras glob = "0.3.0" thiserror = "1.0.29" -sysinfo = "0.20.4" \ No newline at end of file +sysinfo = "0.20.4" +chrono = { version="0.4.19", features=["serde"] } \ No newline at end of file diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 77b9f1c602..5e3ab2e2b9 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use nu_engine::eval_expression; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; @@ -56,25 +57,39 @@ impl Command for Ls { let is_dir = metadata.is_dir(); let filesize = metadata.len(); + let mut cols = vec!["name".into(), "type".into(), "size".into()]; + + let mut vals = vec![ + Value::String { + val: path.to_string_lossy().to_string(), + span: call_span, + }, + if is_file { + Value::string("File", call_span) + } else if is_dir { + Value::string("Dir", call_span) + } else { + Value::Nothing { span: call_span } + }, + Value::Filesize { + val: filesize as i64, + span: call_span, + }, + ]; + + if let Ok(date) = metadata.modified() { + let utc: DateTime = date.into(); + + cols.push("modified".into()); + vals.push(Value::Date { + val: utc.into(), + span: call_span, + }); + } + Value::Record { - cols: vec!["name".into(), "type".into(), "size".into()], - vals: vec![ - Value::String { - val: path.to_string_lossy().to_string(), - span: call_span, - }, - if is_file { - Value::string("file", call_span) - } else if is_dir { - Value::string("dir", call_span) - } else { - Value::Nothing { span: call_span } - }, - Value::Filesize { - val: filesize, - span: call_span, - }, - ], + cols, + vals, span: call_span, } } diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index b51310a387..68f9409a89 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -86,13 +86,13 @@ fn run_ps(call: &Call) -> Result { cols.push("mem".into()); vals.push(Value::Filesize { - val: result.memory() * 1000, + val: result.memory() as i64 * 1000, span, }); cols.push("virtual".into()); vals.push(Value::Filesize { - val: result.virtual_memory() * 1000, + val: result.virtual_memory() as i64 * 1000, span, }); diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs index c3bf8591dd..b0323ffdf6 100644 --- a/crates/nu-command/src/system/sys.rs +++ b/crates/nu-command/src/system/sys.rs @@ -112,13 +112,13 @@ pub fn disks(sys: &mut System, span: Span) -> Option { cols.push("total".into()); vals.push(Value::Filesize { - val: disk.total_space(), + val: disk.total_space() as i64, span, }); cols.push("free".into()); vals.push(Value::Filesize { - val: disk.available_space(), + val: disk.available_space() as i64, span, }); @@ -148,13 +148,13 @@ pub fn net(sys: &mut System, span: Span) -> Option { cols.push("sent".into()); vals.push(Value::Filesize { - val: data.total_transmitted(), + val: data.total_transmitted() as i64, span, }); cols.push("recv".into()); vals.push(Value::Filesize { - val: data.total_received(), + val: data.total_received() as i64, span, }); @@ -215,25 +215,25 @@ pub fn mem(sys: &mut System, span: Span) -> Option { cols.push("total".into()); vals.push(Value::Filesize { - val: total_mem * 1000, + val: total_mem as i64 * 1000, span, }); cols.push("free".into()); vals.push(Value::Filesize { - val: free_mem * 1000, + val: free_mem as i64 * 1000, span, }); cols.push("swap total".into()); vals.push(Value::Filesize { - val: total_swap * 1000, + val: total_swap as i64 * 1000, span, }); cols.push("swap free".into()); vals.push(Value::Filesize { - val: free_swap * 1000, + val: free_swap as i64 * 1000, span, }); @@ -276,7 +276,7 @@ pub fn host(sys: &mut System, span: Span) -> Option { } cols.push("uptime".into()); vals.push(Value::Duration { - val: 1000000000 * sys.uptime() as u64, + val: 1000000000 * sys.uptime() as i64, span, }); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 5882ca2445..d59ff4b3e0 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{Range, ShellError, Span, Type, Value}; +use nu_protocol::{Range, ShellError, Span, Type, Unit, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -123,6 +123,10 @@ pub fn eval_expression( val: *f, span: expr.span, }), + Expr::ValueWithUnit(e, unit) => match eval_expression(context, e)? { + Value::Int { val, .. } => Ok(compute(val, unit.item, unit.span)), + _ => Err(ShellError::CantConvert("unit value".into(), e.span)), + }, Expr::Range(from, next, to, operator) => { let from = if let Some(f) = from { eval_expression(context, f)? @@ -291,3 +295,80 @@ pub fn eval_block( Ok(input) } + +pub fn compute(size: i64, unit: Unit, span: Span) -> Value { + match unit { + Unit::Byte => Value::Filesize { val: size, span }, + Unit::Kilobyte => Value::Filesize { + val: size * 1000, + span, + }, + Unit::Megabyte => Value::Filesize { + val: size * 1000 * 1000, + span, + }, + Unit::Gigabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000, + span, + }, + Unit::Terabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000 * 1000, + span, + }, + Unit::Petabyte => Value::Filesize { + val: size * 1000 * 1000 * 1000 * 1000 * 1000, + span, + }, + + Unit::Kibibyte => Value::Filesize { + val: size * 1024, + span, + }, + Unit::Mebibyte => Value::Filesize { + val: size * 1024 * 1024, + span, + }, + Unit::Gibibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024, + span, + }, + Unit::Tebibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024 * 1024, + span, + }, + Unit::Pebibyte => Value::Filesize { + val: size * 1024 * 1024 * 1024 * 1024 * 1024, + span, + }, + + Unit::Nanosecond => Value::Duration { val: size, span }, + Unit::Microsecond => Value::Duration { + val: size * 1000, + span, + }, + Unit::Millisecond => Value::Duration { + val: size * 1000 * 1000, + span, + }, + Unit::Second => Value::Duration { + val: size * 1000 * 1000 * 1000, + span, + }, + Unit::Minute => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60, + span, + }, + Unit::Hour => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60, + span, + }, + Unit::Day => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60 * 24, + span, + }, + Unit::Week => Value::Duration { + val: size * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7, + span, + }, + } +} diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index c2d3ade9a4..90cfc5092d 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -81,6 +81,12 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::ValueWithUnit(x, unit) => { + let mut output = flatten_expression(working_set, x); + output.push((unit.span, FlatShape::String)); + + output + } Expr::CellPath(cell_path) => { let mut output = vec![]; for path_element in &cell_path.members { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 925cc76bb9..e1a4f62867 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -10,7 +10,7 @@ use nu_protocol::{ Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, - span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, + span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, }; use crate::parse_keywords::{ @@ -1351,6 +1351,193 @@ pub fn parse_filepath( } } +/// Parse a duration type, eg '10day' +pub fn parse_duration( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + fn parse_decimal_str_to_number(decimal: &str) -> Option { + let string_to_parse = format!("0.{}", decimal); + if let Ok(x) = string_to_parse.parse::() { + return Some((1_f64 / x) as i64); + } + None + } + + let bytes = working_set.get_span_contents(span); + let token = String::from_utf8_lossy(bytes).to_string(); + + let unit_groups = [ + (Unit::Nanosecond, "NS", None), + (Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))), + (Unit::Millisecond, "MS", Some((Unit::Microsecond, 1000))), + (Unit::Second, "SEC", Some((Unit::Millisecond, 1000))), + (Unit::Minute, "MIN", Some((Unit::Second, 60))), + (Unit::Hour, "HR", Some((Unit::Minute, 60))), + (Unit::Day, "DAY", Some((Unit::Minute, 1440))), + (Unit::Week, "WK", Some((Unit::Day, 7))), + ]; + if let Some(unit) = unit_groups + .iter() + .find(|&x| token.to_uppercase().ends_with(x.1)) + { + let mut lhs = token.clone(); + for _ in 0..unit.1.len() { + lhs.pop(); + } + + let input: Vec<&str> = lhs.split('.').collect(); + let (value, unit_to_use) = match &input[..] { + [number_str] => (number_str.parse::().ok(), unit.0), + [number_str, decimal_part_str] => match unit.2 { + Some(unit_to_convert_to) => match ( + number_str.parse::(), + parse_decimal_str_to_number(decimal_part_str), + ) { + (Ok(number), Some(decimal_part)) => ( + Some( + (number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), + ), + unit_to_convert_to.0, + ), + _ => (None, unit.0), + }, + None => (None, unit.0), + }, + _ => (None, unit.0), + }; + + if let Some(x) = value { + let lhs_span = Span::new(span.start, span.start + lhs.len()); + let unit_span = Span::new(span.start + lhs.len(), span.end); + return ( + Expression { + expr: Expr::ValueWithUnit( + Box::new(Expression { + expr: Expr::Int(x), + span: lhs_span, + ty: Type::Number, + custom_completion: None, + }), + Spanned { + item: unit_to_use, + span: unit_span, + }, + ), + span, + ty: Type::Duration, + custom_completion: None, + }, + None, + ); + } + } + + ( + garbage(span), + Some(ParseError::Mismatch( + "duration".into(), + "non-duration unit".into(), + span, + )), + ) +} + +/// Parse a unit type, eg '10kb' +pub fn parse_filesize( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + fn parse_decimal_str_to_number(decimal: &str) -> Option { + let string_to_parse = format!("0.{}", decimal); + if let Ok(x) = string_to_parse.parse::() { + return Some((1_f64 / x) as i64); + } + None + } + + let bytes = working_set.get_span_contents(span); + let token = String::from_utf8_lossy(bytes).to_string(); + + let unit_groups = [ + (Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))), + (Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))), + (Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))), + (Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))), + (Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))), + (Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))), + (Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))), + (Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))), + (Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))), + (Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))), + (Unit::Byte, "B", None), + ]; + if let Some(unit) = unit_groups + .iter() + .find(|&x| token.to_uppercase().ends_with(x.1)) + { + let mut lhs = token.clone(); + for _ in 0..unit.1.len() { + lhs.pop(); + } + + let input: Vec<&str> = lhs.split('.').collect(); + let (value, unit_to_use) = match &input[..] { + [number_str] => (number_str.parse::().ok(), unit.0), + [number_str, decimal_part_str] => match unit.2 { + Some(unit_to_convert_to) => match ( + number_str.parse::(), + parse_decimal_str_to_number(decimal_part_str), + ) { + (Ok(number), Some(decimal_part)) => ( + Some( + (number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), + ), + unit_to_convert_to.0, + ), + _ => (None, unit.0), + }, + None => (None, unit.0), + }, + _ => (None, unit.0), + }; + + if let Some(x) = value { + let lhs_span = Span::new(span.start, span.start + lhs.len()); + let unit_span = Span::new(span.start + lhs.len(), span.end); + return ( + Expression { + expr: Expr::ValueWithUnit( + Box::new(Expression { + expr: Expr::Int(x), + span: lhs_span, + ty: Type::Number, + custom_completion: None, + }), + Spanned { + item: unit_to_use, + span: unit_span, + }, + ), + span, + ty: Type::Filesize, + custom_completion: None, + }, + None, + ); + } + } + + ( + garbage(span), + Some(ParseError::Mismatch( + "filesize".into(), + "non-filesize unit".into(), + span, + )), + ) +} + pub fn parse_glob_pattern( working_set: &mut StateWorkingSet, span: Span, @@ -2381,6 +2568,8 @@ pub fn parse_value( } SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Int => parse_int(bytes, span), + SyntaxShape::Duration => parse_duration(working_set, span), + SyntaxShape::Filesize => parse_filesize(working_set, span), SyntaxShape::Range => parse_range(working_set, span), SyntaxShape::Filepath => parse_filepath(working_set, span), SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span), diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index fa07f755da..03ce14c6e6 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -29,6 +29,9 @@ pub fn math_result_type( (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), (Type::String, Type::String) => (Type::String, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), (Type::Int, _) => { @@ -64,6 +67,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -85,6 +91,7 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -106,6 +113,7 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { @@ -127,6 +135,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -148,6 +159,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -169,6 +183,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -190,6 +207,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -209,6 +229,9 @@ pub fn math_result_type( Operator::Equal => match (&lhs.ty, &rhs.ty) { (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (x, y) if x == y => (Type::Bool, None), (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), @@ -231,6 +254,9 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None), (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index a4fbcd6bf8..c44b03cf08 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -9,3 +9,6 @@ edition = "2018" thiserror = "1.0.29" miette = "3.0.0" serde = {version = "1.0.130", features = ["derive"]} +chrono = { version="0.4.19", features=["serde"] } +chrono-humanize = "0.2.1" +byte-unit = "4.0.9" \ No newline at end of file diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 45955da946..4a9b42527d 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,5 +1,5 @@ use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator}; -use crate::{BlockId, Signature, Span, VarId}; +use crate::{BlockId, Signature, Span, Spanned, Unit, VarId}; #[derive(Debug, Clone)] pub enum Expr { @@ -23,6 +23,7 @@ pub enum Expr { List(Vec), Table(Vec, Vec>), Keyword(Vec, Span, Box), + ValueWithUnit(Box, Spanned), Filepath(String), GlobPattern(String), String(String), // FIXME: improve this in the future? diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 1dd0b38982..744915783a 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -1,7 +1,11 @@ use miette::SourceSpan; use serde::{Deserialize, Serialize}; -pub struct Spanned { +#[derive(Clone, Debug)] +pub struct Spanned +where + T: Clone + std::fmt::Debug, +{ pub item: T, pub span: Span, } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index e3015b7c05..9c70210cfe 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -12,6 +12,7 @@ pub enum Type { Block, CellPath, Duration, + Date, Filesize, List(Box), Number, @@ -30,6 +31,7 @@ impl Display for Type { Type::Block => write!(f, "block"), Type::Bool => write!(f, "bool"), Type::CellPath => write!(f, "cell path"), + Type::Date => write!(f, "date"), Type::Duration => write!(f, "duration"), Type::Filesize => write!(f, "filesize"), Type::Float => write!(f, "float"), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index eae64b8dc8..1e634964a4 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1,11 +1,15 @@ mod range; mod row; mod stream; +mod unit; +use chrono::{DateTime, FixedOffset}; +use chrono_humanize::HumanTime; pub use range::*; pub use row::*; use serde::{Deserialize, Serialize}; pub use stream::*; +pub use unit::*; use std::fmt::Debug; @@ -26,11 +30,15 @@ pub enum Value { span: Span, }, Filesize { - val: u64, + val: i64, span: Span, }, Duration { - val: u64, + val: i64, + span: Span, + }, + Date { + val: DateTime, span: Span, }, Range { @@ -95,6 +103,7 @@ impl Value { Value::Float { span, .. } => *span, Value::Filesize { span, .. } => *span, Value::Duration { span, .. } => *span, + Value::Date { span, .. } => *span, Value::Range { span, .. } => *span, Value::String { span, .. } => *span, Value::Record { span, .. } => *span, @@ -115,6 +124,7 @@ impl Value { Value::Float { span, .. } => *span = new_span, Value::Filesize { span, .. } => *span = new_span, Value::Duration { span, .. } => *span = new_span, + Value::Date { span, .. } => *span = new_span, Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::Record { span, .. } => *span = new_span, @@ -138,6 +148,7 @@ impl Value { Value::Float { .. } => Type::Float, Value::Filesize { .. } => Type::Filesize, Value::Duration { .. } => Type::Duration, + Value::Date { .. } => Type::Date, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, Value::Record { cols, vals, .. } => { @@ -159,8 +170,9 @@ impl Value { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format!("{} bytes", val), - Value::Duration { val, .. } => format!("{} ns", val), + Value::Filesize { val, .. } => format_filesize(val), + Value::Duration { val, .. } => format_duration(val), + Value::Date { val, .. } => HumanTime::from(val).to_string(), Value::Range { val, .. } => { format!( "range: [{}]", @@ -202,6 +214,7 @@ impl Value { Value::Float { val, .. } => val.to_string(), Value::Filesize { val, .. } => format!("{} bytes", val), Value::Duration { val, .. } => format!("{} ns", val), + Value::Date { val, .. } => format!("{:?}", val), Value::Range { val, .. } => val .into_iter() .map(|x| x.into_string()) @@ -390,6 +403,18 @@ impl Value { val: lhs.to_string() + rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs + *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs + *rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, @@ -420,6 +445,18 @@ impl Value { val: lhs - rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs - *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs - *rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, @@ -541,6 +578,19 @@ impl Value { val: lhs < rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs < rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs < rhs, + span, + }) + } + _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -570,6 +620,18 @@ impl Value { val: lhs <= rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs <= rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs <= rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -599,6 +661,18 @@ impl Value { val: lhs > rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs > rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs > rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -628,6 +702,18 @@ impl Value { val: lhs >= rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs >= rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs >= rhs, + span, + }) + } _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -664,6 +750,18 @@ impl Value { val: lhs == rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs == rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs == rhs, + span, + }) + } (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs == rhs, span, @@ -719,6 +817,18 @@ impl Value { val: lhs != rhs, span, }), + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs != rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Bool { + val: lhs != rhs, + span, + }) + } (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs != rhs, span, @@ -749,3 +859,69 @@ impl Value { } } } + +/// Format a duration in nanoseconds into a string +pub fn format_duration(duration: i64) -> String { + let (sign, duration) = if duration >= 0 { + (1, duration) + } else { + (-1, -duration) + }; + let (micros, nanos): (i64, i64) = (duration / 1000, duration % 1000); + let (millis, micros): (i64, i64) = (micros / 1000, micros % 1000); + let (secs, millis): (i64, i64) = (millis / 1000, millis % 1000); + let (mins, secs): (i64, i64) = (secs / 60, secs % 60); + let (hours, mins): (i64, i64) = (mins / 60, mins % 60); + let (days, hours): (i64, i64) = (hours / 24, hours % 24); + + let mut output_prep = vec![]; + + if days != 0 { + output_prep.push(format!("{}day", days)); + } + + if hours != 0 { + output_prep.push(format!("{}hr", hours)); + } + + if mins != 0 { + output_prep.push(format!("{}min", mins)); + } + // output 0sec for zero duration + if duration == 0 || secs != 0 { + output_prep.push(format!("{}sec", secs)); + } + + if millis != 0 { + output_prep.push(format!("{}ms", millis)); + } + + if micros != 0 { + output_prep.push(format!("{}us", micros)); + } + + if nanos != 0 { + output_prep.push(format!("{}ns", nanos)); + } + + format!( + "{}{}", + if sign == -1 { "-" } else { "" }, + output_prep.join(" ") + ) +} + +fn format_filesize(num_bytes: i64) -> String { + let byte = byte_unit::Byte::from_bytes(num_bytes as u128); + + if byte.get_bytes() == 0u128 { + return "—".to_string(); + } + + let byte = byte.get_appropriate_unit(false); + + match byte.get_unit() { + byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), + _ => byte.format(1), + } +} diff --git a/crates/nu-protocol/src/value/unit.rs b/crates/nu-protocol/src/value/unit.rs new file mode 100644 index 0000000000..27fd893014 --- /dev/null +++ b/crates/nu-protocol/src/value/unit.rs @@ -0,0 +1,27 @@ +#[derive(Debug, Clone, Copy)] +pub enum Unit { + // Filesize units: metric + Byte, + Kilobyte, + Megabyte, + Gigabyte, + Terabyte, + Petabyte, + + // Filesize units: ISO/IEC 80000 + Kibibyte, + Mebibyte, + Gibibyte, + Tebibyte, + Pebibyte, + + // Duration units + Nanosecond, + Microsecond, + Millisecond, + Second, + Minute, + Hour, + Day, + Week, +} From 31ce8c1e33549dd912aafed334d1c7394917eccc Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 5 Oct 2021 15:46:24 +1300 Subject: [PATCH 12/25] Variable completions and better ls --- crates/nu-cli/src/completions.rs | 34 +++++++++++++++++++ crates/nu-command/src/filesystem/ls.rs | 3 ++ crates/nu-protocol/src/engine/engine_state.rs | 4 +-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 27ff57a6fe..57298d0936 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -32,6 +32,40 @@ impl Completer for NuCompleter { for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { + let prefix = working_set.get_span_contents(flat.0); + if prefix.starts_with(b"$") { + let mut output = vec![]; + + for scope in &working_set.delta.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + for scope in &engine_state.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + + return output; + } + match &flat.1 { nu_parser::FlatShape::Custom(custom_completion) => { let prefix = working_set.get_span_contents(flat.0).to_vec(); diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 5e3ab2e2b9..36fbaff74a 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -36,6 +36,9 @@ impl Command for Ls { let path = std::path::Path::new(&result); if path.is_dir() { + if !result.ends_with(std::path::MAIN_SEPARATOR) { + result.push(std::path::MAIN_SEPARATOR); + } result.push('*'); } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index c5cd679860..3d8f2c8229 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -12,12 +12,12 @@ pub struct EngineState { vars: Vec, decls: Vec>, blocks: Vec, - scope: Vec, + pub scope: Vec, } #[derive(Debug)] pub struct ScopeFrame { - vars: HashMap, VarId>, + pub vars: HashMap, VarId>, decls: HashMap, DeclId>, aliases: HashMap, Vec>, modules: HashMap, BlockId>, From 1b96da5e5b9783c8c7fbc2cded7871453bac60a4 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 20:43:07 -0700 Subject: [PATCH 13/25] add custom filesystem shell errors --- crates/nu-command/src/filesystem/mv.rs | 68 +++++++++++++------------- crates/nu-protocol/src/shell_error.rs | 19 +++++++ 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index b3d9ec2f07..58c9c6dafc 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -49,29 +49,33 @@ impl Command for Mv { glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { - return Err(ShellError::InternalError(format!( - "source \"{:?}\" does not exist", - source - ))); + return Err(ShellError::FileNotFound( + call.positional.first().unwrap().span, + )); } if (destination.exists() && !destination.is_dir() && sources.len() > 1) || (!destination.exists() && sources.len() > 1) { - return Err(ShellError::InternalError( - "can only move multiple sources if destination is a directory".to_string(), - )); + return Err(ShellError::MoveNotPossible { + source_message: "Can't move many files".to_string(), + source_span: call.positional[0].span, + destination_message: "into single file".to_string(), + destination_span: call.positional[1].span, + }); } let some_if_source_is_destination = sources .iter() .find(|f| matches!(f, Ok(f) if destination.starts_with(f))); if destination.exists() && destination.is_dir() && sources.len() == 1 { - if let Some(Ok(filename)) = some_if_source_is_destination { - return Err(ShellError::InternalError(format!( - "Not possible to move {:?} to itself", - filename.file_name().expect("Invalid file name") - ))); + if let Some(Ok(_filename)) = some_if_source_is_destination { + return Err(ShellError::MoveNotPossible { + source_message: "Can't move directory".to_string(), + source_span: call.positional[0].span, + destination_message: "into itself".to_string(), + destination_span: call.positional[1].span, + }); } } @@ -83,19 +87,21 @@ impl Command for Mv { } for entry in sources.into_iter().flatten() { - move_file(&entry, &destination)? + move_file(call, &entry, &destination)? } Ok(Value::Nothing { span: call.head }) } } -fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { +fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { if to.exists() && from.is_dir() && to.is_file() { - return Err(ShellError::InternalError(format!( - "Cannot rename {:?} to a file", - from.file_name().expect("Invalid directory name") - ))); + return Err(ShellError::MoveNotPossible { + source_message: "Can't move a directory".to_string(), + source_span: call.positional[0].span, + destination_message: "to a file".to_string(), + destination_span: call.positional[1].span, + }); } let destination_dir_exists = if to.is_dir() { @@ -105,37 +111,31 @@ fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { }; if !destination_dir_exists { - return Err(ShellError::InternalError(format!( - "{:?} does not exist", - to.file_name().expect("Invalid directory name") - ))); + return Err(ShellError::DirectoryNotFound(call.positional[1].span)); } let mut to = to.clone(); if to.is_dir() { let from_file_name = match from.file_name() { Some(name) => name, - None => { - return Err(ShellError::InternalError(format!( - "{:?} is not a valid entry", - from.file_name().expect("Invalid directory name") - ))) - } + None => return Err(ShellError::DirectoryNotFound(call.positional[1].span)), }; to.push(from_file_name); } - move_item(&from, &to) + move_item(call, &from, &to) } -fn move_item(from: &Path, to: &Path) -> Result<(), ShellError> { +fn move_item(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. std::fs::rename(&from, &to).or_else(|_| { - Err(ShellError::InternalError(format!( - "Could not move {:?} to {:?}", - from, to, - ))) + Err(ShellError::MoveNotPossible { + source_message: "failed to move".to_string(), + source_span: call.positional[0].span, + destination_message: "into".to_string(), + destination_span: call.positional[1].span, + }) }) } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 8307e99617..0bb59291e7 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -78,4 +78,23 @@ pub enum ShellError { #[error("Flag not found")] #[diagnostic(code(nu::shell::flag_not_found), url(docsrs))] FlagNotFound(String, #[label("{0} not found")] Span), + + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + FileNotFound(#[label("file not found")] Span), + + #[error("Directory not found")] + #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] + DirectoryNotFound(#[label("directory not found")] Span), + + #[error("Move not possible")] + #[diagnostic(code(nu::shell::move_not_possible), url(docsrs))] + MoveNotPossible { + source_message: String, + #[label("{source_message}")] + source_span: Span, + destination_message: String, + #[label("{destination_message}")] + destination_span: Span, + }, } From 80e7a8d5949b1aefe2c001a02b2dda599a3813cf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:58:49 +1300 Subject: [PATCH 14/25] Update mv.rs --- crates/nu-command/src/filesystem/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 58c9c6dafc..ebedb42446 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::FilePath, + SyntaxShape::Filepath, "the location to move files/directories to", ) } From 0ef0588e295e085198c2f2a9227d5bc4908cbd39 Mon Sep 17 00:00:00 2001 From: jacremer Date: Mon, 4 Oct 2021 21:40:26 -0700 Subject: [PATCH 15/25] mv clippy suggestions --- crates/nu-command/src/filesystem/mv.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index ebedb42446..4955cf0031 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::Filepath, + SyntaxShape::FilePath, "the location to move files/directories to", ) } @@ -94,7 +94,7 @@ impl Command for Mv { } } -fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { +fn move_file(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { if to.exists() && from.is_dir() && to.is_file() { return Err(ShellError::MoveNotPossible { source_message: "Can't move a directory".to_string(), @@ -114,7 +114,7 @@ fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError return Err(ShellError::DirectoryNotFound(call.positional[1].span)); } - let mut to = to.clone(); + let mut to = to.to_path_buf(); if to.is_dir() { let from_file_name = match from.file_name() { Some(name) => name, @@ -124,18 +124,16 @@ fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError to.push(from_file_name); } - move_item(call, &from, &to) + move_item(call, from, &to) } fn move_item(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. - std::fs::rename(&from, &to).or_else(|_| { - Err(ShellError::MoveNotPossible { - source_message: "failed to move".to_string(), - source_span: call.positional[0].span, - destination_message: "into".to_string(), - destination_span: call.positional[1].span, - }) + std::fs::rename(&from, &to).map_err(|_| ShellError::MoveNotPossible { + source_message: "failed to move".to_string(), + source_span: call.positional[0].span, + destination_message: "into".to_string(), + destination_span: call.positional[1].span, }) } From 27dcbe5c8aab18ae865ec440100dc66dd77fecb9 Mon Sep 17 00:00:00 2001 From: jacremer Date: Mon, 4 Oct 2021 22:08:15 -0700 Subject: [PATCH 16/25] fix SyntaxShape::Filepath build error --- crates/nu-command/src/filesystem/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 4955cf0031..19cdf0d896 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::FilePath, + SyntaxShape::Filepath, "the location to move files/directories to", ) } From 9d49618e87cb6c77f7fc672a123fb638a2938cce Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:54:30 -0700 Subject: [PATCH 17/25] add impl From io::Error and dyn Error for ShellError --- crates/nu-protocol/src/shell_error.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 0bb59291e7..73f5147fa8 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -97,4 +97,20 @@ pub enum ShellError { #[label("{destination_message}")] destination_span: Span, }, + + #[error("Move not possible")] + #[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))] + MoveNotPossibleSingle(String, #[label("{0}")] Span), +} + +impl From for ShellError { + fn from(input: std::io::Error) -> ShellError { + ShellError::InternalError(format!("{:?}", input)) + } +} + +impl From> for ShellError { + fn from(input: Box) -> ShellError { + ShellError::InternalError(format!("{:?}", input)) + } } From 5da1310696ef807a1d83c0a79c21ebe34184d106 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:55:33 -0700 Subject: [PATCH 18/25] add fs utils --- crates/nu-command/src/filesystem/mod.rs | 3 + crates/nu-command/src/filesystem/util.rs | 82 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 crates/nu-command/src/filesystem/util.rs diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 90b697fcd8..2d2212766a 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,7 +1,10 @@ mod cd; +mod cp; mod ls; mod mv; +mod util; pub use cd::Cd; +pub use cp::Cp; pub use ls::Ls; pub use mv::Mv; diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs new file mode 100644 index 0000000000..86a0bd4c96 --- /dev/null +++ b/crates/nu-command/src/filesystem/util.rs @@ -0,0 +1,82 @@ +use std::path::{Path, PathBuf}; + +use nu_path::canonicalize_with; +use nu_protocol::ShellError; + +#[derive(Default)] +pub struct FileStructure { + pub resources: Vec, +} + +impl FileStructure { + pub fn new() -> FileStructure { + FileStructure { resources: vec![] } + } + + #[allow(dead_code)] + pub fn contains_more_than_one_file(&self) -> bool { + self.resources.len() > 1 + } + + #[allow(dead_code)] + pub fn contains_files(&self) -> bool { + !self.resources.is_empty() + } + + pub fn paths_applying_with( + &mut self, + to: F, + ) -> Result, Box> + where + F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box>, + { + self.resources + .iter() + .map(|f| (PathBuf::from(&f.location), f.at)) + .map(|f| to(f)) + .collect() + } + + pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> { + self.resources = Vec::::new(); + self.build(start_path, 0)?; + self.resources.sort(); + + Ok(()) + } + + fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> { + let source = canonicalize_with(src, std::env::current_dir()?)?; + + if source.is_dir() { + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + self.build(&path, lvl + 1)?; + } + + self.resources.push(Resource { + location: path.to_path_buf(), + at: lvl, + }); + } + } else { + self.resources.push(Resource { + location: source, + at: lvl, + }); + } + + Ok(()) + } +} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Resource { + pub at: usize, + pub location: PathBuf, +} + +impl Resource {} From 8dc3ebd6e23123657c8664906b442d093df48aa5 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:55:46 -0700 Subject: [PATCH 19/25] start cp command --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/cp.rs | 155 +++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 crates/nu-command/src/filesystem/cp.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8418b2a384..c36e7ea906 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -17,6 +17,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); working_set.add_decl(Box::new(Cd)); + working_set.add_decl(Box::new(Cp)); working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs new file mode 100644 index 0000000000..6c6850c9ee --- /dev/null +++ b/crates/nu-command/src/filesystem/cp.rs @@ -0,0 +1,155 @@ +use std::env::current_dir; +use std::path::PathBuf; + +use nu_engine::{eval_expression, CallExt}; +use nu_path::canonicalize_with; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; + +use crate::filesystem::util::FileStructure; + +pub struct Cp; + +impl Command for Cp { + fn name(&self) -> &str { + "cp" + } + + fn usage(&self) -> &str { + "Copy files." + } + + fn signature(&self) -> Signature { + Signature::build("cp") + .required("source", SyntaxShape::GlobPattern, "the place to copy from") + .required("destination", SyntaxShape::Filepath, "the place to copy to") + .switch( + "recursive", + "copy recursively through subdirectories", + Some('r'), + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let source: String = call.req(context, 0)?; + let destination: String = call.req(context, 1)?; + + let path: PathBuf = current_dir().unwrap(); + let source = path.join(source.as_str()); + let destination = path.join(destination.as_str()); + + let mut sources = + glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); + if sources.is_empty() { + return Err(ShellError::FileNotFound(call.positional[0].span)); + } + + if sources.len() > 1 && !destination.is_dir() { + return Err(ShellError::MoveNotPossible { + source_message: "Can't move many files".to_string(), + source_span: call.positional[0].span, + destination_message: "into single file".to_string(), + destination_span: call.positional[1].span, + }); + } + + let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); + let recursive = call + .named + .iter() + .fold(false, |acc, p| acc || { &p.0 == "recursive" }); + if any_source_is_dir && !recursive { + return Err(ShellError::MoveNotPossibleSingle( + "Directories must be copied using \"--recursive\"".to_string(), + call.positional[0].span, + )); + } + + // for entry in sources.into_iter().flatten() { + // let mut sources = FileStructure::new(); + // sources.walk_decorate(&entry)?; + + // if entry.is_file() { + // let sources = sources.paths_applying_with(|(source_file, _depth_level)| { + // if destination.is_dir() { + // let mut dest = canonicalize_with(&destination.item, &path)?; + // if let Some(name) = entry.file_name() { + // dest.push(name); + // } + // Ok((source_file, dest)) + // } else { + // Ok((source_file, destination.clone())) + // } + // })?; + + // for (src, dst) in sources { + // if src.is_file() { + // std::fs::copy(src, dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) + // })?; + // } + // } + // } else if entry.is_dir() { + // let destination = if !destination.exists() { + // destination.clone() + // } else { + // match entry.file_name() { + // Some(name) => destination.join(name), + // None => { + // return Err(ShellError::labeled_error( + // "Copy aborted. Not a valid path", + // "not a valid path", + // dst.tag, + // )) + // } + // } + // }; + + // std::fs::create_dir_all(&destination).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag) + // })?; + + // let sources = sources.paths_applying_with(|(source_file, depth_level)| { + // let mut dest = destination.clone(); + // let path = canonicalize_with(&source_file, &path)?; + + // let comps: Vec<_> = path + // .components() + // .map(|fragment| fragment.as_os_str()) + // .rev() + // .take(1 + depth_level) + // .collect(); + + // for fragment in comps.into_iter().rev() { + // dest.push(fragment); + // } + + // Ok((PathBuf::from(&source_file), dest)) + // })?; + + // let dst_tag = &dst.tag; + // for (src, dst) in sources { + // if src.is_dir() && !dst.exists() { + // std::fs::create_dir_all(&dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag) + // })?; + // } + + // if src.is_file() { + // std::fs::copy(&src, &dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) + // })?; + // } + // } + // } + // } + + Ok(Value::Nothing { span: call.head }) + } +} From 8536c12bd9554699020f7b76e7d35e18939a9eaa Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Tue, 5 Oct 2021 12:59:17 -0700 Subject: [PATCH 20/25] change signature name to get, it was (I believe) incorrectly named wrap --- crates/nu-command/src/filters/get.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index c06badbb26..7ad1c12823 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -15,7 +15,7 @@ impl Command for Get { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("wrap").required( + Signature::build("get").required( "cell_path", SyntaxShape::CellPath, "the cell path to the data", From 48f534cd3b93b3f915202635400061784bd892d0 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Tue, 5 Oct 2021 13:02:56 -0700 Subject: [PATCH 21/25] change location of reedline to nushell from the jt repo --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5aa2a2c69f..5ff65ec7db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,7 +827,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/jntrnr/reedline?branch=main#88bded3417e7f6c1242b444f403448de583357f0" +source = "git+https://github.com/nushell/reedline?branch=main#88bded3417e7f6c1242b444f403448de583357f0" dependencies = [ "chrono", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 826e3d542e..01c94b401a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-command", "crates/nu-protocol"] [dependencies] -reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } crossterm = "0.21.*" nu-cli = { path="./crates/nu-cli" } nu-command = { path="./crates/nu-command" } From 5cc7fbcde7e1c81b21c4fad55e6c78cabef6370e Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Tue, 5 Oct 2021 13:03:43 -0700 Subject: [PATCH 22/25] jntrnr to nushell --- crates/nu-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 5b47189196..3f2f90c7cc 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -12,4 +12,4 @@ nu-protocol = { path = "../nu-protocol" } miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" nu-ansi-term = "0.36.0" -reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } From 74d4c501a8a79de85915d6add55dfd86f012e6e5 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 14:08:39 -0700 Subject: [PATCH 23/25] add move, recursive fill, and recursive create procedures --- crates/nu-command/src/filesystem/cp.rs | 167 +++++++++++++---------- crates/nu-command/src/filesystem/util.rs | 5 +- crates/nu-protocol/src/shell_error.rs | 14 ++ 3 files changed, 109 insertions(+), 77 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 6c6850c9ee..d2a4fbb81b 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -44,7 +44,7 @@ impl Command for Cp { let source = path.join(source.as_str()); let destination = path.join(destination.as_str()); - let mut sources = + let sources = glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { return Err(ShellError::FileNotFound(call.positional[0].span)); @@ -60,10 +60,7 @@ impl Command for Cp { } let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); - let recursive = call - .named - .iter() - .fold(false, |acc, p| acc || { &p.0 == "recursive" }); + let recursive = call.named.iter().any(|p| &p.0 == "recursive"); if any_source_is_dir && !recursive { return Err(ShellError::MoveNotPossibleSingle( "Directories must be copied using \"--recursive\"".to_string(), @@ -71,84 +68,106 @@ impl Command for Cp { )); } - // for entry in sources.into_iter().flatten() { - // let mut sources = FileStructure::new(); - // sources.walk_decorate(&entry)?; + for entry in sources.into_iter().flatten() { + let mut sources = FileStructure::new(); + sources.walk_decorate(&entry)?; - // if entry.is_file() { - // let sources = sources.paths_applying_with(|(source_file, _depth_level)| { - // if destination.is_dir() { - // let mut dest = canonicalize_with(&destination.item, &path)?; - // if let Some(name) = entry.file_name() { - // dest.push(name); - // } - // Ok((source_file, dest)) - // } else { - // Ok((source_file, destination.clone())) - // } - // })?; + if entry.is_file() { + let sources = sources.paths_applying_with(|(source_file, _depth_level)| { + if destination.is_dir() { + let mut dest = canonicalize_with(&destination, &path)?; + if let Some(name) = entry.file_name() { + dest.push(name); + } + Ok((source_file, dest)) + } else { + Ok((source_file, destination.clone())) + } + })?; - // for (src, dst) in sources { - // if src.is_file() { - // std::fs::copy(src, dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) - // })?; - // } - // } - // } else if entry.is_dir() { - // let destination = if !destination.exists() { - // destination.clone() - // } else { - // match entry.file_name() { - // Some(name) => destination.join(name), - // None => { - // return Err(ShellError::labeled_error( - // "Copy aborted. Not a valid path", - // "not a valid path", - // dst.tag, - // )) - // } - // } - // }; + for (src, dst) in sources { + if src.is_file() { + std::fs::copy(&src, dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to move containing file \"{}\": {}", + src.to_string_lossy(), + e + ), + call.positional[0].span, + ) + })?; + } + } + } else if entry.is_dir() { + let destination = if !destination.exists() { + destination.clone() + } else { + match entry.file_name() { + Some(name) => destination.join(name), + None => { + return Err(ShellError::FileNotFoundCustom( + format!("containing \"{:?}\" is not a valid path", entry), + call.positional[0].span, + )) + } + } + }; - // std::fs::create_dir_all(&destination).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag) - // })?; + std::fs::create_dir_all(&destination).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!("failed to recursively fill destination: {}", e), + call.positional[1].span, + ) + })?; - // let sources = sources.paths_applying_with(|(source_file, depth_level)| { - // let mut dest = destination.clone(); - // let path = canonicalize_with(&source_file, &path)?; + let sources = sources.paths_applying_with(|(source_file, depth_level)| { + let mut dest = destination.clone(); + let path = canonicalize_with(&source_file, &path)?; - // let comps: Vec<_> = path - // .components() - // .map(|fragment| fragment.as_os_str()) - // .rev() - // .take(1 + depth_level) - // .collect(); + let comps: Vec<_> = path + .components() + .map(|fragment| fragment.as_os_str()) + .rev() + .take(1 + depth_level) + .collect(); - // for fragment in comps.into_iter().rev() { - // dest.push(fragment); - // } + for fragment in comps.into_iter().rev() { + dest.push(fragment); + } - // Ok((PathBuf::from(&source_file), dest)) - // })?; + Ok((PathBuf::from(&source_file), dest)) + })?; - // let dst_tag = &dst.tag; - // for (src, dst) in sources { - // if src.is_dir() && !dst.exists() { - // std::fs::create_dir_all(&dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag) - // })?; - // } + for (src, dst) in sources { + if src.is_dir() && !dst.exists() { + std::fs::create_dir_all(&dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to create containing directory \"{}\": {}", + dst.to_string_lossy(), + e + ), + call.positional[1].span, + ) + })?; + } - // if src.is_file() { - // std::fs::copy(&src, &dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) - // })?; - // } - // } - // } - // } + if src.is_file() { + std::fs::copy(&src, &dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to move containing file \"{}\": {}", + src.to_string_lossy(), + e + ), + call.positional[0].span, + ) + })?; + } + } + } + } Ok(Value::Nothing { span: call.head }) } diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 86a0bd4c96..a2d217cf05 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -8,17 +8,16 @@ pub struct FileStructure { pub resources: Vec, } +#[allow(dead_code)] impl FileStructure { pub fn new() -> FileStructure { FileStructure { resources: vec![] } } - #[allow(dead_code)] pub fn contains_more_than_one_file(&self) -> bool { self.resources.len() > 1 } - #[allow(dead_code)] pub fn contains_files(&self) -> bool { !self.resources.is_empty() } @@ -33,7 +32,7 @@ impl FileStructure { self.resources .iter() .map(|f| (PathBuf::from(&f.location), f.at)) - .map(|f| to(f)) + .map(to) .collect() } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 73f5147fa8..6a7464508e 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -83,10 +83,18 @@ pub enum ShellError { #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] FileNotFound(#[label("file not found")] Span), + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + FileNotFoundCustom(String, #[label("{0}")] Span), + #[error("Directory not found")] #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] DirectoryNotFound(#[label("directory not found")] Span), + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + DirectoryNotFoundCustom(String, #[label("{0}")] Span), + #[error("Move not possible")] #[diagnostic(code(nu::shell::move_not_possible), url(docsrs))] MoveNotPossible { @@ -109,6 +117,12 @@ impl From for ShellError { } } +impl std::convert::From> for ShellError { + fn from(input: Box) -> ShellError { + ShellError::InternalError(input.to_string()) + } +} + impl From> for ShellError { fn from(input: Box) -> ShellError { ShellError::InternalError(format!("{:?}", input)) From cc8a470668367a337167618e8e023ddec912570c Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 14:13:23 -0700 Subject: [PATCH 24/25] clean up unused imports --- crates/nu-command/src/filesystem/cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index d2a4fbb81b..6ebc305923 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,11 +1,11 @@ use std::env::current_dir; use std::path::PathBuf; -use nu_engine::{eval_expression, CallExt}; +use nu_engine::CallExt; use nu_path::canonicalize_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; use crate::filesystem::util::FileStructure; From b3b51a2ed65e02d9e8eee44189a0b3d2dacd4462 Mon Sep 17 00:00:00 2001 From: jacremer Date: Tue, 5 Oct 2021 15:09:51 -0700 Subject: [PATCH 25/25] drop redundant iter -> vec -> iter --- crates/nu-command/src/filesystem/cp.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 6ebc305923..79751f2c93 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -124,18 +124,13 @@ impl Command for Cp { let sources = sources.paths_applying_with(|(source_file, depth_level)| { let mut dest = destination.clone(); let path = canonicalize_with(&source_file, &path)?; - - let comps: Vec<_> = path + let components = path .components() .map(|fragment| fragment.as_os_str()) .rev() - .take(1 + depth_level) - .collect(); - - for fragment in comps.into_iter().rev() { - dest.push(fragment); - } + .take(1 + depth_level); + components.for_each(|fragment| dest.push(fragment)); Ok((PathBuf::from(&source_file), dest)) })?;