From 34292b282a39238bf7deaf09bdadec06fb9473a9 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 26 Aug 2019 12:21:03 -0700 Subject: [PATCH] Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system. --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/cli.rs | 15 +++- src/commands/classified.rs | 3 +- src/commands/command.rs | 33 ++++++++- src/commands/get.rs | 36 ++++++---- src/commands/mkdir.rs | 2 +- src/commands/open.rs | 27 +++---- src/commands/pick.rs | 2 +- src/commands/plugin.rs | 24 ++++++- src/commands/reject.rs | 2 +- src/commands/sort_by.rs | 51 ++++++-------- src/commands/split_column.rs | 2 +- src/context.rs | 8 +-- src/errors.rs | 15 ++++ src/evaluate/evaluator.rs | 1 + src/format/table.rs | 2 +- src/lib.rs | 1 + src/object/base.rs | 17 +++++ src/object/types.rs | 5 +- src/parser/hir.rs | 21 +++++- src/parser/hir/baseline_parse.rs | 45 ++++++++++++ src/parser/hir/baseline_parse_tokens.rs | 93 ++++++++++++++++++++----- src/parser/hir/named.rs | 2 +- src/parser/parse/parser.rs | 4 ++ src/parser/parse_command.rs | 24 ++++--- src/parser/registry.rs | 12 ++-- src/plugins/add.rs | 20 ++---- src/plugins/binaryview.rs | 15 +--- src/plugins/edit.rs | 20 ++---- src/plugins/inc.rs | 24 +++---- src/plugins/skip.rs | 4 +- src/plugins/str.rs | 39 +++++------ src/plugins/sum.rs | 11 +-- src/plugins/sys.rs | 12 +--- src/plugins/textview.rs | 9 +-- src/plugins/tree.rs | 9 +-- src/prelude.rs | 19 +++++ src/shell/filesystem_shell.rs | 8 ++- src/shell/shell.rs | 2 + src/shell/shell_manager.rs | 9 +++ src/shell/value_shell.rs | 4 ++ src/utils.rs | 9 ++- tests/command_ls_tests.rs | 2 +- 44 files changed, 442 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0778faa5b1..76aea16d08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,6 +1733,7 @@ dependencies = [ "serde_ini 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "surf 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2646,6 +2647,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shellexpand" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slab" version = "0.4.2" @@ -3568,6 +3574,7 @@ dependencies = [ "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" "checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a" "checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" +"checksum shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de7a5b5a9142fd278a10e0209b021a1b85849352e6951f4f914735c976737564" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum sluice 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec70d7c3b17c262d4a18f7291c6ce62bf47170915f3b795434d3c5c49a4e59b7" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" diff --git a/Cargo.toml b/Cargo.toml index 4b30972a44..906e8e2b21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ battery = "0.7.4" textwrap = {version = "0.11.0", features = ["term_size"]} rawkey = {version = "0.1.2", optional = true } clipboard = {version = "0.5", optional = true } +shellexpand = "1.0.0" [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/src/cli.rs b/src/cli.rs index a76ba20845..36d2e8b804 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -58,12 +58,17 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel let mut input = String::new(); match reader.read_line(&mut input) { - Ok(_) => { + Ok(count) => { + trace!("processing response ({} bytes)", count); + let response = serde_json::from_str::>>(&input); match response { Ok(jrpc) => match jrpc.params { Ok(params) => { let fname = path.to_string_lossy(); + + trace!("processing {:?}", params); + if params.is_filter { let fname = fname.to_string(); let name = params.name.clone(); @@ -93,14 +98,18 @@ fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Resu let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?; let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?; + trace!("Looking for plugins in {:?}", path); + match std::fs::read_dir(path) { Ok(p) => { for entry in p { let entry = entry?; let filename = entry.file_name(); let f_name = filename.to_string_lossy(); + if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) { let mut load_path = path.clone(); + trace!("Found {:?}", f_name); load_path.push(f_name.to_string()); load_plugin(&load_path, context)?; } @@ -504,7 +513,9 @@ fn classify_command( trace!(target: "nu::build_pipeline", "classifying {:?}", config); - let args: hir::Call = config.parse_args(call, context.registry(), source)?; + let args: hir::Call = config.parse_args(call, &context, source)?; + + trace!(target: "nu::build_pipeline", "args :: {}", args.debug(source)); Ok(ClassifiedCommand::Internal(InternalCommand { command, diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 28005ceb7a..926b08355f 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -122,10 +122,11 @@ impl InternalCommand { self.name_span.clone(), context.source_map.clone(), self.args, - source, + &source, objects, ); + let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result); let mut result = result.values; let mut stream = VecDeque::new(); diff --git a/src/commands/command.rs b/src/commands/command.rs index b498b5e66a..b11c0a5beb 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -422,6 +422,25 @@ pub enum CommandAction { LeaveShell, } +impl ToDebug for CommandAction { + fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result { + match self { + CommandAction::ChangePath(s) => write!(f, "action:change-path={}", s), + CommandAction::AddSpanSource(u, source) => { + write!(f, "action:add-span-source={}@{:?}", u, source) + } + CommandAction::Exit => write!(f, "action:exit"), + CommandAction::EnterShell(s) => write!(f, "action:enter-shell={}", s), + CommandAction::EnterValueShell(t) => { + write!(f, "action:enter-value-shell={:?}", t.debug()) + } + CommandAction::PreviousShell => write!(f, "action:previous-shell"), + CommandAction::NextShell => write!(f, "action:next-shell"), + CommandAction::LeaveShell => write!(f, "action:leave-shell"), + } + } +} + #[derive(Debug, Serialize, Deserialize)] pub enum ReturnSuccess { Value(Tagged), @@ -430,6 +449,16 @@ pub enum ReturnSuccess { pub type ReturnValue = Result; +impl ToDebug for ReturnValue { + fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { + match self { + Err(err) => write!(f, "{}", err.debug(source)), + Ok(ReturnSuccess::Value(v)) => write!(f, "{:?}", v.debug()), + Ok(ReturnSuccess::Action(a)) => write!(f, "{}", a.debug(source)), + } + } +} + impl From> for ReturnValue { fn from(input: Tagged) -> ReturnValue { Ok(ReturnSuccess::Value(input)) @@ -469,7 +498,7 @@ pub trait WholeStreamCommand: Send + Sync { Signature { name: self.name().to_string(), positional: vec![], - rest_positional: true, + rest_positional: None, named: indexmap::IndexMap::new(), is_filter: true, } @@ -491,7 +520,7 @@ pub trait PerItemCommand: Send + Sync { Signature { name: self.name().to_string(), positional: vec![], - rest_positional: true, + rest_positional: None, named: indexmap::IndexMap::new(), is_filter: true, } diff --git a/src/commands/get.rs b/src/commands/get.rs index 37dba702bb..cd8db615a7 100644 --- a/src/commands/get.rs +++ b/src/commands/get.rs @@ -22,33 +22,41 @@ impl WholeStreamCommand for Get { ) -> Result { args.process(registry, get)?.run() } + fn signature(&self) -> Signature { - Signature::build("get").rest() + Signature::build("get").rest(SyntaxType::Member) } } fn get_member(path: &Tagged, obj: &Tagged) -> Result, ShellError> { - let mut current = obj; + let mut current = Some(obj); for p in path.split(".") { - match current.get_data_by_key(p) { - Some(v) => current = v, - None => { + if let Some(obj) = current { + current = match obj.get_data_by_key(p) { + Some(v) => Some(v), + None => // Before we give up, see if they gave us a path that matches a field name by itself - match obj.get_data_by_key(&path.item) { - Some(v) => return Ok(v.clone()), - None => { - return Err(ShellError::labeled_error( - "Unknown column", - "table missing column", - path.span(), - )); + { + match obj.get_data_by_key(&path.item) { + Some(v) => return Ok(v.clone()), + None => { + return Err(ShellError::labeled_error( + "Unknown column", + "table missing column", + path.span(), + )); + } } } } } } - Ok(current.clone()) + match current { + Some(v) => Ok(v.clone()), + None => Ok(Value::nothing().tagged(obj.tag)), + } + // Ok(current.clone()) } pub fn get( diff --git a/src/commands/mkdir.rs b/src/commands/mkdir.rs index 895b46b320..5f92f1177b 100644 --- a/src/commands/mkdir.rs +++ b/src/commands/mkdir.rs @@ -27,7 +27,7 @@ impl PerItemCommand for Mkdir { } fn signature(&self) -> Signature { - Signature::build("mkdir").rest() + Signature::build("mkdir").rest(SyntaxType::Path) } } diff --git a/src/commands/open.rs b/src/commands/open.rs index db5b1a4780..4282da8eab 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -44,7 +44,8 @@ fn run(call_info: &CallInfo, shell_manager: &ShellManager) -> Result file, }; - let path_str = path.as_string()?; + let path_buf = path.as_path()?; + let path_str = path_buf.display().to_string(); let path_span = path.span(); let name_span = call_info.name_span; let has_raw = call_info.args.has("raw"); @@ -426,14 +427,16 @@ pub fn parse_string_as_value( name_span: Span, ) -> Result, ShellError> { match extension { - Some(x) if x == "csv" => crate::commands::from_csv::from_csv_string_to_value( - contents, - false, - contents_tag, - ) - .map_err(move |_| { - ShellError::labeled_error("Could not open as CSV", "could not open as CSV", name_span) - }), + Some(x) if x == "csv" => { + crate::commands::from_csv::from_csv_string_to_value(contents, false, contents_tag) + .map_err(move |_| { + ShellError::labeled_error( + "Could not open as CSV", + "could not open as CSV", + name_span, + ) + }) + } Some(x) if x == "toml" => { crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err( move |_| { @@ -507,9 +510,9 @@ pub fn parse_binary_as_value( crate::commands::from_bson::from_bson_bytes_to_value(contents, contents_tag).map_err( move |_| { ShellError::labeled_error( - "Could not open as BSON", - "could not open as BSON", - name_span, + "Could not open as BSON", + "could not open as BSON", + name_span, ) }, ) diff --git a/src/commands/pick.rs b/src/commands/pick.rs index bee63ecdf9..6c7fca8f25 100644 --- a/src/commands/pick.rs +++ b/src/commands/pick.rs @@ -17,7 +17,7 @@ impl WholeStreamCommand for Pick { } fn signature(&self) -> Signature { - Signature::build("pick").rest() + Signature::build("pick").rest(SyntaxType::Any) } fn run( diff --git a/src/commands/plugin.rs b/src/commands/plugin.rs index 0b02b40f75..09eadc9734 100644 --- a/src/commands/plugin.rs +++ b/src/commands/plugin.rs @@ -3,6 +3,7 @@ use crate::errors::ShellError; use crate::parser::registry; use crate::prelude::*; use derive_new::new; +use log::trace; use serde::{self, Deserialize, Serialize}; use std::io::prelude::*; use std::io::BufReader; @@ -64,6 +65,8 @@ pub fn filter_plugin( args: CommandArgs, registry: &CommandRegistry, ) -> Result { + trace!("filter_plugin :: {}", path); + let args = args.evaluate_once(registry)?; let mut child = std::process::Command::new(path) @@ -80,6 +83,8 @@ pub fn filter_plugin( let call_info = args.call_info.clone(); + trace!("filtering :: {:?}", call_info); + let stream = bos .chain(args.input.values) .chain(eos) @@ -95,7 +100,14 @@ pub fn filter_plugin( let request = JsonRpc::new("begin_filter", call_info.clone()); let request_raw = serde_json::to_string(&request).unwrap(); - let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error + match stdin.write(format!("{}\n", request_raw).as_bytes()) { + Ok(_) => {} + Err(err) => { + let mut result = VecDeque::new(); + result.push_back(Err(ShellError::unexpected(format!("{}", err)))); + return result; + } + } let mut input = String::new(); match reader.read_line(&mut input) { @@ -140,7 +152,15 @@ pub fn filter_plugin( let mut reader = BufReader::new(stdout); let request: JsonRpc> = JsonRpc::new("end_filter", vec![]); - let request_raw = serde_json::to_string(&request).unwrap(); + let request_raw = match serde_json::to_string(&request) { + Ok(req) => req, + Err(err) => { + let mut result = VecDeque::new(); + result.push_back(Err(ShellError::unexpected(format!("{}", err)))); + return result; + } + }; + let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error let mut input = String::new(); diff --git a/src/commands/reject.rs b/src/commands/reject.rs index 4fc60fecf8..92aff1cb2e 100644 --- a/src/commands/reject.rs +++ b/src/commands/reject.rs @@ -24,7 +24,7 @@ impl WholeStreamCommand for Reject { } fn signature(&self) -> Signature { - Signature::build("reject").rest() + Signature::build("reject").rest(SyntaxType::Member) } } diff --git a/src/commands/sort_by.rs b/src/commands/sort_by.rs index 820e0a471d..c726dbedc0 100644 --- a/src/commands/sort_by.rs +++ b/src/commands/sort_by.rs @@ -4,13 +4,19 @@ use crate::prelude::*; pub struct SortBy; +#[derive(Deserialize)] +pub struct SortByArgs { + rest: Vec>, + reverse: bool, +} + impl WholeStreamCommand for SortBy { fn run( &self, args: CommandArgs, registry: &CommandRegistry, ) -> Result { - sort_by(args, registry) + args.process(registry, sort_by)?.run() } fn name(&self) -> &str { @@ -18,43 +24,32 @@ impl WholeStreamCommand for SortBy { } fn signature(&self) -> Signature { - Signature::build("sort-by").switch("reverse") + Signature::build("sort-by") + .rest(SyntaxType::String) + .switch("reverse") } } -fn sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result { - let args = args.evaluate_once(registry)?; - let (input, args) = args.parts(); +fn sort_by( + SortByArgs { reverse, rest }: SortByArgs, + mut context: RunnableContext, +) -> Result { + Ok(OutputStream::new(async_stream_block! { + let mut vec = context.input.drain_vec().await; - let fields: Result, _> = args - .positional - .iter() - .flatten() - .map(|a| a.as_string()) - .collect(); - - let fields = fields?; - - let output = input.values.collect::>(); - - let reverse = args.has("reverse"); - let output = output.map(move |mut vec| { let calc_key = |item: &Tagged| { - fields - .iter() + rest.iter() .map(|f| item.get_data_by_key(f).map(|i| i.clone())) .collect::>>>() }; if reverse { - vec.sort_by_cached_key(|item| { - std::cmp::Reverse(calc_key(item)) - }); + vec.sort_by_cached_key(|item| std::cmp::Reverse(calc_key(item))); } else { vec.sort_by_cached_key(calc_key); + }; + + for item in vec { + yield item.into(); } - - vec.into_iter().collect::>() - }); - - Ok(output.flatten_stream().from_input_stream()) + })) } diff --git a/src/commands/split_column.rs b/src/commands/split_column.rs index fbccc89c6d..612d58ef64 100644 --- a/src/commands/split_column.rs +++ b/src/commands/split_column.rs @@ -28,7 +28,7 @@ impl WholeStreamCommand for SplitColumn { fn signature(&self) -> Signature { Signature::build("split-column") .required("separator", SyntaxType::Any) - .rest() + .rest(SyntaxType::Member) } } diff --git a/src/context.rs b/src/context.rs index 48cc44be5c..0a69979d00 100644 --- a/src/context.rs +++ b/src/context.rs @@ -121,7 +121,7 @@ impl Context { name_span: Span, source_map: SourceMap, args: hir::Call, - source: Text, + source: &Text, input: InputStream, ) -> OutputStream { let command_args = self.command_args(args, input, source, source_map, name_span); @@ -131,13 +131,13 @@ impl Context { fn call_info( &self, args: hir::Call, - source: Text, + source: &Text, source_map: SourceMap, name_span: Span, ) -> UnevaluatedCallInfo { UnevaluatedCallInfo { args, - source, + source: source.clone(), source_map, name_span, } @@ -147,7 +147,7 @@ impl Context { &self, args: hir::Call, input: InputStream, - source: Text, + source: &Text, source_map: SourceMap, name_span: Span, ) -> CommandArgs { diff --git a/src/errors.rs b/src/errors.rs index cd1120dadc..84bb96f037 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,6 +5,7 @@ use ansi_term::Color; use derive_new::new; use language_reporting::{Diagnostic, Label, Severity}; use serde::{Deserialize, Serialize}; +use std::fmt; #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum Description { @@ -56,6 +57,12 @@ pub struct ShellError { cause: Option>, } +impl ToDebug for ShellError { + fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { + self.error.fmt_debug(f, source) + } +} + impl serde::de::Error for ShellError { fn custom(msg: T) -> Self where @@ -335,6 +342,7 @@ pub enum ProximateShellError { right: Tagged, }, } + impl ProximateShellError { fn start(self) -> ShellError { ShellError { @@ -344,6 +352,13 @@ impl ProximateShellError { } } +impl ToDebug for ProximateShellError { + fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result { + // TODO: Custom debug for inner spans + write!(f, "{:?}", self) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShellDiagnostic { crate diagnostic: Diagnostic, diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index b58eac2202..9d768a46d9 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -39,6 +39,7 @@ crate fn evaluate_baseline_expr( ) -> Result, ShellError> { match &expr.item { RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)), + RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())), RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()), RawExpression::Variable(var) => evaluate_reference(var, scope, source), RawExpression::ExternalCommand(external) => evaluate_external(external, scope, source), diff --git a/src/format/table.rs b/src/format/table.rs index ec641bff0c..25c41b47c5 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -68,7 +68,7 @@ impl TableView { for head in 0..headers.len() { let mut current_row_max = 0; for row in 0..values.len() { - if entries[row][head].len() > current_row_max { + if head > entries[row].len() && entries[row][head].len() > current_row_max { current_row_max = entries[row][head].len(); } } diff --git a/src/lib.rs b/src/lib.rs index a9ef8740f4..9f1b245260 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue}; pub use crate::context::{SourceMap, SpanSource}; pub use crate::env::host::BasicHost; pub use crate::object::base::OF64; +pub use crate::parser::hir::SyntaxType; pub use crate::plugin::{serve_plugin, Plugin}; pub use crate::utils::{AbsolutePath, RelativePath}; pub use cli::cli; diff --git a/src/object/base.rs b/src/object/base.rs index ea86386c6c..4f4b4f6bac 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -285,6 +285,7 @@ impl Value { } } + // TODO: This is basically a legacy construct, I think pub fn data_descriptors(&self) -> Vec { match self { Value::Primitive(_) => vec![], @@ -542,6 +543,10 @@ impl Value { Value::Primitive(Primitive::String(s.into())) } + pub fn path(s: impl Into) -> Value { + Value::Primitive(Primitive::Path(s.into())) + } + pub fn bytes(s: impl Into) -> Value { Value::Primitive(Primitive::Bytes(s.into())) } @@ -577,6 +582,18 @@ impl Value { } } +impl Tagged { + crate fn as_path(&self) -> Result { + match self.item() { + Value::Primitive(Primitive::Path(path)) => Ok(path.clone()), + other => Err(ShellError::type_error( + "Path", + other.type_name().tagged(self.span()), + )), + } + } +} + crate fn select_fields(obj: &Value, fields: &[String], tag: impl Into) -> Tagged { let mut out = TaggedDictBuilder::new(tag); diff --git a/src/object/types.rs b/src/object/types.rs index 719a990ad0..9a29284cbf 100644 --- a/src/object/types.rs +++ b/src/object/types.rs @@ -2,7 +2,6 @@ use crate::object::base as value; use crate::parser::hir; use crate::prelude::*; use log::trace; -use std::path::PathBuf; pub trait ExtractType: Sized { fn extract(value: &Tagged) -> Result; @@ -196,9 +195,9 @@ impl ExtractType for std::path::PathBuf { match &value { Tagged { - item: Value::Primitive(Primitive::String(p)), + item: Value::Primitive(Primitive::Path(p)), .. - } => Ok(PathBuf::from(p)), + } => Ok(p.clone()), other => Err(ShellError::type_error("Path", other.tagged_type_name())), } } diff --git a/src/parser/hir.rs b/src/parser/hir.rs index e323812d17..2148a179f4 100644 --- a/src/parser/hir.rs +++ b/src/parser/hir.rs @@ -11,16 +11,22 @@ use derive_new::new; use getset::Getters; use serde::{Deserialize, Serialize}; use std::fmt; +use std::path::PathBuf; use crate::evaluate::Scope; -crate use self::baseline_parse::{baseline_parse_single_token, baseline_parse_token_as_string}; -crate use self::baseline_parse_tokens::{baseline_parse_next_expr, SyntaxType, TokensIterator}; +crate use self::baseline_parse::{ + baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, + baseline_parse_token_as_string, +}; +crate use self::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator}; crate use self::binary::Binary; crate use self::external_command::ExternalCommand; crate use self::named::NamedArguments; crate use self::path::Path; +pub use self::baseline_parse_tokens::SyntaxType; + pub fn path(head: impl Into, tail: Vec>>) -> Path { Path::new( head.into(), @@ -68,6 +74,8 @@ impl ToDebug for Call { write!(f, "{}", named.debug(source))?; } + write!(f, ")")?; + Ok(()) } } @@ -81,6 +89,7 @@ pub enum RawExpression { Block(Vec), List(Vec), Path(Box), + FilePath(PathBuf), ExternalCommand(ExternalCommand), #[allow(unused)] @@ -105,6 +114,7 @@ impl RawExpression { match self { RawExpression::Literal(literal) => literal.type_name(), RawExpression::Synthetic(synthetic) => synthetic.type_name(), + RawExpression::FilePath(..) => "filepath", RawExpression::Variable(..) => "variable", RawExpression::List(..) => "list", RawExpression::Binary(..) => "binary", @@ -141,6 +151,10 @@ impl Expression { ) } + crate fn file_path(path: impl Into, outer: impl Into) -> Expression { + Tagged::from_simple_spanned_item(RawExpression::FilePath(path.into()), outer.into()) + } + crate fn bare(span: impl Into) -> Expression { Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into()) } @@ -170,7 +184,8 @@ impl Expression { impl ToDebug for Expression { fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { match self.item() { - RawExpression::Literal(l) => write!(f, "{:?}", l), + RawExpression::Literal(l) => l.tagged(self.span()).fmt_debug(f, source), + RawExpression::FilePath(p) => write!(f, "{}", p.display()), RawExpression::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s), RawExpression::Variable(Variable::It(_)) => write!(f, "$it"), RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)), diff --git a/src/parser/hir/baseline_parse.rs b/src/parser/hir/baseline_parse.rs index 6813470640..a894f2bd7c 100644 --- a/src/parser/hir/baseline_parse.rs +++ b/src/parser/hir/baseline_parse.rs @@ -1,5 +1,7 @@ +use crate::context::Context; use crate::parser::{hir, RawToken, Token}; use crate::Text; +use std::path::PathBuf; pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression { match *token.item() { @@ -15,6 +17,20 @@ pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Express } } +pub fn baseline_parse_token_as_number(token: &Token, source: &Text) -> hir::Expression { + match *token.item() { + RawToken::Variable(span) if span.slice(source) == "it" => { + hir::Expression::it_variable(span, token.span()) + } + RawToken::External(span) => hir::Expression::external_command(span, token.span()), + RawToken::Variable(span) => hir::Expression::variable(span, token.span()), + RawToken::Integer(int) => hir::Expression::int(int, token.span()), + RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()), + RawToken::Bare => hir::Expression::bare(token.span()), + RawToken::String(span) => hir::Expression::string(span, token.span()), + } +} + pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expression { match *token.item() { RawToken::Variable(span) if span.slice(source) == "it" => { @@ -28,3 +44,32 @@ pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expr RawToken::String(span) => hir::Expression::string(span, token.span()), } } + +pub fn baseline_parse_token_as_path( + token: &Token, + context: &Context, + source: &Text, +) -> hir::Expression { + match *token.item() { + RawToken::Variable(span) if span.slice(source) == "it" => { + hir::Expression::it_variable(span, token.span()) + } + RawToken::External(span) => hir::Expression::external_command(span, token.span()), + RawToken::Variable(span) => hir::Expression::variable(span, token.span()), + RawToken::Integer(_) => hir::Expression::bare(token.span()), + RawToken::Size(_, _) => hir::Expression::bare(token.span()), + RawToken::Bare => hir::Expression::file_path( + expand_path(token.span().slice(source), context), + token.span(), + ), + RawToken::String(span) => { + hir::Expression::file_path(expand_path(span.slice(source), context), token.span()) + } + } +} + +pub fn expand_path(string: &str, context: &Context) -> PathBuf { + let expanded = shellexpand::tilde_with_context(string, || context.shell_manager.homedir()); + + PathBuf::from(expanded.as_ref()) +} diff --git a/src/parser/hir/baseline_parse_tokens.rs b/src/parser/hir/baseline_parse_tokens.rs index d9891b2fca..248169bbb8 100644 --- a/src/parser/hir/baseline_parse_tokens.rs +++ b/src/parser/hir/baseline_parse_tokens.rs @@ -1,8 +1,11 @@ +use crate::context::Context; use crate::errors::ShellError; -use crate::parser::registry::CommandRegistry; use crate::parser::{ hir, - hir::{baseline_parse_single_token, baseline_parse_token_as_string}, + hir::{ + baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, + baseline_parse_token_as_string, + }, DelimitedNode, Delimiter, PathNode, RawToken, TokenNode, }; use crate::{Span, Tag, Tagged, TaggedItem, Text}; @@ -12,8 +15,9 @@ use serde::{Deserialize, Serialize}; pub fn baseline_parse_tokens( token_nodes: &mut TokensIterator<'_>, - registry: &CommandRegistry, + context: &Context, source: &Text, + syntax_type: SyntaxType, ) -> Result, ShellError> { let mut exprs: Vec = vec![]; @@ -22,7 +26,7 @@ pub fn baseline_parse_tokens( break; } - let expr = baseline_parse_next_expr(token_nodes, registry, source, SyntaxType::Any)?; + let expr = baseline_parse_next_expr(token_nodes, context, source, syntax_type)?; exprs.push(expr); } @@ -35,7 +39,10 @@ pub enum SyntaxType { Any, List, Literal, + String, + Member, Variable, + Number, Path, Binary, Block, @@ -44,7 +51,7 @@ pub enum SyntaxType { pub fn baseline_parse_next_expr( tokens: &mut TokensIterator, - registry: &CommandRegistry, + context: &Context, source: &Text, syntax_type: SyntaxType, ) -> Result { @@ -56,7 +63,7 @@ pub fn baseline_parse_next_expr( match (syntax_type, next) { (SyntaxType::Path, TokenNode::Token(token)) => { - return Ok(baseline_parse_token_as_string(token, source)) + return Ok(baseline_parse_token_as_path(token, context, source)) } (SyntaxType::Path, token) => { @@ -66,10 +73,50 @@ pub fn baseline_parse_next_expr( )) } - _ => {} + (SyntaxType::String, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_string(token, source)); + } + + (SyntaxType::String, token) => { + return Err(ShellError::type_error( + "String", + token.type_name().simple_spanned(token.span()), + )) + } + + (SyntaxType::Number, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_number(token, source)); + } + + (SyntaxType::Number, token) => { + return Err(ShellError::type_error( + "Numeric", + token.type_name().simple_spanned(token.span()), + )) + } + + // TODO: More legit member processing + (SyntaxType::Member, TokenNode::Token(token)) => { + return Ok(baseline_parse_token_as_string(token, source)); + } + + (SyntaxType::Member, token) => { + return Err(ShellError::type_error( + "member", + token.type_name().simple_spanned(token.span()), + )) + } + + (SyntaxType::Any, _) => {} + (SyntaxType::List, _) => {} + (SyntaxType::Literal, _) => {} + (SyntaxType::Variable, _) => {} + (SyntaxType::Binary, _) => {} + (SyntaxType::Block, _) => {} + (SyntaxType::Boolean, _) => {} }; - let first = baseline_parse_semantic_token(next, registry, source)?; + let first = baseline_parse_semantic_token(next, context, source)?; let possible_op = tokens.peek(); @@ -88,7 +135,7 @@ pub fn baseline_parse_next_expr( op.span(), )) } - Some(token) => baseline_parse_semantic_token(token, registry, source)?, + Some(token) => baseline_parse_semantic_token(token, context, source)?, }; // We definitely have a binary expression here -- let's see if we should coerce it into a block @@ -176,13 +223,13 @@ pub fn baseline_parse_next_expr( pub fn baseline_parse_semantic_token( token: &TokenNode, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { match token { TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)), TokenNode::Call(_call) => unimplemented!(), - TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, registry, source), + TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, context, source), TokenNode::Pipeline(_pipeline) => unimplemented!(), TokenNode::Operator(_op) => unreachable!(), TokenNode::Flag(_flag) => Err(ShellError::unimplemented( @@ -191,20 +238,24 @@ pub fn baseline_parse_semantic_token( TokenNode::Member(_span) => unreachable!(), TokenNode::Whitespace(_span) => unreachable!(), TokenNode::Error(error) => Err(*error.item.clone()), - TokenNode::Path(path) => baseline_parse_path(path, registry, source), + TokenNode::Path(path) => baseline_parse_path(path, context, source), } } pub fn baseline_parse_delimited( token: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { match token.delimiter() { Delimiter::Brace => { let children = token.children(); - let exprs = - baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; + let exprs = baseline_parse_tokens( + &mut TokensIterator::new(children), + context, + source, + SyntaxType::Any, + )?; let expr = hir::RawExpression::Block(exprs); Ok(Tagged::from_simple_spanned_item(expr, token.span())) @@ -212,8 +263,12 @@ pub fn baseline_parse_delimited( Delimiter::Paren => unimplemented!(), Delimiter::Square => { let children = token.children(); - let exprs = - baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; + let exprs = baseline_parse_tokens( + &mut TokensIterator::new(children), + context, + source, + SyntaxType::Any, + )?; let expr = hir::RawExpression::List(exprs); Ok(expr.tagged(Tag::unknown_origin(token.span()))) @@ -223,10 +278,10 @@ pub fn baseline_parse_delimited( pub fn baseline_parse_path( token: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { - let head = baseline_parse_semantic_token(token.head(), registry, source)?; + let head = baseline_parse_semantic_token(token.head(), context, source)?; let mut tail = vec![]; diff --git a/src/parser/hir/named.rs b/src/parser/hir/named.rs index 5ce06fe6f5..f95896cbfb 100644 --- a/src/parser/hir/named.rs +++ b/src/parser/hir/named.rs @@ -27,7 +27,7 @@ impl ToDebug for NamedArguments { for (name, value) in &self.named { match value { NamedValue::AbsentSwitch => continue, - NamedValue::PresentSwitch(span) => write!(f, " {}", span.slice(source))?, + NamedValue::PresentSwitch(span) => write!(f, " --{}", span.slice(source))?, NamedValue::AbsentValue => continue, NamedValue::Value(expr) => write!(f, " --{} {}", name, expr.debug(source))?, } diff --git a/src/parser/parse/parser.rs b/src/parser/parse/parser.rs index 2f060ca29b..a5afac756d 100644 --- a/src/parser/parse/parser.rs +++ b/src/parser/parse/parser.rs @@ -540,6 +540,8 @@ fn is_start_bare_char(c: char) -> bool { '@' => true, '*' => true, '?' => true, + '~' => true, + '+' => true, _ => false, } } @@ -557,6 +559,8 @@ fn is_bare_char(c: char) -> bool { '*' => true, '?' => true, '=' => true, + '~' => true, + '+' => true, _ => false, } } diff --git a/src/parser/parse_command.rs b/src/parser/parse_command.rs index 9ae920d2c2..4d8204f16b 100644 --- a/src/parser/parse_command.rs +++ b/src/parser/parse_command.rs @@ -1,5 +1,6 @@ +use crate::context::Context; use crate::errors::{ArgumentError, ShellError}; -use crate::parser::registry::{CommandRegistry, NamedType, PositionalType, Signature}; +use crate::parser::registry::{NamedType, PositionalType, Signature}; use crate::parser::{baseline_parse_tokens, CallNode}; use crate::parser::{ hir::{self, NamedArguments}, @@ -10,7 +11,7 @@ use log::trace; pub fn parse_command( config: &Signature, - registry: &CommandRegistry, + context: &Context, call: &Tagged, source: &Text, ) -> Result { @@ -31,7 +32,7 @@ pub fn parse_command( .collect() }); - match parse_command_tail(&config, registry, children, source, call.span())? { + match parse_command_tail(&config, context, children, source, call.span())? { None => Ok(hir::Call::new(Box::new(head), None, None)), Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)), } @@ -63,7 +64,7 @@ fn parse_command_head(head: &TokenNode) -> Result { fn parse_command_tail( config: &Signature, - registry: &CommandRegistry, + context: &Context, tail: Option>, source: &Text, command_span: Span, @@ -101,7 +102,7 @@ fn parse_command_tail( } let expr = - hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?; + hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?; tail.restart(); named.insert_mandatory(name, expr); @@ -121,7 +122,7 @@ fn parse_command_tail( )); } - let expr = hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?; + let expr = hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?; tail.restart(); named.insert_optional(name, Some(expr)); @@ -160,16 +161,17 @@ fn parse_command_tail( } } - let result = hir::baseline_parse_next_expr(tail, registry, source, arg.syntax_type())?; + let result = hir::baseline_parse_next_expr(tail, context, source, arg.syntax_type())?; positional.push(result); } trace_remaining("after positional", tail.clone(), source); - // TODO: Only do this if rest params are specified - let remainder = baseline_parse_tokens(tail, registry, source)?; - positional.extend(remainder); + if let Some(syntax_type) = config.rest_positional { + let remainder = baseline_parse_tokens(tail, context, source, syntax_type)?; + positional.extend(remainder); + } trace_remaining("after rest", tail.clone(), source); @@ -180,6 +182,8 @@ fn parse_command_tail( positional => Some(positional), }; + // TODO: Error if extra unconsumed positional arguments + let named = match named { named if named.named.is_empty() => None, named => Some(named), diff --git a/src/parser/registry.rs b/src/parser/registry.rs index d7f41d4360..3d24e99214 100644 --- a/src/parser/registry.rs +++ b/src/parser/registry.rs @@ -74,8 +74,8 @@ pub struct Signature { pub name: String, #[new(default)] pub positional: Vec, - #[new(value = "false")] - pub rest_positional: bool, + #[new(value = "None")] + pub rest_positional: Option, #[new(default)] pub named: IndexMap, #[new(value = "false")] @@ -130,8 +130,8 @@ impl Signature { self } - pub fn rest(mut self) -> Signature { - self.rest_positional = true; + pub fn rest(mut self, ty: SyntaxType) -> Signature { + self.rest_positional = Some(ty); self } } @@ -279,10 +279,10 @@ impl Signature { crate fn parse_args( &self, call: &Tagged, - registry: &CommandRegistry, + context: &Context, source: &Text, ) -> Result { - let args = parse_command(self, registry, call, source)?; + let args = parse_command(self, context, call, source)?; trace!("parsed args: {:?}", args); diff --git a/src/plugins/add.rs b/src/plugins/add.rs index 2634c14970..93c519212e 100644 --- a/src/plugins/add.rs +++ b/src/plugins/add.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, - ShellError, Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; struct Add { @@ -43,17 +42,12 @@ impl Add { impl Plugin for Add { fn config(&mut self) -> Result { - Ok(Signature { - name: "add".to_string(), - positional: vec![ - PositionalType::mandatory_any("Field"), - PositionalType::mandatory_any("Value"), - ], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("add") + .required("Field", SyntaxType::String) + .required("Value", SyntaxType::String) + .rest(SyntaxType::String).filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if let Some(args) = call_info.args.positional { match &args[0] { diff --git a/src/plugins/binaryview.rs b/src/plugins/binaryview.rs index 5668fa8696..a032a9dbff 100644 --- a/src/plugins/binaryview.rs +++ b/src/plugins/binaryview.rs @@ -1,9 +1,6 @@ #![feature(option_flattening)] use crossterm::{cursor, terminal, Attribute, RawScreen}; -use indexmap::IndexMap; -use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, ShellError, Signature, SpanSource, Tagged, Value, -}; +use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, SpanSource, Tagged, Value}; use pretty_hex::*; struct BinaryView; @@ -16,15 +13,7 @@ impl BinaryView { impl Plugin for BinaryView { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("lores".to_string(), NamedType::Switch); - Ok(Signature { - name: "binaryview".to_string(), - positional: vec![], - is_filter: false, - named, - rest_positional: false, - }) + Ok(Signature::build("binaryview").switch("lores")) } fn sink(&mut self, call_info: CallInfo, input: Vec>) { diff --git a/src/plugins/edit.rs b/src/plugins/edit.rs index 6e94c1c83f..902c038bc1 100644 --- a/src/plugins/edit.rs +++ b/src/plugins/edit.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, - ShellError, Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; struct Edit { @@ -42,17 +41,12 @@ impl Edit { impl Plugin for Edit { fn config(&mut self) -> Result { - Ok(Signature { - name: "edit".to_string(), - positional: vec![ - PositionalType::mandatory_any("Field"), - PositionalType::mandatory_any("Value"), - ], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("edit") + .required("Field", SyntaxType::String) + .required("Value", SyntaxType::String) + .filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if let Some(args) = call_info.args.positional { match &args[0] { diff --git a/src/plugins/inc.rs b/src/plugins/inc.rs index 173ced2fe1..ece83b7f21 100644 --- a/src/plugins/inc.rs +++ b/src/plugins/inc.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, - Signature, Tagged, TaggedItem, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, TaggedItem, Value, }; enum Action { @@ -116,19 +115,14 @@ impl Inc { impl Plugin for Inc { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("major".to_string(), NamedType::Switch); - named.insert("minor".to_string(), NamedType::Switch); - named.insert("patch".to_string(), NamedType::Switch); - - Ok(Signature { - name: "inc".to_string(), - positional: vec![], - is_filter: true, - named, - rest_positional: true, - }) + Ok(Signature::build("inc") + .switch("major") + .switch("minor") + .switch("patch") + .rest(SyntaxType::String) + .filter()) } + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { if call_info.args.has("major") { self.for_semver(SemVerAction::Major); diff --git a/src/plugins/skip.rs b/src/plugins/skip.rs index 8633c5e5db..6f78351fee 100644 --- a/src/plugins/skip.rs +++ b/src/plugins/skip.rs @@ -1,7 +1,7 @@ use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, - Tagged, Value, + SyntaxType, Tagged, Value, }; struct Skip { @@ -20,7 +20,7 @@ impl Plugin for Skip { positional: vec![], is_filter: true, named: IndexMap::new(), - rest_positional: true, + rest_positional: Some(SyntaxType::Number), }) } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 679ede80a6..0656d5f780 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -1,7 +1,6 @@ -use indexmap::IndexMap; use nu::{ - serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, - Signature, Tagged, Value, + serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, + SyntaxType, Tagged, Value, }; use regex::Regex; @@ -40,9 +39,11 @@ impl Str { let applied = match self.action.as_ref() { Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()), Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()), - Some(Action::ToInteger) => match input.trim().parse::() { - Ok(v) => Value::int(v), - Err(_) => Value::string(input), + Some(Action::ToInteger) => match input.trim() { + other => match other.parse::() { + Ok(v) => Value::int(v), + Err(_) => Value::string(input), + }, }, Some(Action::Replace(ref mode)) => match mode { ReplaceAction::Direct => Value::string(self.first_param()), @@ -138,9 +139,7 @@ impl Str { Some(ref f) => { let replacement = match value.item.get_data_by_path(value.tag(), f) { Some(result) => self.strutils(result.map(|x| x.clone()))?, - None => { - return Err(ShellError::string("str could not find field to replace")) - } + None => return Ok(Tagged::from_item(Value::nothing(), value.tag)), }; match value .item @@ -168,20 +167,14 @@ impl Str { impl Plugin for Str { fn config(&mut self) -> Result { - let mut named = IndexMap::new(); - named.insert("downcase".to_string(), NamedType::Switch); - named.insert("upcase".to_string(), NamedType::Switch); - named.insert("to-int".to_string(), NamedType::Switch); - named.insert("replace".to_string(), NamedType::Switch); - named.insert("find-replace".to_string(), NamedType::Switch); - - Ok(Signature { - name: "str".to_string(), - positional: vec![], - is_filter: true, - named, - rest_positional: true, - }) + Ok(Signature::build("str") + .switch("downcase") + .switch("upcase") + .switch("to-int") + .switch("replace") + .switch("find-replace") + .rest(SyntaxType::Member) + .filter()) } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { diff --git a/src/plugins/sum.rs b/src/plugins/sum.rs index 34cc59ec7b..1c983b1ad8 100644 --- a/src/plugins/sum.rs +++ b/src/plugins/sum.rs @@ -1,4 +1,3 @@ -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, Tag, Tagged, Value, @@ -14,6 +13,7 @@ impl Sum { fn sum(&mut self, value: Tagged) -> Result<(), ShellError> { match value.item { + Value::Primitive(Primitive::Nothing) => Ok(()), Value::Primitive(Primitive::Int(i)) => { match self.total { Some(Tagged { @@ -64,14 +64,9 @@ impl Sum { impl Plugin for Sum { fn config(&mut self) -> Result { - Ok(Signature { - name: "sum".to_string(), - positional: vec![], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("sum").filter()) } + fn begin_filter(&mut self, _: CallInfo) -> Result, ShellError> { Ok(vec![]) } diff --git a/src/plugins/sys.rs b/src/plugins/sys.rs index c442f39f9d..00e4ca47d2 100644 --- a/src/plugins/sys.rs +++ b/src/plugins/sys.rs @@ -1,10 +1,9 @@ use futures::executor::block_on; use futures::stream::StreamExt; use heim::{disk, memory, net}; -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, - Tag, Tagged, TaggedDictBuilder, Value, + SyntaxType, Tag, Tagged, TaggedDictBuilder, Value, }; use std::ffi::OsStr; @@ -251,14 +250,9 @@ async fn sysinfo(tag: Tag) -> Vec> { impl Plugin for Sys { fn config(&mut self) -> Result { - Ok(Signature { - name: "sys".to_string(), - positional: vec![], - is_filter: true, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("sys").rest(SyntaxType::Any)) } + fn begin_filter(&mut self, callinfo: CallInfo) -> Result, ShellError> { Ok(block_on(sysinfo(Tag::unknown_origin(callinfo.name_span))) .into_iter() diff --git a/src/plugins/textview.rs b/src/plugins/textview.rs index 92d95c83e1..f82127cd85 100644 --- a/src/plugins/textview.rs +++ b/src/plugins/textview.rs @@ -2,7 +2,6 @@ use crossterm::{cursor, terminal, RawScreen}; use crossterm::{InputEvent, KeyEvent}; -use indexmap::IndexMap; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ShellError, Signature, SourceMap, SpanSource, Tagged, Value, @@ -29,13 +28,7 @@ impl TextView { impl Plugin for TextView { fn config(&mut self) -> Result { - Ok(Signature { - name: "textview".to_string(), - positional: vec![], - is_filter: false, - named: IndexMap::new(), - rest_positional: false, - }) + Ok(Signature::build("textview")) } fn sink(&mut self, call_info: CallInfo, input: Vec>) { diff --git a/src/plugins/tree.rs b/src/plugins/tree.rs index dcffa48eb8..9784e854c3 100644 --- a/src/plugins/tree.rs +++ b/src/plugins/tree.rs @@ -1,5 +1,4 @@ use derive_new::new; -use indexmap::IndexMap; use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, Tagged, Value}; use ptree::item::StringItem; use ptree::output::print_tree_with; @@ -81,13 +80,7 @@ struct TreeViewer; impl Plugin for TreeViewer { fn config(&mut self) -> Result { - Ok(Signature { - name: "tree".to_string(), - positional: vec![], - is_filter: false, - named: IndexMap::new(), - rest_positional: true, - }) + Ok(Signature::build("tree")) } fn sink(&mut self, _call_info: CallInfo, input: Vec>) { diff --git a/src/prelude.rs b/src/prelude.rs index 9e1f7322f8..481d897576 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -32,6 +32,25 @@ macro_rules! trace_stream { }}; } +#[macro_export] +macro_rules! trace_out_stream { + (target: $target:tt, source: $source:expr, $desc:tt = $expr:expr) => {{ + if log::log_enabled!(target: $target, log::Level::Trace) { + use futures::stream::StreamExt; + + let source = $source.clone(); + + let objects = $expr.values.inspect(move |o| { + trace!(target: $target, "{} = {}", $desc, o.debug(&source)); + }); + + $crate::stream::OutputStream::new(objects) + } else { + $expr + } + }}; +} + crate use crate::cli::MaybeOwned; crate use crate::commands::command::{ CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext, diff --git a/src/shell/filesystem_shell.rs b/src/shell/filesystem_shell.rs index adeb551faf..1f2f945510 100644 --- a/src/shell/filesystem_shell.rs +++ b/src/shell/filesystem_shell.rs @@ -72,12 +72,16 @@ impl Shell for FilesystemShell { "filesystem".to_string() } + fn homedir(&self) -> Option { + dirs::home_dir() + } + fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let cwd = self.path(); let mut full_path = PathBuf::from(self.path()); match &args.nth(0) { - Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)), + Some(value) => full_path.push(Path::new(&value.as_path()?)), _ => {} } @@ -176,7 +180,7 @@ impl Shell for FilesystemShell { } }, Some(v) => { - let target = v.as_string()?; + let target = v.as_path()?; let path = PathBuf::from(self.path()); match dunce::canonicalize(path.join(target).as_path()) { Ok(p) => p, diff --git a/src/shell/shell.rs b/src/shell/shell.rs index 1db76d699c..4157cacb68 100644 --- a/src/shell/shell.rs +++ b/src/shell/shell.rs @@ -7,9 +7,11 @@ use crate::context::SourceMap; use crate::errors::ShellError; use crate::prelude::*; use crate::stream::OutputStream; +use std::path::PathBuf; pub trait Shell: std::fmt::Debug { fn name(&self, source_map: &SourceMap) -> String; + fn homedir(&self) -> Option; fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result; fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result; fn cp(&self, args: CopyArgs, name: Span, path: &str) -> Result; diff --git a/src/shell/shell_manager.rs b/src/shell/shell_manager.rs index 49e8484ee2..1d58738fee 100644 --- a/src/shell/shell_manager.rs +++ b/src/shell/shell_manager.rs @@ -9,6 +9,7 @@ use crate::shell::filesystem_shell::FilesystemShell; use crate::shell::shell::Shell; use crate::stream::OutputStream; use std::error::Error; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; #[derive(Clone, Debug)] @@ -102,16 +103,24 @@ impl ShellManager { self.set_path(self.path()); } + pub fn homedir(&self) -> Option { + let env = self.shells.lock().unwrap(); + + env[self.current_shell].homedir() + } + pub fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let env = self.shells.lock().unwrap(); env[self.current_shell].ls(args) } + pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result { let env = self.shells.lock().unwrap(); env[self.current_shell].cd(args) } + pub fn cp( &self, args: CopyArgs, diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs index c65a8833c1..3c4e78c8bf 100644 --- a/src/shell/value_shell.rs +++ b/src/shell/value_shell.rs @@ -69,6 +69,10 @@ impl Shell for ValueShell { ) } + fn homedir(&self) -> Option { + dirs::home_dir() + } + fn ls(&self, _args: EvaluatedWholeStreamCommandArgs) -> Result { Ok(self .members() diff --git a/src/utils.rs b/src/utils.rs index 99a59e850e..6b4fc33f37 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,7 +105,10 @@ impl FileStructure { self.root = path.to_path_buf(); } - pub fn paths_applying_with(&mut self, to: F) -> Result, Box> + pub fn paths_applying_with( + &mut self, + to: F, + ) -> Result, Box> where F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box>, { @@ -155,6 +158,7 @@ impl FileStructure { #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use super::{FileStructure, Res}; use std::path::PathBuf; @@ -175,7 +179,8 @@ mod tests { fn prepares_and_decorates_source_files_for_copying() { let mut res = FileStructure::new(); - res.walk_decorate(fixtures().as_path()).expect("Can not decorate files traversal."); + res.walk_decorate(fixtures().as_path()) + .expect("Can not decorate files traversal."); assert_eq!( res.resources, diff --git a/tests/command_ls_tests.rs b/tests/command_ls_tests.rs index 0cda5c93d8..6223709805 100644 --- a/tests/command_ls_tests.rs +++ b/tests/command_ls_tests.rs @@ -18,7 +18,7 @@ fn ls_lists_regular_files() { nu!( output, cwd(&full_path), - "ls | get name | lines| split-column \".\" | get Column2 | str Column2 --to-int | sum | echo $it" + r#"ls | get name | lines | split-column "." | get Column2 | str Column2 --to-int | sum | echo $it"# ); assert_eq!(output, "30");