diff --git a/Cargo.lock b/Cargo.lock index 5e9478b02..c66b47087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,7 @@ name = "engine-q" version = "0.1.0" dependencies = [ "assert_cmd", + "crossterm", "miette", "nu-cli", "nu-command", @@ -509,6 +510,7 @@ name = "nu-engine" version = "0.1.0" dependencies = [ "nu-parser", + "nu-path", "nu-protocol", ] diff --git a/Cargo.toml b/Cargo.toml index 5ab8b8831..826e3d542 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-c [dependencies] reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } +crossterm = "0.21.*" nu-cli = { path="./crates/nu-cli" } nu-command = { path="./crates/nu-command" } nu-engine = { path="./crates/nu-engine" } diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs new file mode 100644 index 000000000..0313f2553 --- /dev/null +++ b/crates/nu-command/src/core_commands/help.rs @@ -0,0 +1,406 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Example, ShellError, Signature, Spanned, SyntaxShape, Value, +}; + +use nu_engine::CallExt; + +pub struct Help; + +impl Command for Help { + fn name(&self) -> &str { + "help" + } + + fn signature(&self) -> Signature { + Signature::build("help") + .rest( + "rest", + SyntaxShape::String, + "the name of command to get help on", + ) + .named( + "find", + SyntaxShape::String, + "string to find in command usage", + Some('f'), + ) + } + + fn usage(&self) -> &str { + "Display help information about commands." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + help(context, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "show all commands and sub-commands", + example: "help commands", + result: None, + }, + Example { + description: "generate documentation", + example: "help generate_docs", + result: None, + }, + Example { + description: "show help for single command", + example: "help match", + result: None, + }, + Example { + description: "show help for single sub-command", + example: "help str lpad", + result: None, + }, + Example { + description: "search for string in command usage", + example: "help --find char", + result: None, + }, + ] + } +} + +fn help(context: &EvaluationContext, call: &Call) -> Result { + let span = call.head; + let find: Option> = call.get_flag(context, "find")?; + let rest: Vec> = call.rest(context, 0)?; + + let full_commands = context.get_commands_info(); + + if let Some(f) = find { + let search_string = f.item; + let mut found_cmds_vec = Vec::new(); + + for cmd in full_commands { + let mut cols = vec![]; + let mut vals = vec![]; + + let key = cmd.name.clone(); + let c = cmd.usage.clone(); + let e = cmd.extra_usage.clone(); + if key.to_lowercase().contains(&search_string) + || c.to_lowercase().contains(&search_string) + || e.to_lowercase().contains(&search_string) + { + cols.push("name".into()); + vals.push(Value::String { val: key, span }); + + cols.push("usage".into()); + vals.push(Value::String { val: c, span }); + + cols.push("extra_usage".into()); + vals.push(Value::String { val: e, span }); + + found_cmds_vec.push(Value::Record { cols, vals, span }); + } + } + + return Ok(Value::List { + vals: found_cmds_vec, + span, + }); + } + + if !rest.is_empty() { + let mut found_cmds_vec = Vec::new(); + + if rest[0].item == "commands" { + for cmd in full_commands { + let mut cols = vec![]; + let mut vals = vec![]; + + let key = cmd.name.clone(); + let c = cmd.usage.clone(); + let e = cmd.extra_usage.clone(); + + cols.push("name".into()); + vals.push(Value::String { val: key, span }); + + cols.push("usage".into()); + vals.push(Value::String { val: c, span }); + + cols.push("extra_usage".into()); + vals.push(Value::String { val: e, span }); + + found_cmds_vec.push(Value::Record { cols, vals, span }); + } + } else { + let mut name = String::new(); + + for r in rest { + if !name.is_empty() { + name.push(' '); + } + name.push_str(&r.item); + } + + for cmd in full_commands { + let mut cols = vec![]; + let mut vals = vec![]; + + let key = cmd.name.clone(); + let c = cmd.usage.clone(); + let e = cmd.extra_usage.clone(); + + if key.starts_with(&name) { + cols.push("name".into()); + vals.push(Value::String { val: key, span }); + + cols.push("usage".into()); + vals.push(Value::String { val: c, span }); + + cols.push("extra_usage".into()); + vals.push(Value::String { val: e, span }); + + found_cmds_vec.push(Value::Record { cols, vals, span }); + } + } + } + Ok(Value::List { + vals: found_cmds_vec, + span, + }) + + // FIXME: the fancy help stuff needs to be reimplemented + /* + if rest[0].item == "commands" { + let mut sorted_names = scope.get_command_names(); + sorted_names.sort(); + + let (mut subcommand_names, command_names) = sorted_names + .into_iter() + // private only commands shouldn't be displayed + .filter(|cmd_name| { + scope + .get_command(cmd_name) + .filter(|command| !command.is_private()) + .is_some() + }) + .partition::, _>(|cmd_name| cmd_name.contains(' ')); + + fn process_name( + dict: &mut TaggedDictBuilder, + cmd_name: &str, + scope: Scope, + rest: Vec>, + name: Tag, + ) -> Result<(), ShellError> { + let document_tag = rest[0].tag.clone(); + let value = command_dict( + scope.get_command(cmd_name).ok_or_else(|| { + ShellError::labeled_error( + format!("Could not load {}", cmd_name), + "could not load command", + document_tag, + ) + })?, + name, + ); + + dict.insert_untagged("name", cmd_name); + dict.insert_untagged( + "description", + value + .get_data_by_key("usage".spanned_unknown()) + .ok_or_else(|| { + ShellError::labeled_error( + "Expected a usage key", + "expected a 'usage' key", + &value.tag, + ) + })? + .as_string()?, + ); + + Ok(()) + } + + fn make_subcommands_table( + subcommand_names: &mut Vec, + cmd_name: &str, + scope: Scope, + rest: Vec>, + name: Tag, + ) -> Result { + let (matching, not_matching) = + subcommand_names.drain(..).partition(|subcommand_name| { + subcommand_name.starts_with(&format!("{} ", cmd_name)) + }); + *subcommand_names = not_matching; + Ok(if !matching.is_empty() { + UntaggedValue::table( + &(matching + .into_iter() + .map(|cmd_name: String| -> Result<_, ShellError> { + let mut short_desc = TaggedDictBuilder::new(name.clone()); + process_name( + &mut short_desc, + &cmd_name, + scope.clone(), + rest.clone(), + name.clone(), + )?; + Ok(short_desc.into_value()) + }) + .collect::, _>>()?[..]), + ) + .into_value(name) + } else { + UntaggedValue::nothing().into_value(name) + }) + } + + let iterator = + command_names + .into_iter() + .map(move |cmd_name| -> Result<_, ShellError> { + let mut short_desc = TaggedDictBuilder::new(name.clone()); + process_name( + &mut short_desc, + &cmd_name, + scope.clone(), + rest.clone(), + name.clone(), + )?; + short_desc.insert_value( + "subcommands", + make_subcommands_table( + &mut subcommand_names, + &cmd_name, + scope.clone(), + rest.clone(), + name.clone(), + )?, + ); + ReturnSuccess::value(short_desc.into_value()) + }); + + Ok(iterator.into_action_stream()) + } else if rest[0].item == "generate_docs" { + Ok(ActionStream::one(ReturnSuccess::value(generate_docs( + &scope, + )))) + } else if rest.len() == 2 { + // Check for a subcommand + let command_name = format!("{} {}", rest[0].item, rest[1].item); + if let Some(command) = scope.get_command(&command_name) { + Ok(ActionStream::one(ReturnSuccess::value( + UntaggedValue::string(get_full_help(command.stream_command(), &scope)) + .into_value(Tag::unknown()), + ))) + } else { + Ok(ActionStream::empty()) + } + } else if let Some(command) = scope.get_command(&rest[0].item) { + Ok(ActionStream::one(ReturnSuccess::value( + UntaggedValue::string(get_full_help(command.stream_command(), &scope)) + .into_value(Tag::unknown()), + ))) + } else { + Err(ShellError::labeled_error( + "Can't find command (use 'help commands' for full list)", + "can't find command", + rest[0].tag.span, + )) + } + */ + } else { + let msg = r#"Welcome to Nushell. + +Here are some tips to help you get started. + * help commands - list all available commands + * help - display help about a particular command + +Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character. +Each stage in the pipeline works together to load, parse, and display information to you. + +[Examples] + +List the files in the current directory, sorted by size: + ls | sort-by size + +Get information about the current system: + sys | get host + +Get the processes on your system actively using CPU: + ps | where cpu > 0 + +You can also learn more at https://www.nushell.sh/book/"#; + + Ok(Value::String { + val: msg.into(), + span, + }) + } +} + +/* +fn for_spec(name: &str, ty: &str, required: bool, tag: impl Into) -> Value { + let tag = tag.into(); + + let mut spec = TaggedDictBuilder::new(tag); + + spec.insert_untagged("name", UntaggedValue::string(name)); + spec.insert_untagged("type", UntaggedValue::string(ty)); + spec.insert_untagged( + "required", + UntaggedValue::string(if required { "yes" } else { "no" }), + ); + + spec.into_value() +} + +pub fn signature_dict(signature: Signature, tag: impl Into) -> Value { + let tag = tag.into(); + let mut sig = TaggedListBuilder::new(&tag); + + for arg in &signature.positional { + let is_required = matches!(arg.0, PositionalType::Mandatory(_, _)); + + sig.push_value(for_spec(arg.0.name(), "argument", is_required, &tag)); + } + + if signature.rest_positional.is_some() { + let is_required = false; + sig.push_value(for_spec("rest", "argument", is_required, &tag)); + } + + for (name, ty) in &signature.named { + match ty.0 { + NamedType::Mandatory(_, _) => sig.push_value(for_spec(name, "flag", true, &tag)), + NamedType::Optional(_, _) => sig.push_value(for_spec(name, "flag", false, &tag)), + NamedType::Switch(_) => sig.push_value(for_spec(name, "switch", false, &tag)), + } + } + + sig.into_value() +} + +fn command_dict(command: Command, tag: impl Into) -> Value { + let tag = tag.into(); + + let mut cmd_dict = TaggedDictBuilder::new(&tag); + + cmd_dict.insert_untagged("name", UntaggedValue::string(command.name())); + + cmd_dict.insert_untagged("type", UntaggedValue::string("Command")); + + cmd_dict.insert_value("signature", signature_dict(command.signature(), tag)); + cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage())); + + cmd_dict.into_value() +} + +*/ diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index a16b748af..384ed11e8 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -3,6 +3,7 @@ mod def; mod do_; mod export_def; mod hide; +mod help; mod if_; mod let_; mod module; @@ -13,6 +14,7 @@ pub use def::Def; pub use do_::Do; pub use export_def::ExportDef; pub use hide::Hide; +pub use help::Help; pub use if_::If; pub use let_::Let; pub use module::Module; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a2c888e26..6d6b474d0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -2,14 +2,10 @@ use std::{cell::RefCell, rc::Rc}; use nu_protocol::{ engine::{EngineState, StateWorkingSet}, - Signature, SyntaxShape, + Signature, }; -use crate::{ - Alias, Benchmark, BuildString, Def, Do, Each, ExportDef, External, For, From, FromJson, Git, - GitCheckout, Hide, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, - Use, Where, -}; +use crate::*; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -17,10 +13,6 @@ pub fn create_default_context() -> Rc> { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let sig = - Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.predeclare()); - working_set.add_decl(Box::new(Alias)); working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); @@ -32,6 +24,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(For)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(FromJson)); + working_set.add_decl(Box::new(Get)); + working_set.add_decl(Box::new(Help)); working_set.add_decl(Box::new(Hide)); working_set.add_decl(Box::new(If)); working_set.add_decl(Box::new(Length)); @@ -40,10 +34,13 @@ 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(Ps)); + working_set.add_decl(Box::new(Select)); working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Wrap)); // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index c8b00a8fb..2e243e05f 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,8 +63,8 @@ impl Command for Ls { } else { Value::Nothing { span: call_span } }, - Value::Int { - val: filesize as i64, + Value::Filesize { + val: filesize, span: call_span, }, ], diff --git a/crates/nu-command/src/filters/for_.rs b/crates/nu-command/src/filters/for_.rs index 158cd6b6c..a3dfa5652 100644 --- a/crates/nu-command/src/filters/for_.rs +++ b/crates/nu-command/src/filters/for_.rs @@ -1,7 +1,7 @@ use nu_engine::{eval_block, eval_expression}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; +use nu_protocol::{Example, IntoValueStream, Signature, Span, SyntaxShape, Value}; pub struct For; @@ -91,4 +91,42 @@ impl Command for For { _ => Ok(Value::nothing()), } } + + fn examples(&self) -> Vec { + let span = Span::unknown(); + vec![ + Example { + description: "Echo the square of each integer", + example: "for x in [1 2 3] { $x * $x }", + result: Some(vec![ + Value::Int { val: 1, span }, + Value::Int { val: 4, span }, + Value::Int { val: 9, span }, + ]), + }, + Example { + description: "Work with elements of a range", + example: "for $x in 1..3 { $x }", + result: Some(vec![ + Value::Int { val: 1, span }, + Value::Int { val: 2, span }, + Value::Int { val: 3, span }, + ]), + }, + Example { + description: "Number each item and echo a message", + example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }", + result: Some(vec![ + Value::String { + val: "0 is bob".into(), + span, + }, + Value::String { + val: "0 is fred".into(), + span, + }, + ]), + }, + ] + } } diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs new file mode 100644 index 000000000..c06badbb2 --- /dev/null +++ b/crates/nu-command/src/filters/get.rs @@ -0,0 +1,35 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Get; + +impl Command for Get { + fn name(&self) -> &str { + "get" + } + + fn usage(&self) -> &str { + "Extract data using a cell path." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required( + "cell_path", + SyntaxShape::CellPath, + "the cell path to the data", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let cell_path: CellPath = call.req(context, 0)?; + + input.follow_cell_path(&cell_path.members) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 143a57b65..c90572156 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,11 +1,17 @@ mod each; mod for_; +mod get; mod length; mod lines; +mod select; mod where_; +mod wrap; pub use each::Each; pub use for_::For; +pub use get::Get; pub use length::Length; pub use lines::Lines; +pub use select::Select; pub use where_::Where; +pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs new file mode 100644 index 000000000..8611517e4 --- /dev/null +++ b/crates/nu-command/src/filters/select.rs @@ -0,0 +1,182 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Example, IntoValueStream, ShellError, Signature, Span, SyntaxShape, Value}; + +pub struct Select; + +impl Command for Select { + fn name(&self) -> &str { + "select" + } + + fn signature(&self) -> Signature { + Signature::build("select").rest( + "rest", + SyntaxShape::CellPath, + "the columns to select from the table", + ) + } + + fn usage(&self) -> &str { + "Down-select table to only these columns." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let columns: Vec = call.rest(context, 0)?; + let span = call.head; + + select(span, columns, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Select just the name column", + example: "ls | select name", + result: None, + }, + Example { + description: "Select the name and size columns", + example: "ls | select name size", + result: None, + }, + ] + } +} + +fn select(span: Span, columns: Vec, input: Value) -> Result { + if columns.is_empty() { + return Err(ShellError::CantFindColumn(span)); + } + + match input { + Value::List { + vals: input_vals, + span, + } => { + let mut output = vec![]; + + for input_val in input_vals { + let mut cols = vec![]; + let mut vals = vec![]; + for path in &columns { + //FIXME: improve implementation to not clone + let fetcher = input_val.clone().follow_cell_path(&path.members)?; + + cols.push(path.into_string()); + vals.push(fetcher); + } + + output.push(Value::Record { cols, vals, span }) + } + + Ok(Value::List { vals: output, span }) + } + Value::Stream { stream, span } => Ok(Value::Stream { + stream: stream + .map(move |x| { + let mut cols = vec![]; + let mut vals = vec![]; + for path in &columns { + //FIXME: improve implementation to not clone + match x.clone().follow_cell_path(&path.members) { + Ok(value) => { + cols.push(path.into_string()); + vals.push(value); + } + Err(error) => { + cols.push(path.into_string()); + vals.push(Value::Error { error }); + } + } + } + + Value::Record { cols, vals, span } + }) + .into_value_stream(), + span, + }), + v => { + let mut cols = vec![]; + let mut vals = vec![]; + + for cell_path in columns { + // FIXME: remove clone + let result = v.clone().follow_cell_path(&cell_path.members)?; + + cols.push(cell_path.into_string()); + vals.push(result); + } + + Ok(Value::Record { cols, vals, span }) + } + } +} + +// #[cfg(test)] +// mod tests { +// use nu_protocol::ColumnPath; +// use nu_source::Span; +// use nu_source::SpannedItem; +// use nu_source::Tag; +// use nu_stream::InputStream; +// use nu_test_support::value::nothing; +// use nu_test_support::value::row; +// use nu_test_support::value::string; + +// use super::select; +// use super::Command; +// use super::ShellError; + +// #[test] +// fn examples_work_as_expected() -> Result<(), ShellError> { +// use crate::examples::test as test_examples; + +// test_examples(Command {}) +// } + +// #[test] +// fn select_using_sparse_table() { +// // Create a sparse table with 3 rows: +// // col_foo | col_bar +// // ----------------- +// // foo | +// // | bar +// // foo | +// let input = vec![ +// row(indexmap! {"col_foo".into() => string("foo")}), +// row(indexmap! {"col_bar".into() => string("bar")}), +// row(indexmap! {"col_foo".into() => string("foo")}), +// ]; + +// let expected = vec![ +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, +// ), +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => nothing(), "col_bar".into() => string("bar")}, +// ), +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, +// ), +// ]; + +// let actual = select( +// Tag::unknown(), +// vec![ +// ColumnPath::build(&"col_none".to_string().spanned(Span::unknown())), +// ColumnPath::build(&"col_foo".to_string().spanned(Span::unknown())), +// ColumnPath::build(&"col_bar".to_string().spanned(Span::unknown())), +// ], +// input.into(), +// ); + +// assert_eq!(Ok(expected), actual.map(InputStream::into_vec)); +// } +// } diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs new file mode 100644 index 000000000..f23624ad2 --- /dev/null +++ b/crates/nu-command/src/filters/wrap.rs @@ -0,0 +1,59 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct Wrap; + +impl Command for Wrap { + fn name(&self) -> &str { + "wrap" + } + + fn usage(&self) -> &str { + "Wrap the value into a column." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required("name", SyntaxShape::String, "the name of the column") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let span = call.head; + let name: String = call.req(context, 0)?; + + match input { + Value::List { vals, .. } => Ok(Value::List { + vals: vals + .into_iter() + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .collect(), + span, + }), + Value::Stream { stream, .. } => Ok(Value::Stream { + stream: stream + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .into_value_stream(), + span, + }), + _ => Ok(Value::Record { + cols: vec![name], + vals: vec![input], + span, + }), + } + } +} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index a37368d52..a92122597 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,7 +1,9 @@ mod benchmark; +mod ps; mod run_external; mod sys; pub use benchmark::Benchmark; +pub use ps::Ps; pub use run_external::{External, ExternalCommand}; pub use sys::Sys; diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs new file mode 100644 index 000000000..b51310a38 --- /dev/null +++ b/crates/nu-command/src/system/ps.rs @@ -0,0 +1,128 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Example, ShellError, Signature, Value, +}; +use sysinfo::{ProcessExt, System, SystemExt}; + +pub struct Ps; + +impl Command for Ps { + fn name(&self) -> &str { + "ps" + } + + fn signature(&self) -> Signature { + Signature::build("ps") + .desc("View information about system processes.") + .switch( + "long", + "list all available columns for each entry", + Some('l'), + ) + .filter() + } + + fn usage(&self) -> &str { + "View information about system processes." + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + run_ps(call) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "List the system processes", + example: "ps", + result: None, + }] + } +} + +fn run_ps(call: &Call) -> Result { + let span = call.head; + let long = call.has_flag("long"); + let mut sys = System::new_all(); + sys.refresh_all(); + + let mut output = vec![]; + + let result: Vec<_> = sys.processes().iter().map(|x| *x.0).collect(); + + for pid in result { + if let Some(result) = sys.process(pid) { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("pid".into()); + vals.push(Value::Int { + val: pid as i64, + span, + }); + + cols.push("name".into()); + vals.push(Value::String { + val: result.name().into(), + span, + }); + + cols.push("status".into()); + vals.push(Value::String { + val: format!("{:?}", result.status()), + span, + }); + + cols.push("cpu".into()); + vals.push(Value::Float { + val: result.cpu_usage() as f64, + span, + }); + + cols.push("mem".into()); + vals.push(Value::Filesize { + val: result.memory() * 1000, + span, + }); + + cols.push("virtual".into()); + vals.push(Value::Filesize { + val: result.virtual_memory() * 1000, + span, + }); + + if long { + cols.push("parent".into()); + if let Some(parent) = result.parent() { + vals.push(Value::Int { + val: parent as i64, + span, + }); + } else { + vals.push(Value::Nothing { span }); + } + + cols.push("exe".into()); + vals.push(Value::String { + val: result.exe().to_string_lossy().to_string(), + span, + }); + + cols.push("command".into()); + vals.push(Value::String { + val: result.cmd().join(" "), + span, + }); + } + + output.push(Value::Record { cols, vals, span }); + } + } + + Ok(Value::List { vals: output, span }) +} diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs index 5a965700c..c3bf8591d 100644 --- a/crates/nu-command/src/system/sys.rs +++ b/crates/nu-command/src/system/sys.rs @@ -1,7 +1,7 @@ use nu_protocol::{ ast::Call, engine::{Command, EvaluationContext}, - ShellError, Signature, Span, Value, + Example, ShellError, Signature, Span, Value, }; use sysinfo::{ComponentExt, DiskExt, NetworkExt, ProcessorExt, System, SystemExt, UserExt}; @@ -31,13 +31,13 @@ impl Command for Sys { run_sys(call) } - // fn examples(&self) -> Vec { - // vec![Example { - // description: "Show info about the system", - // example: "sys", - // result: None, - // }] - // } + fn examples(&self) -> Vec { + vec![Example { + description: "Show info about the system", + example: "sys", + result: None, + }] + } } fn run_sys(call: &Call) -> Result { @@ -274,10 +274,11 @@ pub fn host(sys: &mut System, span: Span) -> Option { span, }); } - // dict.insert_untagged( - // "uptime", - // UntaggedValue::duration(1000000000 * sys.uptime() as i64), - // ); + cols.push("uptime".into()); + vals.push(Value::Duration { + val: 1000000000 * sys.uptime() as u64, + span, + }); let mut users = vec![]; for user in sys.users() { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2a43bcee2..e3b64e34e 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -63,7 +63,7 @@ impl Command for Table { output.push(vec![ StyledString { contents: c, - style: nu_table::TextStyle::default_header(), + style: nu_table::TextStyle::default_field(), }, StyledString { contents: v.into_string(), diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 3fbdb0650..6bb588bc0 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,4 +5,5 @@ edition = "2018" [dependencies] nu-parser = { path = "../nu-parser" } -nu-protocol = { path = "../nu-protocol" } \ No newline at end of file +nu-protocol = { path = "../nu-protocol" } +nu-path = { path = "../nu-path" } \ No newline at end of file diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs new file mode 100644 index 000000000..9e311600a --- /dev/null +++ b/crates/nu-engine/src/call_ext.rs @@ -0,0 +1,80 @@ +use nu_protocol::{ast::Call, engine::EvaluationContext, ShellError}; + +use crate::{eval_expression, FromValue}; + +pub trait CallExt { + fn get_flag( + &self, + context: &EvaluationContext, + name: &str, + ) -> Result, ShellError>; + + fn rest( + &self, + context: &EvaluationContext, + starting_pos: usize, + ) -> Result, ShellError>; + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError>; + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result; +} + +impl CallExt for Call { + fn get_flag( + &self, + context: &EvaluationContext, + name: &str, + ) -> Result, ShellError> { + if let Some(expr) = self.get_flag_expr(name) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result).map(Some) + } else { + Ok(None) + } + } + + fn rest( + &self, + context: &EvaluationContext, + starting_pos: usize, + ) -> Result, ShellError> { + let mut output = vec![]; + + for expr in self.positional.iter().skip(starting_pos) { + let result = eval_expression(context, expr)?; + output.push(FromValue::from_value(&result)?); + } + + Ok(output) + } + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError> { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result).map(Some) + } else { + Ok(None) + } + } + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result) + } else { + Err(ShellError::AccessBeyondEnd( + self.positional.len(), + self.head, + )) + } + } +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 2cfe44b37..04057e540 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -156,6 +156,10 @@ pub fn eval_expression( Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), + Expr::CellPath(cell_path) => Ok(Value::CellPath { + val: cell_path.clone(), + span: expr.span, + }), Expr::FullCellPath(cell_path) => { let value = eval_expression(context, &cell_path.head)?; diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs new file mode 100644 index 000000000..b356bbf6f --- /dev/null +++ b/crates/nu-engine/src/from_value.rs @@ -0,0 +1,270 @@ +// use std::path::PathBuf; + +// use nu_path::expand_path; +use nu_protocol::ast::{CellPath, PathMember}; +use nu_protocol::ShellError; +use nu_protocol::{Range, Spanned, Value}; + +pub trait FromValue: Sized { + fn from_value(v: &Value) -> Result; +} + +impl FromValue for Value { + fn from_value(v: &Value) -> Result { + Ok(v.clone()) + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + match v { + Value::Int { val, span } => Ok(Spanned { + item: *val, + span: *span, + }), + Value::Filesize { val, span } => Ok(Spanned { + // FIXME: error check that this fits + item: *val as i64, + span: *span, + }), + Value::Duration { val, span } => Ok(Spanned { + // FIXME: error check that this fits + item: *val as i64, + span: *span, + }), + + v => Err(ShellError::CantConvert("integer".into(), v.span())), + } + } +} + +impl FromValue for i64 { + fn from_value(v: &Value) -> Result { + match v { + Value::Int { val, .. } => Ok(*val), + Value::Filesize { val, .. } => Ok( + // FIXME: error check that this fits + *val as i64, + ), + Value::Duration { val, .. } => Ok( + // FIXME: error check that this fits + *val as i64, + ), + + v => Err(ShellError::CantConvert("integer".into(), v.span())), + } + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + match v { + Value::Int { val, span } => Ok(Spanned { + item: *val as f64, + span: *span, + }), + Value::Float { val, span } => Ok(Spanned { + // FIXME: error check that this fits + item: *val, + span: *span, + }), + + v => Err(ShellError::CantConvert("float".into(), v.span())), + } + } +} + +impl FromValue for f64 { + fn from_value(v: &Value) -> Result { + match v { + Value::Float { val, .. } => Ok(*val), + Value::Int { val, .. } => Ok(*val as f64), + v => Err(ShellError::CantConvert("float".into(), v.span())), + } + } +} + +impl FromValue for String { + fn from_value(v: &Value) -> Result { + // FIXME: we may want to fail a little nicer here + Ok(v.clone().into_string()) + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + Ok(Spanned { + item: v.clone().into_string(), + span: v.span(), + }) + } +} + +//FIXME +/* +impl FromValue for ColumnPath { + fn from_value(v: &Value) -> Result { + match v { + Value:: => Ok(c.clone()), + v => Err(ShellError::type_error("column path", v.spanned_type_name())), + } + } +} + +*/ + +impl FromValue for CellPath { + fn from_value(v: &Value) -> Result { + let span = v.span(); + match v { + Value::CellPath { val, .. } => Ok(val.clone()), + Value::String { val, .. } => Ok(CellPath { + members: vec![PathMember::String { + val: val.clone(), + span, + }], + }), + v => Err(ShellError::CantConvert("cell path".into(), v.span())), + } + } +} + +impl FromValue for bool { + fn from_value(v: &Value) -> Result { + match v { + Value::Bool { val, .. } => Ok(*val), + v => Err(ShellError::CantConvert("bool".into(), v.span())), + } + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + match v { + Value::Bool { val, span } => Ok(Spanned { + item: *val, + span: *span, + }), + v => Err(ShellError::CantConvert("bool".into(), v.span())), + } + } +} + +// impl FromValue for DateTime { +// fn from_value(v: &Value) -> Result { +// match v { +// Value { +// value: UntaggedValue::Primitive(Primitive::Date(d)), +// .. +// } => Ok(*d), +// Value { +// value: UntaggedValue::Row(_), +// .. +// } => { +// let mut shell_error = ShellError::type_error("date", v.spanned_type_name()); +// shell_error.notes.push( +// "Note: you can access columns using dot. eg) $it.column or (ls).column".into(), +// ); +// Err(shell_error) +// } +// v => Err(ShellError::type_error("date", v.spanned_type_name())), +// } +// } +// } + +impl FromValue for Range { + fn from_value(v: &Value) -> Result { + match v { + Value::Range { val, .. } => Ok((**val).clone()), + v => Err(ShellError::CantConvert("range".into(), v.span())), + } + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + match v { + Value::Range { val, span } => Ok(Spanned { + item: (**val).clone(), + span: *span, + }), + v => Err(ShellError::CantConvert("range".into(), v.span())), + } + } +} + +// impl FromValue for Vec { +// fn from_value(v: &Value) -> Result { +// match v { +// Value { +// value: UntaggedValue::Primitive(Primitive::Binary(b)), +// .. +// } => Ok(b.clone()), +// Value { +// value: UntaggedValue::Primitive(Primitive::String(s)), +// .. +// } => Ok(s.bytes().collect()), +// Value { +// value: UntaggedValue::Row(_), +// .. +// } => { +// let mut shell_error = ShellError::type_error("binary data", v.spanned_type_name()); +// shell_error.notes.push( +// "Note: you can access columns using dot. eg) $it.column or (ls).column".into(), +// ); +// Err(shell_error) +// } +// v => Err(ShellError::type_error("binary data", v.spanned_type_name())), +// } +// } +// } + +// impl FromValue for Dictionary { +// fn from_value(v: &Value) -> Result { +// match v { +// Value { +// value: UntaggedValue::Row(r), +// .. +// } => Ok(r.clone()), +// v => Err(ShellError::type_error("row", v.spanned_type_name())), +// } +// } +// } + +// impl FromValue for CapturedBlock { +// fn from_value(v: &Value) -> Result { +// match v { +// Value { +// value: UntaggedValue::Block(b), +// .. +// } => Ok((**b).clone()), +// Value { +// value: UntaggedValue::Row(_), +// .. +// } => { +// let mut shell_error = ShellError::type_error("block", v.spanned_type_name()); +// shell_error.notes.push( +// "Note: you can access columns using dot. eg) $it.column or (ls).column".into(), +// ); +// Err(shell_error) +// } +// v => Err(ShellError::type_error("block", v.spanned_type_name())), +// } +// } +// } + +// impl FromValue for Vec { +// fn from_value(v: &Value) -> Result { +// match v { +// Value { +// value: UntaggedValue::Table(t), +// .. +// } => Ok(t.clone()), +// Value { +// value: UntaggedValue::Row(_), +// .. +// } => Ok(vec![v.clone()]), +// v => Err(ShellError::type_error("table", v.spanned_type_name())), +// } +// } +// } diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index c912ee477..2cbbbee63 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,3 +1,7 @@ +mod call_ext; mod eval; +mod from_value; +pub use call_ext::CallExt; pub use eval::{eval_block, eval_expression, eval_operator}; +pub use from_value::FromValue; diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 7b94d007c..fffb5626e 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -79,6 +79,16 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::CellPath(cell_path) => { + let mut output = vec![]; + for path_element in &cell_path.members { + match path_element { + PathMember::String { span, .. } => output.push((*span, FlatShape::String)), + PathMember::Int { span, .. } => output.push((*span, FlatShape::Int)), + } + } + output + } Expr::FullCellPath(cell_path) => { let mut output = vec![]; output.extend(flatten_expression(working_set, &cell_path.head)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 79213fdf7..fa9703a73 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -6,8 +6,8 @@ use crate::{ use nu_protocol::{ ast::{ - Block, Call, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, Operator, - PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, + Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, + Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, @@ -1157,6 +1157,62 @@ pub fn parse_variable_expr( } } +pub fn parse_cell_path( + working_set: &mut StateWorkingSet, + tokens: impl Iterator, + mut expect_dot: bool, + span: Span, +) -> (Vec, Option) { + let mut error = None; + let mut tail = vec![]; + + for path_element in tokens { + let bytes = working_set.get_span_contents(path_element.span); + + if expect_dot { + expect_dot = false; + if bytes.len() != 1 || bytes[0] != b'.' { + error = error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); + } + } else { + expect_dot = true; + + match parse_int(bytes, path_element.span) { + ( + Expression { + expr: Expr::Int(val), + span, + .. + }, + None, + ) => tail.push(PathMember::Int { + val: val as usize, + span, + }), + _ => { + let (result, err) = parse_string(working_set, path_element.span); + error = error.or(err); + match result { + Expression { + expr: Expr::String(string), + span, + .. + } => { + tail.push(PathMember::String { val: string, span }); + } + _ => { + error = + error.or_else(|| Some(ParseError::Expected("string".into(), span))); + } + } + } + } + } + } + + (tail, error) +} + pub fn parse_full_cell_path( working_set: &mut StateWorkingSet, implicit_head: Option, @@ -1173,7 +1229,7 @@ pub fn parse_full_cell_path( let mut tokens = tokens.into_iter().peekable(); if let Some(head) = tokens.peek() { let bytes = working_set.get_span_contents(head.span); - let (head, mut expect_dot) = if bytes.starts_with(b"(") { + let (head, expect_dot) = if bytes.starts_with(b"(") { let mut start = head.span.start; let mut end = head.span.end; @@ -1247,52 +1303,8 @@ pub fn parse_full_cell_path( ); }; - let mut tail = vec![]; - - for path_element in tokens { - let bytes = working_set.get_span_contents(path_element.span); - - if expect_dot { - expect_dot = false; - if bytes.len() != 1 || bytes[0] != b'.' { - error = - error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); - } - } else { - expect_dot = true; - - match parse_int(bytes, path_element.span) { - ( - Expression { - expr: Expr::Int(val), - span, - .. - }, - None, - ) => tail.push(PathMember::Int { - val: val as usize, - span, - }), - _ => { - let (result, err) = parse_string(working_set, path_element.span); - error = error.or(err); - match result { - Expression { - expr: Expr::String(string), - span, - .. - } => { - tail.push(PathMember::String { val: string, span }); - } - _ => { - error = error - .or_else(|| Some(ParseError::Expected("string".into(), span))); - } - } - } - } - } - } + let (tail, err) = parse_cell_path(working_set, tokens, expect_dot, span); + error = error.or(err); ( Expression { @@ -2351,6 +2363,28 @@ pub fn parse_value( ) } } + SyntaxShape::CellPath => { + let source = working_set.get_span_contents(span); + let mut error = None; + + let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']); + error = error.or(err); + + let tokens = tokens.into_iter().peekable(); + + let (cell_path, err) = parse_cell_path(working_set, tokens, false, span); + error = error.or(err); + + ( + Expression { + expr: Expr::CellPath(CellPath { members: cell_path }), + span, + ty: Type::CellPath, + custom_completion: None, + }, + error, + ) + } SyntaxShape::Any => { if bytes.starts_with(b"[") { parse_value(working_set, span, &SyntaxShape::Table) diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 7ee6d6c16..1f2a4870e 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -35,4 +35,18 @@ impl Call { false } + + pub fn get_flag_expr(&self, flag_name: &str) -> Option { + for name in &self.named { + if flag_name == name.0 { + return name.1.clone(); + } + } + + None + } + + pub fn nth(&self, pos: usize) -> Option { + self.positional.get(pos).cloned() + } } diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 26cefd855..078b64919 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -1,17 +1,36 @@ use super::Expression; use crate::Span; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum PathMember { String { val: String, span: Span }, Int { val: usize, span: Span }, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CellPath { pub members: Vec, } +impl CellPath { + pub fn into_string(&self) -> String { + let mut output = String::new(); + + for (idx, elem) in self.members.iter().enumerate() { + if idx > 0 { + output.push('.'); + } + match elem { + PathMember::Int { val, .. } => output.push_str(&format!("{}", val)), + PathMember::String { val, .. } => output.push_str(val), + } + } + + output + } +} + #[derive(Debug, Clone)] pub struct FullCellPath { pub head: Expression, diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 62d7a25c8..449f4687d 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,4 @@ -use super::{Call, Expression, FullCellPath, Operator, RangeOperator}; +use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator}; use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] @@ -24,6 +24,7 @@ pub enum Expr { Table(Vec, Vec>), Keyword(Vec, Span, Box), String(String), // FIXME: improve this in the future? + CellPath(CellPath), FullCellPath(Box), Signature(Box), Garbage, diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index ea75692eb..bbe6bdb1c 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,5 +1,5 @@ use super::Command; -use crate::{ast::Block, BlockId, DeclId, Span, Type, VarId}; +use crate::{ast::Block, BlockId, DeclId, Signature, Span, Type, VarId}; use core::panic; use std::{ collections::{HashMap, HashSet}, @@ -178,6 +178,21 @@ impl EngineState { .expect("internal error: missing declaration") } + pub fn get_decls(&self) -> Vec { + let mut output = vec![]; + for decl in self.decls.iter() { + if decl.get_block_id().is_none() { + let mut signature = (*decl).signature(); + signature.usage = decl.usage().to_string(); + signature.extra_usage = decl.extra_usage().to_string(); + + output.push(signature); + } + } + + output + } + pub fn get_block(&self, block_id: BlockId) -> &Block { self.blocks .get(block_id) diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 3dd90d94d..9a0c1c685 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -1,7 +1,7 @@ use super::EngineState; use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use crate::{ShellError, Value, VarId}; +use crate::{ShellError, Signature, Value, VarId}; #[derive(Clone)] pub struct EvaluationContext { @@ -46,6 +46,10 @@ impl EvaluationContext { pub fn print_stack(&self) { self.stack.print_stack(); } + + pub fn get_commands_info(&self) -> Vec { + self.engine_state.borrow().get_decls() + } } #[derive(Debug)] diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 90c6b082c..8307e9961 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -74,4 +74,8 @@ pub enum ShellError { #[error("Unsupported input")] #[diagnostic(code(nu::shell::unsupported_input), url(docsrs))] UnsupportedInput(String, #[label("{0}")] Span), + + #[error("Flag not found")] + #[diagnostic(code(nu::shell::flag_not_found), url(docsrs))] + FlagNotFound(String, #[label("{0} not found")] Span), } diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index e3a190a8c..1dd0b3898 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -1,6 +1,11 @@ use miette::SourceSpan; use serde::{Deserialize, Serialize}; +pub struct Spanned { + pub item: T, + pub span: Span, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Span { pub start: usize, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index f91cd65ec..eae64b8dc 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -9,7 +9,7 @@ pub use stream::*; use std::fmt::Debug; -use crate::ast::PathMember; +use crate::ast::{CellPath, PathMember}; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -29,6 +29,10 @@ pub enum Value { val: u64, span: Span, }, + Duration { + val: u64, + span: Span, + }, Range { val: Box, span: Span, @@ -68,6 +72,10 @@ pub enum Value { val: Vec, span: Span, }, + CellPath { + val: CellPath, + span: Span, + }, } impl Value { @@ -86,6 +94,7 @@ impl Value { Value::Int { span, .. } => *span, Value::Float { span, .. } => *span, Value::Filesize { span, .. } => *span, + Value::Duration { span, .. } => *span, Value::Range { span, .. } => *span, Value::String { span, .. } => *span, Value::Record { span, .. } => *span, @@ -94,6 +103,7 @@ impl Value { Value::Stream { span, .. } => *span, Value::Nothing { span, .. } => *span, Value::Binary { span, .. } => *span, + Value::CellPath { span, .. } => *span, } } @@ -104,6 +114,7 @@ impl Value { Value::Int { span, .. } => *span = new_span, Value::Float { span, .. } => *span = new_span, Value::Filesize { span, .. } => *span = new_span, + Value::Duration { span, .. } => *span = new_span, Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::Record { span, .. } => *span = new_span, @@ -113,6 +124,7 @@ impl Value { Value::Nothing { span, .. } => *span = new_span, Value::Error { .. } => {} Value::Binary { span, .. } => *span = new_span, + Value::CellPath { span, .. } => *span = new_span, } self @@ -125,6 +137,7 @@ impl Value { Value::Int { .. } => Type::Int, Value::Float { .. } => Type::Float, Value::Filesize { .. } => Type::Filesize, + Value::Duration { .. } => Type::Duration, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, Value::Record { cols, vals, .. } => { @@ -136,6 +149,7 @@ impl Value { Value::Stream { .. } => Type::ValueStream, Value::Error { .. } => Type::Error, Value::Binary { .. } => Type::Binary, + Value::CellPath { .. } => Type::CellPath, } } @@ -146,6 +160,7 @@ impl Value { 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::Range { val, .. } => { format!( "range: [{}]", @@ -176,6 +191,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => val.into_string(), } } @@ -185,6 +201,7 @@ impl Value { 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::Range { val, .. } => val .into_iter() .map(|x| x.into_string()) @@ -206,6 +223,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { .. } => self.into_string(), } } diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 3110e2751..0d731a6a1 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -240,6 +240,10 @@ impl TextStyle { .bold(Some(true)) } + pub fn default_field() -> TextStyle { + TextStyle::new().fg(Color::Green).bold(Some(true)) + } + pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle { TextStyle::new().alignment(al).fg(co).bold(Some(bo)) } diff --git a/src/main.rs b/src/main.rs index 577a9c721..c10bf2c80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,11 @@ mod tests; fn main() -> Result<()> { miette::set_panic_hook(); + let miette_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |x| { + crossterm::terminal::disable_raw_mode().unwrap(); + miette_hook(x); + })); let engine_state = create_default_context(); diff --git a/src/tests.rs b/src/tests.rs index 056cac1a9..844df53c3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -468,3 +468,32 @@ fn from_json_2() -> TestResult { "Sally", ) } + +#[test] +fn wrap() -> TestResult { + run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2") +} + +#[test] +fn get() -> TestResult { + run_test( + r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#, + "B", + ) +} + +#[test] +fn select() -> TestResult { + run_test( + r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#, + "b", + ) +} + +#[test] +fn string_cell_path() -> TestResult { + run_test( + r#"let x = "name"; [["name", "score"]; [a, b], [c, d]] | get $x | get 1"#, + "c", + ) +}