nushell/crates/nu-cli/src/commands/command.rs

396 lines
11 KiB
Rust
Raw Normal View History

use crate::deserializer::ConfigDeserializer;
use crate::evaluate::evaluate_args::evaluate_args;
use crate::prelude::*;
use crate::{commands::help::get_help, run_block};
2019-08-02 21:15:07 +02:00
use derive_new::new;
2019-06-22 05:43:37 +02:00
use getset::Getters;
use nu_errors::ShellError;
use nu_protocol::hir::{self, Block};
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use serde::Deserialize;
2019-07-24 00:22:11 +02:00
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
2019-05-10 18:59:12 +02:00
#[derive(Debug, Clone)]
2019-07-24 00:22:11 +02:00
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub name_tag: Tag,
2019-07-24 00:22:11 +02:00
}
impl UnevaluatedCallInfo {
pub async fn evaluate(self, ctx: &EvaluationContext) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, ctx).await?;
2019-07-24 00:22:11 +02:00
Ok(CallInfo {
args,
name_tag: self.name_tag,
2019-07-24 00:22:11 +02:00
})
}
Add --help for commands (#1226) * WIP --help works for PerItemCommands. * De-linting * Add more comments (#1228) * Add some more docs * More docs * More docs * More docs (#1229) * Add some more docs * More docs * More docs * Add more docs * External commands: wrap values that contain spaces in quotes (#1214) (#1220) * External commands: wrap values that contain spaces in quotes (#1214) * Add fn's argument_contains_whitespace & add_quotes (#1214) * Fix formatting with cargo fmt * Don't wrap argument in quotes when $it is already quoted (#1214) * Implement --help for internal commands * Externals now spawn independently. (#1230) This commit changes the way we shell out externals when using the `"$it"` argument. Also pipes per row to an external's stdin if no `"$it"` argument is present for external commands. Further separation of logic (preparing the external's command arguments, getting the data for piping, emitting values, spawning processes) will give us a better idea for lower level details regarding external commands until we can find the right abstractions for making them more generic and unify within the pipeline calling logic of Nu internal's and external's. * Poll externals quicker. (#1231) * WIP --help works for PerItemCommands. * De-linting * Implement --help for internal commands * Make having --help the default * Update test to include new default switch Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com> Co-authored-by: Koenraad Verheyden <mail@koenraadverheyden.com> Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-17 23:46:18 +01:00
pub fn switch_present(&self, switch: &str) -> bool {
self.args.switch_preset(switch)
}
2019-07-24 00:22:11 +02:00
}
2019-06-22 05:43:37 +02:00
#[derive(Getters)]
#[get = "pub(crate)"]
pub struct CommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
2019-08-07 19:49:11 +02:00
pub shell_manager: ShellManager,
2019-07-24 00:22:11 +02:00
pub call_info: UnevaluatedCallInfo,
pub scope: Scope,
pub input: InputStream,
2019-05-16 02:21:46 +02:00
}
2019-08-14 19:02:39 +02:00
#[derive(Getters, Clone)]
#[get = "pub(crate)"]
2019-08-03 04:17:28 +02:00
pub struct RawCommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
2019-08-09 06:51:21 +02:00
pub shell_manager: ShellManager,
pub scope: Scope,
2019-08-03 04:17:28 +02:00
pub call_info: UnevaluatedCallInfo,
}
impl RawCommandArgs {
pub fn with_input(self, input: impl Into<InputStream>) -> CommandArgs {
2019-08-03 04:17:28 +02:00
CommandArgs {
host: self.host,
ctrl_c: self.ctrl_c,
current_errors: self.current_errors,
2019-08-09 06:51:21 +02:00
shell_manager: self.shell_manager,
2019-08-03 04:17:28 +02:00
call_info: self.call_info,
scope: self.scope,
2019-08-03 04:17:28 +02:00
input: input.into(),
}
}
}
impl std::fmt::Debug for CommandArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.call_info.fmt(f)
}
}
2019-06-22 05:43:37 +02:00
impl CommandArgs {
pub async fn evaluate_once(self) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
let ctx = EvaluationContext::from_args(&self);
2019-07-24 00:22:11 +02:00
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
2019-08-09 06:51:21 +02:00
let shell_manager = self.shell_manager.clone();
2019-07-24 00:22:11 +02:00
let input = self.input;
let call_info = self.call_info.evaluate(&ctx).await?;
let scope = self.scope.clone();
2019-07-24 00:22:11 +02:00
2019-08-15 07:02:02 +02:00
Ok(EvaluatedWholeStreamCommandArgs::new(
2019-08-09 06:51:21 +02:00
host,
ctrl_c,
2019-08-09 06:51:21 +02:00
shell_manager,
call_info,
input,
scope,
2019-08-09 06:51:21 +02:00
))
2019-07-24 00:22:11 +02:00
}
pub async fn process<'de, T: Deserialize<'de>>(self) -> Result<(T, InputStream), ShellError> {
let args = self.evaluate_once().await?;
let call_info = args.call_info.clone();
2019-08-03 04:17:28 +02:00
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
2019-08-03 04:17:28 +02:00
Ok((T::deserialize(&mut deserializer)?, args.input))
2019-08-02 21:15:07 +02:00
}
}
pub struct RunnableContext {
pub input: InputStream,
2019-08-09 06:51:21 +02:00
pub shell_manager: ShellManager,
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub scope: Scope,
pub name: Tag,
2019-08-02 21:15:07 +02:00
}
impl RunnableContext {
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}
2019-08-02 21:15:07 +02:00
}
2019-08-15 07:02:02 +02:00
pub struct EvaluatedWholeStreamCommandArgs {
2019-07-24 00:22:11 +02:00
pub args: EvaluatedCommandArgs,
pub input: InputStream,
}
2019-08-15 07:02:02 +02:00
impl Deref for EvaluatedWholeStreamCommandArgs {
2019-07-24 00:22:11 +02:00
type Target = EvaluatedCommandArgs;
fn deref(&self) -> &Self::Target {
&self.args
}
}
2019-08-15 07:02:02 +02:00
impl EvaluatedWholeStreamCommandArgs {
2019-07-24 00:22:11 +02:00
pub fn new(
host: Arc<parking_lot::Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
2019-08-09 06:51:21 +02:00
shell_manager: ShellManager,
2019-07-24 00:22:11 +02:00
call_info: CallInfo,
input: impl Into<InputStream>,
scope: Scope,
2019-08-15 07:02:02 +02:00
) -> EvaluatedWholeStreamCommandArgs {
EvaluatedWholeStreamCommandArgs {
2019-07-24 00:22:11 +02:00
args: EvaluatedCommandArgs {
host,
ctrl_c,
2019-08-09 06:51:21 +02:00
shell_manager,
2019-07-24 00:22:11 +02:00
call_info,
scope,
2019-07-24 00:22:11 +02:00
},
input: input.into(),
}
}
pub fn name_tag(&self) -> Tag {
self.args.call_info.name_tag.clone()
2019-07-24 00:22:11 +02:00
}
pub fn parts(self) -> (InputStream, EvaluatedArgs) {
2019-08-15 07:02:02 +02:00
let EvaluatedWholeStreamCommandArgs { args, input } = self;
2019-07-24 00:22:11 +02:00
(input, args.call_info.args)
}
2019-08-02 21:15:07 +02:00
pub fn split(self) -> (InputStream, EvaluatedCommandArgs) {
2019-08-15 07:02:02 +02:00
let EvaluatedWholeStreamCommandArgs { args, input } = self;
2019-08-02 21:15:07 +02:00
(input, args)
}
2019-07-24 00:22:11 +02:00
}
2019-08-02 21:15:07 +02:00
#[derive(Getters, new)]
#[get = "pub(crate)"]
2019-07-24 00:22:11 +02:00
pub struct EvaluatedCommandArgs {
pub host: Arc<parking_lot::Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
2019-08-09 06:51:21 +02:00
pub shell_manager: ShellManager,
2019-07-24 00:22:11 +02:00
pub call_info: CallInfo,
pub scope: Scope,
2019-07-24 00:22:11 +02:00
}
impl EvaluatedCommandArgs {
pub fn nth(&self, pos: usize) -> Option<&Value> {
self.call_info.args.nth(pos)
2019-06-22 05:43:37 +02:00
}
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
2020-08-03 00:34:33 +02:00
self.call_info
.args
.nth(pos)
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.call_info.args.get(name)
2019-06-22 05:43:37 +02:00
}
pub fn has(&self, name: &str) -> bool {
self.call_info.args.has(name)
2019-06-22 05:43:37 +02:00
}
}
pub struct Example {
pub example: &'static str,
pub description: &'static str,
pub result: Option<Vec<Value>>,
}
2020-05-29 10:22:52 +02:00
#[async_trait]
2019-08-15 07:02:02 +02:00
pub trait WholeStreamCommand: Send + Sync {
2019-08-02 21:15:07 +02:00
fn name(&self) -> &str;
fn signature(&self) -> Signature {
Add Range and start Signature support This commit contains two improvements: - Support for a Range syntax (and a corresponding Range value) - Work towards a signature syntax Implementing the Range syntax resulted in cleaning up how operators in the core syntax works. There are now two kinds of infix operators - tight operators (`.` and `..`) - loose operators Tight operators may not be interspersed (`$it.left..$it.right` is a syntax error). Loose operators require whitespace on both sides of the operator, and can be arbitrarily interspersed. Precedence is left to right in the core syntax. Note that delimited syntax (like `( ... )` or `[ ... ]`) is a single token node in the core syntax. A single token node can be parsed from beginning to end in a context-free manner. The rule for `.` is `<token node>.<member>`. The rule for `..` is `<token node>..<token node>`. Loose operators all have the same syntactic rule: `<token node><space><loose op><space><token node>`. The second aspect of this pull request is the beginning of support for a signature syntax. Before implementing signatures, a necessary prerequisite is for the core syntax to support multi-line programs. That work establishes a few things: - `;` and newlines are handled in the core grammar, and both count as "separators" - line comments begin with `#` and continue until the end of the line In this commit, multi-token productions in the core grammar can use separators interchangably with spaces. However, I think we will ultimately want a different rule preventing separators from occurring before an infix operator, so that the end of a line is always unambiguous. This would avoid gratuitous differences between modules and repl usage. We already effectively have this rule, because otherwise `x<newline> | y` would be a single pipeline, but of course that wouldn't work.
2019-12-04 22:14:52 +01:00
Signature::new(self.name()).desc(self.usage()).filter()
2019-05-28 08:45:18 +02:00
}
fn usage(&self) -> &str;
2019-08-14 19:02:39 +02:00
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
2019-09-04 03:50:23 +02:00
fn is_binary(&self) -> bool {
false
}
// Commands that are not meant to be run by users
fn is_internal(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
Vec::new()
}
}
// Custom commands are blocks, so we can use the information in the block to also
// implement a WholeStreamCommand
2021-01-01 03:13:59 +01:00
#[allow(clippy::suspicious_else_formatting)]
#[async_trait]
impl WholeStreamCommand for Block {
fn name(&self) -> &str {
&self.params.name
}
fn signature(&self) -> Signature {
self.params.clone()
}
fn usage(&self) -> &str {
&self.params.usage
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let call_info = args.call_info.clone();
let mut block = self.clone();
block.set_redirect(call_info.args.external_redirection);
let ctx = EvaluationContext::from_args(&args);
let evaluated = call_info.evaluate(&ctx).await?;
let input = args.input;
ctx.scope.enter_scope();
if let Some(args) = evaluated.args.positional {
// FIXME: do not do this
for arg in args.into_iter().zip(self.params.positional.iter()) {
let name = arg.1 .0.name();
if name.starts_with('$') {
ctx.scope.add_var(name, arg.0);
} else {
ctx.scope.add_var(format!("${}", name), arg.0);
}
}
}
if let Some(args) = evaluated.args.named {
for named in &block.params.named {
let name = named.0;
if let Some(value) = args.get(name) {
if name.starts_with('$') {
ctx.scope.add_var(name, value.clone());
} else {
ctx.scope.add_var(format!("${}", name), value.clone());
}
} else if name.starts_with('$') {
ctx.scope
.add_var(name, UntaggedValue::nothing().into_untagged_value());
} else {
ctx.scope.add_var(
format!("${}", name),
UntaggedValue::nothing().into_untagged_value(),
);
}
}
} else {
for named in &block.params.named {
let name = named.0;
if name.starts_with('$') {
ctx.scope
.add_var(name, UntaggedValue::nothing().into_untagged_value());
} else {
ctx.scope.add_var(
format!("${}", name),
UntaggedValue::nothing().into_untagged_value(),
);
}
}
}
let result = run_block(&block, &ctx, input).await;
ctx.scope.exit_scope();
result.map(|x| x.to_output_stream())
}
fn is_binary(&self) -> bool {
false
}
fn is_internal(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
vec![]
}
}
#[derive(Clone)]
pub struct Command(Arc<dyn WholeStreamCommand>);
2019-06-07 08:34:42 +02:00
impl PrettyDebugWithSource for Command {
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
b::typed(
"whole stream command",
b::description(self.name())
+ b::space()
+ b::equals()
+ b::space()
+ self.signature().pretty_debug(source),
)
}
}
Overhaul the expansion system The main thrust of this (very large) commit is an overhaul of the expansion system. The parsing pipeline is: - Lightly parse the source file for atoms, basic delimiters and pipeline structure into a token tree - Expand the token tree into a HIR (high-level intermediate representation) based upon the baseline syntax rules for expressions and the syntactic shape of commands. Somewhat non-traditionally, nu doesn't have an AST at all. It goes directly from the token tree, which doesn't represent many important distinctions (like the difference between `hello` and `5KB`) directly into a high-level representation that doesn't have a direct correspondence to the source code. At a high level, nu commands work like macros, in the sense that the syntactic shape of the invocation of a command depends on the definition of a command. However, commands do not have the ability to perform unrestricted expansions of the token tree. Instead, they describe their arguments in terms of syntactic shapes, and the expander expands the token tree into HIR based upon that definition. For example, the `where` command says that it takes a block as its first required argument, and the description of the block syntactic shape expands the syntax `cpu > 10` into HIR that represents `{ $it.cpu > 10 }`. This commit overhauls that system so that the syntactic shapes are described in terms of a few new traits (`ExpandSyntax` and `ExpandExpression` are the primary ones) that are more composable than the previous system. The first big win of this new system is the addition of the `ColumnPath` shape, which looks like `cpu."max ghz"` or `package.version`. Previously, while a variable path could look like `$it.cpu."max ghz"`, the tail of a variable path could not be easily reused in other contexts. Now, that tail is its own syntactic shape, and it can be used as part of a command's signature. This cleans up commands like `inc`, `add` and `edit` as well as shorthand blocks, which can now look like `| where cpu."max ghz" > 10`
2019-09-18 00:26:27 +02:00
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Command({})", self.name())
Overhaul the expansion system The main thrust of this (very large) commit is an overhaul of the expansion system. The parsing pipeline is: - Lightly parse the source file for atoms, basic delimiters and pipeline structure into a token tree - Expand the token tree into a HIR (high-level intermediate representation) based upon the baseline syntax rules for expressions and the syntactic shape of commands. Somewhat non-traditionally, nu doesn't have an AST at all. It goes directly from the token tree, which doesn't represent many important distinctions (like the difference between `hello` and `5KB`) directly into a high-level representation that doesn't have a direct correspondence to the source code. At a high level, nu commands work like macros, in the sense that the syntactic shape of the invocation of a command depends on the definition of a command. However, commands do not have the ability to perform unrestricted expansions of the token tree. Instead, they describe their arguments in terms of syntactic shapes, and the expander expands the token tree into HIR based upon that definition. For example, the `where` command says that it takes a block as its first required argument, and the description of the block syntactic shape expands the syntax `cpu > 10` into HIR that represents `{ $it.cpu > 10 }`. This commit overhauls that system so that the syntactic shapes are described in terms of a few new traits (`ExpandSyntax` and `ExpandExpression` are the primary ones) that are more composable than the previous system. The first big win of this new system is the addition of the `ColumnPath` shape, which looks like `cpu."max ghz"` or `package.version`. Previously, while a variable path could look like `$it.cpu."max ghz"`, the tail of a variable path could not be easily reused in other contexts. Now, that tail is its own syntactic shape, and it can be used as part of a command's signature. This cleans up commands like `inc`, `add` and `edit` as well as shorthand blocks, which can now look like `| where cpu."max ghz" > 10`
2019-09-18 00:26:27 +02:00
}
}
2019-08-02 21:15:07 +02:00
impl Command {
pub fn name(&self) -> &str {
self.0.name()
2019-08-02 21:15:07 +02:00
}
pub fn signature(&self) -> Signature {
self.0.signature()
2019-08-02 21:15:07 +02:00
}
pub fn usage(&self) -> &str {
self.0.usage()
}
pub fn examples(&self) -> Vec<Example> {
self.0.examples()
}
pub async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Add --help for commands (#1226) * WIP --help works for PerItemCommands. * De-linting * Add more comments (#1228) * Add some more docs * More docs * More docs * More docs (#1229) * Add some more docs * More docs * More docs * Add more docs * External commands: wrap values that contain spaces in quotes (#1214) (#1220) * External commands: wrap values that contain spaces in quotes (#1214) * Add fn's argument_contains_whitespace & add_quotes (#1214) * Fix formatting with cargo fmt * Don't wrap argument in quotes when $it is already quoted (#1214) * Implement --help for internal commands * Externals now spawn independently. (#1230) This commit changes the way we shell out externals when using the `"$it"` argument. Also pipes per row to an external's stdin if no `"$it"` argument is present for external commands. Further separation of logic (preparing the external's command arguments, getting the data for piping, emitting values, spawning processes) will give us a better idea for lower level details regarding external commands until we can find the right abstractions for making them more generic and unify within the pipeline calling logic of Nu internal's and external's. * Poll externals quicker. (#1231) * WIP --help works for PerItemCommands. * De-linting * Implement --help for internal commands * Make having --help the default * Update test to include new default switch Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com> Co-authored-by: Koenraad Verheyden <mail@koenraadverheyden.com> Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-17 23:46:18 +01:00
if args.call_info.switch_present("help") {
let cl = self.0.clone();
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&*cl, &args.scope)).into_value(Tag::unknown()),
))))
Add --help for commands (#1226) * WIP --help works for PerItemCommands. * De-linting * Add more comments (#1228) * Add some more docs * More docs * More docs * More docs (#1229) * Add some more docs * More docs * More docs * Add more docs * External commands: wrap values that contain spaces in quotes (#1214) (#1220) * External commands: wrap values that contain spaces in quotes (#1214) * Add fn's argument_contains_whitespace & add_quotes (#1214) * Fix formatting with cargo fmt * Don't wrap argument in quotes when $it is already quoted (#1214) * Implement --help for internal commands * Externals now spawn independently. (#1230) This commit changes the way we shell out externals when using the `"$it"` argument. Also pipes per row to an external's stdin if no `"$it"` argument is present for external commands. Further separation of logic (preparing the external's command arguments, getting the data for piping, emitting values, spawning processes) will give us a better idea for lower level details regarding external commands until we can find the right abstractions for making them more generic and unify within the pipeline calling logic of Nu internal's and external's. * Poll externals quicker. (#1231) * WIP --help works for PerItemCommands. * De-linting * Implement --help for internal commands * Make having --help the default * Update test to include new default switch Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com> Co-authored-by: Koenraad Verheyden <mail@koenraadverheyden.com> Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-17 23:46:18 +01:00
} else {
self.0.run(args).await
2019-06-07 08:34:42 +02:00
}
}
2019-08-14 19:02:39 +02:00
2019-09-04 03:50:23 +02:00
pub fn is_binary(&self) -> bool {
self.0.is_binary()
2019-09-04 03:50:23 +02:00
}
pub fn is_internal(&self) -> bool {
self.0.is_internal()
}
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
&*self.0
}
2019-06-07 08:34:42 +02:00
}
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
Command(Arc::new(command))
2019-07-24 00:22:11 +02:00
}