From 008bdfa43fb97fcc3be97b18adaa086fd0dc73e5 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 29 Jun 2021 09:27:16 -0500 Subject: [PATCH] a new command to query the nushell internals (#3704) * a new command to query the nushell internals * added signature * a little cleanup --- Cargo.lock | 2 +- .../src/commands/core_commands/help.rs | 4 +- .../nu-command/src/commands/platform/lang.rs | 229 ++++++++++++++++++ .../nu-command/src/commands/platform/mod.rs | 2 + .../src/commands/platform/run_external.rs | 2 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-engine/src/evaluate/mod.rs | 2 +- crates/nu-engine/src/evaluate/scope.rs | 14 ++ crates/nu-engine/src/lib.rs | 2 +- crates/nu-engine/src/plugin/run_plugin.rs | 24 ++ crates/nu-engine/src/whole_stream_command.rs | 61 ++++- crates/nu-protocol/src/signature.rs | 36 +++ crates/nu-protocol/src/syntax_shape.rs | 16 +- 13 files changed, 379 insertions(+), 16 deletions(-) create mode 100644 crates/nu-command/src/commands/platform/lang.rs diff --git a/Cargo.lock b/Cargo.lock index 8101c3a433..f6720fe56b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,7 +4438,7 @@ dependencies = [ "lz4", "num-bigint 0.4.0", "parquet-format", - "rand 0.8.3", + "rand 0.8.4", "snap", "thrift", "zstd", diff --git a/crates/nu-command/src/commands/core_commands/help.rs b/crates/nu-command/src/commands/core_commands/help.rs index 6f7d5163f6..b837e90d4c 100644 --- a/crates/nu-command/src/commands/core_commands/help.rs +++ b/crates/nu-command/src/commands/core_commands/help.rs @@ -44,11 +44,11 @@ fn help(args: CommandArgs) -> Result { let (mut subcommand_names, command_names) = sorted_names .into_iter() - // Internal only commands shouldn't be displayed + // private only commands shouldn't be displayed .filter(|cmd_name| { scope .get_command(cmd_name) - .filter(|command| !command.is_internal()) + .filter(|command| !command.is_private()) .is_some() }) .partition::, _>(|cmd_name| cmd_name.contains(' ')); diff --git a/crates/nu-command/src/commands/platform/lang.rs b/crates/nu-command/src/commands/platform/lang.rs new file mode 100644 index 0000000000..be33b4e9c2 --- /dev/null +++ b/crates/nu-command/src/commands/platform/lang.rs @@ -0,0 +1,229 @@ +use crate::prelude::*; +use indexmap::IndexMap; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Dictionary, Signature, UntaggedValue, Value}; + +pub struct Lang; + +impl WholeStreamCommand for Lang { + fn name(&self) -> &str { + "lang" + } + + fn signature(&self) -> Signature { + Signature::build("lang") + } + + fn usage(&self) -> &str { + "Returns the nushell-lang information" + } + + fn run(&self, args: CommandArgs) -> Result { + let tag = args.call_info.name_tag.clone(); + let full_commands = args.context.scope.get_commands_info(); + let mut cmd_vec_deque = VecDeque::new(); + for (key, cmd) in full_commands { + let mut indexmap = IndexMap::new(); + let mut sig = cmd.signature(); + // eprintln!("{}", get_signature(&sig)); + indexmap.insert( + "name".to_string(), + UntaggedValue::string(key).into_value(&tag), + ); + indexmap.insert( + "usage".to_string(), + UntaggedValue::string(cmd.usage().to_string()).into_value(&tag), + ); + // let sig_deser = serde_json::to_string(&sig).unwrap(); + // indexmap.insert( + // "signature".to_string(), + // UntaggedValue::string(sig_deser).into_value(&tag), + // ); + let signature_table = get_signature(&mut sig, tag.clone()); + indexmap.insert( + "signature".to_string(), + UntaggedValue::Table(signature_table).into_value(&tag), + ); + indexmap.insert( + "is_filter".to_string(), + UntaggedValue::boolean(sig.is_filter).into_value(&tag), + ); + indexmap.insert( + "is_builtin".to_string(), + UntaggedValue::boolean(cmd.is_builtin()).into_value(&tag), + ); + indexmap.insert( + "is_sub".to_string(), + UntaggedValue::boolean(cmd.is_sub()).into_value(&tag), + ); + indexmap.insert( + "is_plugin".to_string(), + UntaggedValue::boolean(cmd.is_plugin()).into_value(&tag), + ); + indexmap.insert( + "is_custom".to_string(), + UntaggedValue::boolean(cmd.is_custom()).into_value(&tag), + ); + indexmap.insert( + "is_private".to_string(), + UntaggedValue::boolean(cmd.is_private()).into_value(&tag), + ); + indexmap.insert( + "is_binary".to_string(), + UntaggedValue::boolean(cmd.is_binary()).into_value(&tag), + ); + indexmap.insert( + "extra_usage".to_string(), + UntaggedValue::string(cmd.extra_usage().to_string()).into_value(&tag), + ); + + cmd_vec_deque + .push_back(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)); + } + Ok(cmd_vec_deque.into_iter().into_output_stream()) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Query command information from Nushell", + example: "lang", + result: None, + }] + } +} + +fn get_signature(sig: &mut Signature, tag: Tag) -> Vec { + sig.remove_named("help"); + let p = &sig.positional; + let r = &sig.rest_positional; + let n = &sig.named; + let name = &sig.name; + let mut sig_vec: Vec = Vec::new(); + + for item in p { + let mut indexmap = IndexMap::new(); + + let (parameter, syntax_shape) = item.0.get_type_description(); + let description = &item.1; + // let output = format!( + // "Positional|{}|{}|{}|{}\n", + // name, parameter, syntax_shape, description + // ); + // eprintln!("{}", output); + + indexmap.insert( + "cmd_name".to_string(), + UntaggedValue::string(name).into_value(&tag), + ); + indexmap.insert( + "parameter_name".to_string(), + UntaggedValue::string(parameter).into_value(&tag), + ); + indexmap.insert( + "parameter_type".to_string(), + UntaggedValue::string("positional".to_string()).into_value(&tag), + ); + indexmap.insert( + "syntax_shape".to_string(), + UntaggedValue::string(syntax_shape).into_value(&tag), + ); + indexmap.insert( + "description".to_string(), + UntaggedValue::string(description).into_value(&tag), + ); + indexmap.insert( + "flag_name".to_string(), + UntaggedValue::string("".to_string()).into_value(&tag), + ); + indexmap.insert( + "flag_type".to_string(), + UntaggedValue::string("".to_string()).into_value(&tag), + ); + + sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)); + } + + match r { + Some((shape, desc)) => { + let mut indexmap = IndexMap::new(); + // let output = format!("Rest|{}|{}|{}\n", name, shape.syntax_shape_name(), desc); + // eprintln!("{}", output); + + indexmap.insert( + "cmd_name".to_string(), + UntaggedValue::string(name).into_value(&tag), + ); + indexmap.insert( + "parameter_name".to_string(), + UntaggedValue::string("".to_string()).into_value(&tag), + ); + indexmap.insert( + "parameter_type".to_string(), + UntaggedValue::string("rest".to_string()).into_value(&tag), + ); + indexmap.insert( + "syntax_shape".to_string(), + UntaggedValue::string(shape.syntax_shape_name()).into_value(&tag), + ); + indexmap.insert( + "description".to_string(), + UntaggedValue::string(desc).into_value(&tag), + ); + indexmap.insert( + "flag_name".to_string(), + UntaggedValue::string("".to_string()).into_value(&tag), + ); + indexmap.insert( + "flag_type".to_string(), + UntaggedValue::string("".to_string()).into_value(&tag), + ); + + sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)); + } + None => {} + } + + for (parameter, (b, description)) in n { + let mut indexmap = IndexMap::new(); + + let (named_type, flag_name, shape) = b.get_type_description(); + // let output = format!( + // "Named|{}|{}|{}|{}|{}|{}\n", + // name, parameter, named_type, flag_name, shape, description + // ); + // eprint!("{}", output); + + indexmap.insert( + "cmd_name".to_string(), + UntaggedValue::string(name).into_value(&tag), + ); + indexmap.insert( + "parameter_name".to_string(), + UntaggedValue::string(parameter).into_value(&tag), + ); + indexmap.insert( + "parameter_type".to_string(), + UntaggedValue::string("named".to_string()).into_value(&tag), + ); + indexmap.insert( + "syntax_shape".to_string(), + UntaggedValue::string(shape).into_value(&tag), + ); + indexmap.insert( + "description".to_string(), + UntaggedValue::string(description).into_value(&tag), + ); + indexmap.insert( + "flag_name".to_string(), + UntaggedValue::string(flag_name).into_value(&tag), + ); + indexmap.insert( + "flag_type".to_string(), + UntaggedValue::string(named_type).into_value(&tag), + ); + sig_vec.push(UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)); + } + + sig_vec +} diff --git a/crates/nu-command/src/commands/platform/mod.rs b/crates/nu-command/src/commands/platform/mod.rs index 9bc0133852..62b2053b7a 100644 --- a/crates/nu-command/src/commands/platform/mod.rs +++ b/crates/nu-command/src/commands/platform/mod.rs @@ -6,6 +6,7 @@ mod clip; mod du; mod exec; mod kill; +mod lang; #[cfg(feature = "clipboard-cli")] mod paste; mod pwd; @@ -22,6 +23,7 @@ pub use clip::Clip; pub use du::Du; pub use exec::Exec; pub use kill::Kill; +pub use lang::Lang; #[cfg(feature = "clipboard-cli")] pub use paste::Paste; pub use pwd::Pwd; diff --git a/crates/nu-command/src/commands/platform/run_external.rs b/crates/nu-command/src/commands/platform/run_external.rs index 10e1b93f68..22235247c9 100644 --- a/crates/nu-command/src/commands/platform/run_external.rs +++ b/crates/nu-command/src/commands/platform/run_external.rs @@ -58,7 +58,7 @@ impl WholeStreamCommand for RunExternalCommand { }] } - fn is_internal(&self) -> bool { + fn is_private(&self) -> bool { true } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c17e1bd438..441b44f594 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -69,6 +69,7 @@ pub fn create_default_context(interactive: bool) -> Result IndexMap { + let mut output: IndexMap = IndexMap::new(); + + for frame in self.frames.lock().iter().rev() { + for (name, command) in frame.commands.iter() { + if !output.contains_key(name) { + output.insert(name.clone(), command.clone()); + } + } + } + + output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect() + } + pub fn get_variable_names(&self) -> Vec { self.get_vars().iter().map(|(k, _)| k.to_string()).collect() } diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index b84309845d..618b053fdd 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -3,7 +3,7 @@ mod command_args; mod config_holder; pub mod documentation; mod env; -mod evaluate; +pub mod evaluate; pub mod evaluation_context; mod example; pub mod filesystem; diff --git a/crates/nu-engine/src/plugin/run_plugin.rs b/crates/nu-engine/src/plugin/run_plugin.rs index e7e80da996..6790b0f542 100644 --- a/crates/nu-engine/src/plugin/run_plugin.rs +++ b/crates/nu-engine/src/plugin/run_plugin.rs @@ -111,9 +111,21 @@ impl WholeStreamCommand for PluginFilter { &self.config.usage } + fn extra_usage(&self) -> &str { + &self.config.extra_usage + } + fn run_with_actions(&self, args: CommandArgs) -> Result { run_filter(self.path.clone(), args) } + + fn is_plugin(&self) -> bool { + true + } + + fn is_builtin(&self) -> bool { + false + } } fn run_filter(path: String, args: CommandArgs) -> Result { @@ -383,9 +395,21 @@ impl WholeStreamCommand for PluginSink { &self.config.usage } + fn extra_usage(&self) -> &str { + &self.config.extra_usage + } + fn run_with_actions(&self, args: CommandArgs) -> Result { run_sink(self.path.clone(), args) } + + fn is_plugin(&self) -> bool { + true + } + + fn is_builtin(&self) -> bool { + false + } } fn run_sink(path: String, args: CommandArgs) -> Result { diff --git a/crates/nu-engine/src/whole_stream_command.rs b/crates/nu-engine/src/whole_stream_command.rs index ba22082508..fe936b6ce3 100644 --- a/crates/nu-engine/src/whole_stream_command.rs +++ b/crates/nu-engine/src/whole_stream_command.rs @@ -47,19 +47,38 @@ pub trait WholeStreamCommand: Send + Sync { } // Commands that are not meant to be run by users - fn is_internal(&self) -> bool { + fn is_private(&self) -> bool { false } fn examples(&self) -> Vec { Vec::new() } + + // This is a built-in command + fn is_builtin(&self) -> bool { + true + } + + // Is a sub command + fn is_sub(&self) -> bool { + self.name().contains(' ') + } + + // Is a plugin command + fn is_plugin(&self) -> bool { + false + } + + // Is a custom command i.e. def blah [] { } + fn is_custom(&self) -> bool { + false + } } // Custom commands are blocks, so we can use the information in the block to also // implement a WholeStreamCommand #[allow(clippy::suspicious_else_formatting)] - impl WholeStreamCommand for Arc { fn name(&self) -> &str { &self.params.name @@ -73,6 +92,10 @@ impl WholeStreamCommand for Arc { &self.params.usage } + fn extra_usage(&self) -> &str { + &self.params.extra_usage + } + fn run(&self, args: CommandArgs) -> Result { let call_info = args.call_info.clone(); @@ -184,13 +207,21 @@ impl WholeStreamCommand for Arc { false } - fn is_internal(&self) -> bool { + fn is_private(&self) -> bool { false } fn examples(&self) -> Vec { vec![] } + + fn is_custom(&self) -> bool { + true + } + + fn is_builtin(&self) -> bool { + false + } } #[derive(Clone)] @@ -228,6 +259,10 @@ impl Command { self.0.usage() } + pub fn extra_usage(&self) -> &str { + self.0.extra_usage() + } + pub fn examples(&self) -> Vec { self.0.examples() } @@ -260,13 +295,29 @@ impl Command { self.0.is_binary() } - pub fn is_internal(&self) -> bool { - self.0.is_internal() + pub fn is_private(&self) -> bool { + self.0.is_private() } pub fn stream_command(&self) -> &dyn WholeStreamCommand { &*self.0 } + + pub fn is_builtin(&self) -> bool { + self.0.is_builtin() + } + + pub fn is_sub(&self) -> bool { + self.0.is_sub() + } + + pub fn is_plugin(&self) -> bool { + self.0.is_plugin() + } + + pub fn is_custom(&self) -> bool { + self.0.is_custom() + } } pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 597d4a89d5..eb408f8c87 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -23,6 +23,32 @@ impl NamedType { NamedType::Optional(s, _) => *s, } } + + pub fn get_type_description(&self) -> (String, String, String) { + let empty_string = ("".to_string(), "".to_string(), "".to_string()); + match self { + NamedType::Switch(f) => match f { + Some(flag) => ("switch_flag".to_string(), flag.to_string(), "".to_string()), + None => empty_string, + }, + NamedType::Mandatory(f, shape) => match f { + Some(flag) => ( + "mandatory_flag".to_string(), + flag.to_string(), + shape.syntax_shape_name().to_string(), + ), + None => empty_string, + }, + NamedType::Optional(f, shape) => match f { + Some(flag) => ( + "optional_flag".to_string(), + flag.to_string(), + shape.syntax_shape_name().to_string(), + ), + None => empty_string, + }, + } + } } /// The type of positional arguments @@ -96,6 +122,13 @@ impl PositionalType { PositionalType::Optional(_, t) => t, } } + + pub fn get_type_description(&self) -> (String, String) { + match &self { + PositionalType::Mandatory(c, s) => (c.to_string(), s.syntax_shape_name().to_string()), + PositionalType::Optional(c, s) => (c.to_string(), s.syntax_shape_name().to_string()), + } + } } type Description = String; @@ -109,6 +142,8 @@ pub struct Signature { pub name: String, /// Usage instructions about the command pub usage: String, + /// Longer or more verbose usage statement + pub extra_usage: String, /// The list of positional arguments, both required and optional, and their corresponding types and help text pub positional: Vec<(PositionalType, Description)>, /// After the positional arguments, a catch-all for the rest of the arguments that might follow, their type, and help text @@ -192,6 +227,7 @@ impl Signature { Signature { name: name.into(), usage: String::new(), + extra_usage: String::new(), positional: vec![], rest_positional: None, named: indexmap::indexmap! {"help".into() => (NamedType::Switch(Some('h')), "Display this help message".into())}, diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index ab11b77fa4..0cea4011b4 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -39,10 +39,9 @@ pub enum SyntaxShape { MathExpression, } -impl PrettyDebug for SyntaxShape { - /// Prepare SyntaxShape for pretty-printing - fn pretty(&self) -> DebugDocBuilder { - DbgDocBldr::kind(match self { +impl SyntaxShape { + pub fn syntax_shape_name(&self) -> &str { + match self { SyntaxShape::Any => "any", SyntaxShape::String => "string", SyntaxShape::FullColumnPath => "column path (with variable)", @@ -59,6 +58,13 @@ impl PrettyDebug for SyntaxShape { SyntaxShape::Operator => "operator", SyntaxShape::RowCondition => "condition", SyntaxShape::MathExpression => "math expression", - }) + } + } +} + +impl PrettyDebug for SyntaxShape { + /// Prepare SyntaxShape for pretty-printing + fn pretty(&self) -> DebugDocBuilder { + DbgDocBldr::kind(self.syntax_shape_name().to_string()) } }