diff --git a/Cargo.lock b/Cargo.lock index cf1052648a..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" @@ -399,9 +418,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 +437,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", @@ -487,6 +506,7 @@ dependencies = [ "nu-ansi-term", "nu-engine", "nu-parser", + "nu-path", "nu-protocol", "reedline", "thiserror", @@ -496,6 +516,7 @@ dependencies = [ name = "nu-command" version = "0.1.0" dependencies = [ + "chrono", "glob", "nu-engine", "nu-json", @@ -549,6 +570,9 @@ dependencies = [ name = "nu-protocol" version = "0.1.0" dependencies = [ + "byte-unit", + "chrono", + "chrono-humanize", "miette", "serde", "thiserror", @@ -803,7 +827,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", @@ -935,9 +959,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" @@ -947,9 +971,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", @@ -966,18 +990,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", @@ -1098,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-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..57298d0936 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>, } @@ -30,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(); @@ -80,6 +116,27 @@ impl Completer for NuCompleter { }) .collect(); } + 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(); + + 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 +145,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/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/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 94282c75e2..919e11e021 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( @@ -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/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 2e243e05f4..36fbaff74a 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}; @@ -31,7 +32,17 @@ 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() { + if !result.ends_with(std::path::MAIN_SEPARATOR) { + result.push(std::path::MAIN_SEPARATOR); + } + result.push('*'); + } + + result } else { "*".into() }; @@ -49,25 +60,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 04057e540e..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)? @@ -240,6 +244,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 }), } @@ -283,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 fffb5626ec..90cfc5092d 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), } @@ -79,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 { @@ -118,7 +126,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/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 033a21e738..d093cd88ce 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -600,12 +600,90 @@ 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/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index fa9703a73f..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::{ @@ -1320,6 +1320,255 @@ 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)), + ) + } +} + +/// 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, +) -> (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 +1613,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, @@ -2319,10 +2568,12 @@ 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::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-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 449f4687df..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,9 @@ 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? CellPath(CellPath), FullCellPath(Box), diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index bbe6bdb1c1..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>, @@ -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 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/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..9c70210cfe 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -12,7 +12,7 @@ pub enum Type { Block, CellPath, Duration, - FilePath, + Date, Filesize, List(Box), Number, @@ -31,8 +31,8 @@ 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::FilePath => write!(f, "filepath"), Type::Filesize => write!(f, "filesize"), Type::Float => write!(f, "float"), Type::Int => write!(f, "int"), 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, +} 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(), diff --git a/src/tests.rs b/src/tests.rs index 7416aecb6f..0efd0b513a 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( @@ -449,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")