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.
This commit is contained in:
Yehuda Katz 2019-08-26 12:21:03 -07:00
parent 8da82c79b8
commit 34292b282a
44 changed files with 442 additions and 229 deletions

7
Cargo.lock generated
View File

@ -1733,6 +1733,7 @@ dependencies = [
"serde_ini 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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_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)", "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)", "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)", "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)", "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)", "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]] [[package]]
name = "slab" name = "slab"
version = "0.4.2" 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 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 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 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 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 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" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"

View File

@ -78,6 +78,7 @@ battery = "0.7.4"
textwrap = {version = "0.11.0", features = ["term_size"]} textwrap = {version = "0.11.0", features = ["term_size"]}
rawkey = {version = "0.1.2", optional = true } rawkey = {version = "0.1.2", optional = true }
clipboard = {version = "0.5", optional = true } clipboard = {version = "0.5", optional = true }
shellexpand = "1.0.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View File

@ -58,12 +58,17 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let mut input = String::new(); let mut input = String::new();
match reader.read_line(&mut input) { match reader.read_line(&mut input) {
Ok(_) => { Ok(count) => {
trace!("processing response ({} bytes)", count);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input); let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response { match response {
Ok(jrpc) => match jrpc.params { Ok(jrpc) => match jrpc.params {
Ok(params) => { Ok(params) => {
let fname = path.to_string_lossy(); let fname = path.to_string_lossy();
trace!("processing {:?}", params);
if params.is_filter { if params.is_filter {
let fname = fname.to_string(); let fname = fname.to_string();
let name = params.name.clone(); 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_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?;
let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?; let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?;
trace!("Looking for plugins in {:?}", path);
match std::fs::read_dir(path) { match std::fs::read_dir(path) {
Ok(p) => { Ok(p) => {
for entry in p { for entry in p {
let entry = entry?; let entry = entry?;
let filename = entry.file_name(); let filename = entry.file_name();
let f_name = filename.to_string_lossy(); let f_name = filename.to_string_lossy();
if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) { if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) {
let mut load_path = path.clone(); let mut load_path = path.clone();
trace!("Found {:?}", f_name);
load_path.push(f_name.to_string()); load_path.push(f_name.to_string());
load_plugin(&load_path, context)?; load_plugin(&load_path, context)?;
} }
@ -504,7 +513,9 @@ fn classify_command(
trace!(target: "nu::build_pipeline", "classifying {:?}", config); 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 { Ok(ClassifiedCommand::Internal(InternalCommand {
command, command,

View File

@ -122,10 +122,11 @@ impl InternalCommand {
self.name_span.clone(), self.name_span.clone(),
context.source_map.clone(), context.source_map.clone(),
self.args, self.args,
source, &source,
objects, objects,
); );
let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result);
let mut result = result.values; let mut result = result.values;
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();

View File

@ -422,6 +422,25 @@ pub enum CommandAction {
LeaveShell, 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)] #[derive(Debug, Serialize, Deserialize)]
pub enum ReturnSuccess { pub enum ReturnSuccess {
Value(Tagged<Value>), Value(Tagged<Value>),
@ -430,6 +449,16 @@ pub enum ReturnSuccess {
pub type ReturnValue = Result<ReturnSuccess, ShellError>; pub type ReturnValue = Result<ReturnSuccess, ShellError>;
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<Tagged<Value>> for ReturnValue { impl From<Tagged<Value>> for ReturnValue {
fn from(input: Tagged<Value>) -> ReturnValue { fn from(input: Tagged<Value>) -> ReturnValue {
Ok(ReturnSuccess::Value(input)) Ok(ReturnSuccess::Value(input))
@ -469,7 +498,7 @@ pub trait WholeStreamCommand: Send + Sync {
Signature { Signature {
name: self.name().to_string(), name: self.name().to_string(),
positional: vec![], positional: vec![],
rest_positional: true, rest_positional: None,
named: indexmap::IndexMap::new(), named: indexmap::IndexMap::new(),
is_filter: true, is_filter: true,
} }
@ -491,7 +520,7 @@ pub trait PerItemCommand: Send + Sync {
Signature { Signature {
name: self.name().to_string(), name: self.name().to_string(),
positional: vec![], positional: vec![],
rest_positional: true, rest_positional: None,
named: indexmap::IndexMap::new(), named: indexmap::IndexMap::new(),
is_filter: true, is_filter: true,
} }

View File

@ -22,18 +22,21 @@ impl WholeStreamCommand for Get {
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
args.process(registry, get)?.run() args.process(registry, get)?.run()
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("get").rest() Signature::build("get").rest(SyntaxType::Member)
} }
} }
fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value>, ShellError> { fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
let mut current = obj; let mut current = Some(obj);
for p in path.split(".") { for p in path.split(".") {
match current.get_data_by_key(p) { if let Some(obj) = current {
Some(v) => current = v, current = match obj.get_data_by_key(p) {
None => { Some(v) => Some(v),
None =>
// Before we give up, see if they gave us a path that matches a field name by itself // 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) { match obj.get_data_by_key(&path.item) {
Some(v) => return Ok(v.clone()), Some(v) => return Ok(v.clone()),
None => { None => {
@ -47,8 +50,13 @@ fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value
} }
} }
} }
}
Ok(current.clone()) match current {
Some(v) => Ok(v.clone()),
None => Ok(Value::nothing().tagged(obj.tag)),
}
// Ok(current.clone())
} }
pub fn get( pub fn get(

View File

@ -27,7 +27,7 @@ impl PerItemCommand for Mkdir {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("mkdir").rest() Signature::build("mkdir").rest(SyntaxType::Path)
} }
} }

View File

@ -44,7 +44,8 @@ fn run(call_info: &CallInfo, shell_manager: &ShellManager) -> Result<OutputStrea
{ {
file => file, file => 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 path_span = path.span();
let name_span = call_info.name_span; let name_span = call_info.name_span;
let has_raw = call_info.args.has("raw"); let has_raw = call_info.args.has("raw");
@ -426,14 +427,16 @@ pub fn parse_string_as_value(
name_span: Span, name_span: Span,
) -> Result<Tagged<Value>, ShellError> { ) -> Result<Tagged<Value>, ShellError> {
match extension { match extension {
Some(x) if x == "csv" => crate::commands::from_csv::from_csv_string_to_value( Some(x) if x == "csv" => {
contents, crate::commands::from_csv::from_csv_string_to_value(contents, false, contents_tag)
false,
contents_tag,
)
.map_err(move |_| { .map_err(move |_| {
ShellError::labeled_error("Could not open as CSV", "could not open as CSV", name_span) ShellError::labeled_error(
}), "Could not open as CSV",
"could not open as CSV",
name_span,
)
})
}
Some(x) if x == "toml" => { Some(x) if x == "toml" => {
crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err( crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err(
move |_| { move |_| {

View File

@ -17,7 +17,7 @@ impl WholeStreamCommand for Pick {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("pick").rest() Signature::build("pick").rest(SyntaxType::Any)
} }
fn run( fn run(

View File

@ -3,6 +3,7 @@ use crate::errors::ShellError;
use crate::parser::registry; use crate::parser::registry;
use crate::prelude::*; use crate::prelude::*;
use derive_new::new; use derive_new::new;
use log::trace;
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
@ -64,6 +65,8 @@ pub fn filter_plugin(
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
trace!("filter_plugin :: {}", path);
let args = args.evaluate_once(registry)?; let args = args.evaluate_once(registry)?;
let mut child = std::process::Command::new(path) let mut child = std::process::Command::new(path)
@ -80,6 +83,8 @@ pub fn filter_plugin(
let call_info = args.call_info.clone(); let call_info = args.call_info.clone();
trace!("filtering :: {:?}", call_info);
let stream = bos let stream = bos
.chain(args.input.values) .chain(args.input.values)
.chain(eos) .chain(eos)
@ -95,7 +100,14 @@ pub fn filter_plugin(
let request = JsonRpc::new("begin_filter", call_info.clone()); let request = JsonRpc::new("begin_filter", call_info.clone());
let request_raw = serde_json::to_string(&request).unwrap(); 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(); let mut input = String::new();
match reader.read_line(&mut input) { match reader.read_line(&mut input) {
@ -140,7 +152,15 @@ pub fn filter_plugin(
let mut reader = BufReader::new(stdout); let mut reader = BufReader::new(stdout);
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]); let request: JsonRpc<std::vec::Vec<Value>> = 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 _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
let mut input = String::new(); let mut input = String::new();

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for Reject {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("reject").rest() Signature::build("reject").rest(SyntaxType::Member)
} }
} }

View File

@ -4,13 +4,19 @@ use crate::prelude::*;
pub struct SortBy; pub struct SortBy;
#[derive(Deserialize)]
pub struct SortByArgs {
rest: Vec<Tagged<String>>,
reverse: bool,
}
impl WholeStreamCommand for SortBy { impl WholeStreamCommand for SortBy {
fn run( fn run(
&self, &self,
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
sort_by(args, registry) args.process(registry, sort_by)?.run()
} }
fn name(&self) -> &str { fn name(&self) -> &str {
@ -18,43 +24,32 @@ impl WholeStreamCommand for SortBy {
} }
fn signature(&self) -> Signature { 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<OutputStream, ShellError> { fn sort_by(
let args = args.evaluate_once(registry)?; SortByArgs { reverse, rest }: SortByArgs,
let (input, args) = args.parts(); mut context: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::new(async_stream_block! {
let mut vec = context.input.drain_vec().await;
let fields: Result<Vec<_>, _> = args
.positional
.iter()
.flatten()
.map(|a| a.as_string())
.collect();
let fields = fields?;
let output = input.values.collect::<Vec<_>>();
let reverse = args.has("reverse");
let output = output.map(move |mut vec| {
let calc_key = |item: &Tagged<Value>| { let calc_key = |item: &Tagged<Value>| {
fields rest.iter()
.iter()
.map(|f| item.get_data_by_key(f).map(|i| i.clone())) .map(|f| item.get_data_by_key(f).map(|i| i.clone()))
.collect::<Vec<Option<Tagged<Value>>>>() .collect::<Vec<Option<Tagged<Value>>>>()
}; };
if reverse { if reverse {
vec.sort_by_cached_key(|item| { vec.sort_by_cached_key(|item| std::cmp::Reverse(calc_key(item)));
std::cmp::Reverse(calc_key(item))
});
} else { } else {
vec.sort_by_cached_key(calc_key); vec.sort_by_cached_key(calc_key);
} };
vec.into_iter().collect::<VecDeque<_>>() for item in vec {
}); yield item.into();
}
Ok(output.flatten_stream().from_input_stream()) }))
} }

View File

@ -28,7 +28,7 @@ impl WholeStreamCommand for SplitColumn {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("split-column") Signature::build("split-column")
.required("separator", SyntaxType::Any) .required("separator", SyntaxType::Any)
.rest() .rest(SyntaxType::Member)
} }
} }

View File

@ -121,7 +121,7 @@ impl Context {
name_span: Span, name_span: Span,
source_map: SourceMap, source_map: SourceMap,
args: hir::Call, args: hir::Call,
source: Text, source: &Text,
input: InputStream, input: InputStream,
) -> OutputStream { ) -> OutputStream {
let command_args = self.command_args(args, input, source, source_map, name_span); let command_args = self.command_args(args, input, source, source_map, name_span);
@ -131,13 +131,13 @@ impl Context {
fn call_info( fn call_info(
&self, &self,
args: hir::Call, args: hir::Call,
source: Text, source: &Text,
source_map: SourceMap, source_map: SourceMap,
name_span: Span, name_span: Span,
) -> UnevaluatedCallInfo { ) -> UnevaluatedCallInfo {
UnevaluatedCallInfo { UnevaluatedCallInfo {
args, args,
source, source: source.clone(),
source_map, source_map,
name_span, name_span,
} }
@ -147,7 +147,7 @@ impl Context {
&self, &self,
args: hir::Call, args: hir::Call,
input: InputStream, input: InputStream,
source: Text, source: &Text,
source_map: SourceMap, source_map: SourceMap,
name_span: Span, name_span: Span,
) -> CommandArgs { ) -> CommandArgs {

View File

@ -5,6 +5,7 @@ use ansi_term::Color;
use derive_new::new; use derive_new::new;
use language_reporting::{Diagnostic, Label, Severity}; use language_reporting::{Diagnostic, Label, Severity};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Description { pub enum Description {
@ -56,6 +57,12 @@ pub struct ShellError {
cause: Option<Box<ProximateShellError>>, cause: Option<Box<ProximateShellError>>,
} }
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 { impl serde::de::Error for ShellError {
fn custom<T>(msg: T) -> Self fn custom<T>(msg: T) -> Self
where where
@ -335,6 +342,7 @@ pub enum ProximateShellError {
right: Tagged<String>, right: Tagged<String>,
}, },
} }
impl ProximateShellError { impl ProximateShellError {
fn start(self) -> ShellError { fn start(self) -> ShellError {
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)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellDiagnostic { pub struct ShellDiagnostic {
crate diagnostic: Diagnostic<Span>, crate diagnostic: Diagnostic<Span>,

View File

@ -39,6 +39,7 @@ crate fn evaluate_baseline_expr(
) -> Result<Tagged<Value>, ShellError> { ) -> Result<Tagged<Value>, ShellError> {
match &expr.item { match &expr.item {
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)), 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::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()),
RawExpression::Variable(var) => evaluate_reference(var, scope, source), RawExpression::Variable(var) => evaluate_reference(var, scope, source),
RawExpression::ExternalCommand(external) => evaluate_external(external, scope, source), RawExpression::ExternalCommand(external) => evaluate_external(external, scope, source),

View File

@ -68,7 +68,7 @@ impl TableView {
for head in 0..headers.len() { for head in 0..headers.len() {
let mut current_row_max = 0; let mut current_row_max = 0;
for row in 0..values.len() { 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(); current_row_max = entries[row][head].len();
} }
} }

View File

@ -30,6 +30,7 @@ pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue};
pub use crate::context::{SourceMap, SpanSource}; pub use crate::context::{SourceMap, SpanSource};
pub use crate::env::host::BasicHost; pub use crate::env::host::BasicHost;
pub use crate::object::base::OF64; pub use crate::object::base::OF64;
pub use crate::parser::hir::SyntaxType;
pub use crate::plugin::{serve_plugin, Plugin}; pub use crate::plugin::{serve_plugin, Plugin};
pub use crate::utils::{AbsolutePath, RelativePath}; pub use crate::utils::{AbsolutePath, RelativePath};
pub use cli::cli; pub use cli::cli;

View File

@ -285,6 +285,7 @@ impl Value {
} }
} }
// TODO: This is basically a legacy construct, I think
pub fn data_descriptors(&self) -> Vec<String> { pub fn data_descriptors(&self) -> Vec<String> {
match self { match self {
Value::Primitive(_) => vec![], Value::Primitive(_) => vec![],
@ -542,6 +543,10 @@ impl Value {
Value::Primitive(Primitive::String(s.into())) Value::Primitive(Primitive::String(s.into()))
} }
pub fn path(s: impl Into<PathBuf>) -> Value {
Value::Primitive(Primitive::Path(s.into()))
}
pub fn bytes(s: impl Into<u64>) -> Value { pub fn bytes(s: impl Into<u64>) -> Value {
Value::Primitive(Primitive::Bytes(s.into())) Value::Primitive(Primitive::Bytes(s.into()))
} }
@ -577,6 +582,18 @@ impl Value {
} }
} }
impl Tagged<Value> {
crate fn as_path(&self) -> Result<PathBuf, ShellError> {
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<Tag>) -> Tagged<Value> { crate fn select_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Tagged<Value> {
let mut out = TaggedDictBuilder::new(tag); let mut out = TaggedDictBuilder::new(tag);

View File

@ -2,7 +2,6 @@ use crate::object::base as value;
use crate::parser::hir; use crate::parser::hir;
use crate::prelude::*; use crate::prelude::*;
use log::trace; use log::trace;
use std::path::PathBuf;
pub trait ExtractType: Sized { pub trait ExtractType: Sized {
fn extract(value: &Tagged<Value>) -> Result<Self, ShellError>; fn extract(value: &Tagged<Value>) -> Result<Self, ShellError>;
@ -196,9 +195,9 @@ impl ExtractType for std::path::PathBuf {
match &value { match &value {
Tagged { 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())), other => Err(ShellError::type_error("Path", other.tagged_type_name())),
} }
} }

View File

@ -11,16 +11,22 @@ use derive_new::new;
use getset::Getters; use getset::Getters;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::path::PathBuf;
use crate::evaluate::Scope; use crate::evaluate::Scope;
crate use self::baseline_parse::{baseline_parse_single_token, baseline_parse_token_as_string}; crate use self::baseline_parse::{
crate use self::baseline_parse_tokens::{baseline_parse_next_expr, SyntaxType, TokensIterator}; 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::binary::Binary;
crate use self::external_command::ExternalCommand; crate use self::external_command::ExternalCommand;
crate use self::named::NamedArguments; crate use self::named::NamedArguments;
crate use self::path::Path; crate use self::path::Path;
pub use self::baseline_parse_tokens::SyntaxType;
pub fn path(head: impl Into<Expression>, tail: Vec<Tagged<impl Into<String>>>) -> Path { pub fn path(head: impl Into<Expression>, tail: Vec<Tagged<impl Into<String>>>) -> Path {
Path::new( Path::new(
head.into(), head.into(),
@ -68,6 +74,8 @@ impl ToDebug for Call {
write!(f, "{}", named.debug(source))?; write!(f, "{}", named.debug(source))?;
} }
write!(f, ")")?;
Ok(()) Ok(())
} }
} }
@ -81,6 +89,7 @@ pub enum RawExpression {
Block(Vec<Expression>), Block(Vec<Expression>),
List(Vec<Expression>), List(Vec<Expression>),
Path(Box<Path>), Path(Box<Path>),
FilePath(PathBuf),
ExternalCommand(ExternalCommand), ExternalCommand(ExternalCommand),
#[allow(unused)] #[allow(unused)]
@ -105,6 +114,7 @@ impl RawExpression {
match self { match self {
RawExpression::Literal(literal) => literal.type_name(), RawExpression::Literal(literal) => literal.type_name(),
RawExpression::Synthetic(synthetic) => synthetic.type_name(), RawExpression::Synthetic(synthetic) => synthetic.type_name(),
RawExpression::FilePath(..) => "filepath",
RawExpression::Variable(..) => "variable", RawExpression::Variable(..) => "variable",
RawExpression::List(..) => "list", RawExpression::List(..) => "list",
RawExpression::Binary(..) => "binary", RawExpression::Binary(..) => "binary",
@ -141,6 +151,10 @@ impl Expression {
) )
} }
crate fn file_path(path: impl Into<PathBuf>, outer: impl Into<Span>) -> Expression {
Tagged::from_simple_spanned_item(RawExpression::FilePath(path.into()), outer.into())
}
crate fn bare(span: impl Into<Span>) -> Expression { crate fn bare(span: impl Into<Span>) -> Expression {
Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into()) Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into())
} }
@ -170,7 +184,8 @@ impl Expression {
impl ToDebug for Expression { impl ToDebug for Expression {
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
match self.item() { 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::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s),
RawExpression::Variable(Variable::It(_)) => write!(f, "$it"), RawExpression::Variable(Variable::It(_)) => write!(f, "$it"),
RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)), RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)),

View File

@ -1,5 +1,7 @@
use crate::context::Context;
use crate::parser::{hir, RawToken, Token}; use crate::parser::{hir, RawToken, Token};
use crate::Text; use crate::Text;
use std::path::PathBuf;
pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression { pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression {
match *token.item() { 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 { pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expression {
match *token.item() { match *token.item() {
RawToken::Variable(span) if span.slice(source) == "it" => { 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()), 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())
}

View File

@ -1,8 +1,11 @@
use crate::context::Context;
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::parser::registry::CommandRegistry;
use crate::parser::{ use crate::parser::{
hir, 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, DelimitedNode, Delimiter, PathNode, RawToken, TokenNode,
}; };
use crate::{Span, Tag, Tagged, TaggedItem, Text}; use crate::{Span, Tag, Tagged, TaggedItem, Text};
@ -12,8 +15,9 @@ use serde::{Deserialize, Serialize};
pub fn baseline_parse_tokens( pub fn baseline_parse_tokens(
token_nodes: &mut TokensIterator<'_>, token_nodes: &mut TokensIterator<'_>,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
syntax_type: SyntaxType,
) -> Result<Vec<hir::Expression>, ShellError> { ) -> Result<Vec<hir::Expression>, ShellError> {
let mut exprs: Vec<hir::Expression> = vec![]; let mut exprs: Vec<hir::Expression> = vec![];
@ -22,7 +26,7 @@ pub fn baseline_parse_tokens(
break; 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); exprs.push(expr);
} }
@ -35,7 +39,10 @@ pub enum SyntaxType {
Any, Any,
List, List,
Literal, Literal,
String,
Member,
Variable, Variable,
Number,
Path, Path,
Binary, Binary,
Block, Block,
@ -44,7 +51,7 @@ pub enum SyntaxType {
pub fn baseline_parse_next_expr( pub fn baseline_parse_next_expr(
tokens: &mut TokensIterator, tokens: &mut TokensIterator,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
syntax_type: SyntaxType, syntax_type: SyntaxType,
) -> Result<hir::Expression, ShellError> { ) -> Result<hir::Expression, ShellError> {
@ -56,7 +63,7 @@ pub fn baseline_parse_next_expr(
match (syntax_type, next) { match (syntax_type, next) {
(SyntaxType::Path, TokenNode::Token(token)) => { (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) => { (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(); let possible_op = tokens.peek();
@ -88,7 +135,7 @@ pub fn baseline_parse_next_expr(
op.span(), 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 // 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( pub fn baseline_parse_semantic_token(
token: &TokenNode, token: &TokenNode,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
) -> Result<hir::Expression, ShellError> { ) -> Result<hir::Expression, ShellError> {
match token { match token {
TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)), TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)),
TokenNode::Call(_call) => unimplemented!(), 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::Pipeline(_pipeline) => unimplemented!(),
TokenNode::Operator(_op) => unreachable!(), TokenNode::Operator(_op) => unreachable!(),
TokenNode::Flag(_flag) => Err(ShellError::unimplemented( TokenNode::Flag(_flag) => Err(ShellError::unimplemented(
@ -191,20 +238,24 @@ pub fn baseline_parse_semantic_token(
TokenNode::Member(_span) => unreachable!(), TokenNode::Member(_span) => unreachable!(),
TokenNode::Whitespace(_span) => unreachable!(), TokenNode::Whitespace(_span) => unreachable!(),
TokenNode::Error(error) => Err(*error.item.clone()), 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( pub fn baseline_parse_delimited(
token: &Tagged<DelimitedNode>, token: &Tagged<DelimitedNode>,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
) -> Result<hir::Expression, ShellError> { ) -> Result<hir::Expression, ShellError> {
match token.delimiter() { match token.delimiter() {
Delimiter::Brace => { Delimiter::Brace => {
let children = token.children(); let children = token.children();
let exprs = let exprs = baseline_parse_tokens(
baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; &mut TokensIterator::new(children),
context,
source,
SyntaxType::Any,
)?;
let expr = hir::RawExpression::Block(exprs); let expr = hir::RawExpression::Block(exprs);
Ok(Tagged::from_simple_spanned_item(expr, token.span())) Ok(Tagged::from_simple_spanned_item(expr, token.span()))
@ -212,8 +263,12 @@ pub fn baseline_parse_delimited(
Delimiter::Paren => unimplemented!(), Delimiter::Paren => unimplemented!(),
Delimiter::Square => { Delimiter::Square => {
let children = token.children(); let children = token.children();
let exprs = let exprs = baseline_parse_tokens(
baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?; &mut TokensIterator::new(children),
context,
source,
SyntaxType::Any,
)?;
let expr = hir::RawExpression::List(exprs); let expr = hir::RawExpression::List(exprs);
Ok(expr.tagged(Tag::unknown_origin(token.span()))) Ok(expr.tagged(Tag::unknown_origin(token.span())))
@ -223,10 +278,10 @@ pub fn baseline_parse_delimited(
pub fn baseline_parse_path( pub fn baseline_parse_path(
token: &Tagged<PathNode>, token: &Tagged<PathNode>,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
) -> Result<hir::Expression, ShellError> { ) -> Result<hir::Expression, ShellError> {
let head = baseline_parse_semantic_token(token.head(), registry, source)?; let head = baseline_parse_semantic_token(token.head(), context, source)?;
let mut tail = vec![]; let mut tail = vec![];

View File

@ -27,7 +27,7 @@ impl ToDebug for NamedArguments {
for (name, value) in &self.named { for (name, value) in &self.named {
match value { match value {
NamedValue::AbsentSwitch => continue, NamedValue::AbsentSwitch => continue,
NamedValue::PresentSwitch(span) => write!(f, " {}", span.slice(source))?, NamedValue::PresentSwitch(span) => write!(f, " --{}", span.slice(source))?,
NamedValue::AbsentValue => continue, NamedValue::AbsentValue => continue,
NamedValue::Value(expr) => write!(f, " --{} {}", name, expr.debug(source))?, NamedValue::Value(expr) => write!(f, " --{} {}", name, expr.debug(source))?,
} }

View File

@ -540,6 +540,8 @@ fn is_start_bare_char(c: char) -> bool {
'@' => true, '@' => true,
'*' => true, '*' => true,
'?' => true, '?' => true,
'~' => true,
'+' => true,
_ => false, _ => false,
} }
} }
@ -557,6 +559,8 @@ fn is_bare_char(c: char) -> bool {
'*' => true, '*' => true,
'?' => true, '?' => true,
'=' => true, '=' => true,
'~' => true,
'+' => true,
_ => false, _ => false,
} }
} }

View File

@ -1,5 +1,6 @@
use crate::context::Context;
use crate::errors::{ArgumentError, ShellError}; 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::{baseline_parse_tokens, CallNode};
use crate::parser::{ use crate::parser::{
hir::{self, NamedArguments}, hir::{self, NamedArguments},
@ -10,7 +11,7 @@ use log::trace;
pub fn parse_command( pub fn parse_command(
config: &Signature, config: &Signature,
registry: &CommandRegistry, context: &Context,
call: &Tagged<CallNode>, call: &Tagged<CallNode>,
source: &Text, source: &Text,
) -> Result<hir::Call, ShellError> { ) -> Result<hir::Call, ShellError> {
@ -31,7 +32,7 @@ pub fn parse_command(
.collect() .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)), None => Ok(hir::Call::new(Box::new(head), None, None)),
Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)), Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)),
} }
@ -63,7 +64,7 @@ fn parse_command_head(head: &TokenNode) -> Result<hir::Expression, ShellError> {
fn parse_command_tail( fn parse_command_tail(
config: &Signature, config: &Signature,
registry: &CommandRegistry, context: &Context,
tail: Option<Vec<TokenNode>>, tail: Option<Vec<TokenNode>>,
source: &Text, source: &Text,
command_span: Span, command_span: Span,
@ -101,7 +102,7 @@ fn parse_command_tail(
} }
let expr = let expr =
hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?; hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?;
tail.restart(); tail.restart();
named.insert_mandatory(name, expr); 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(); tail.restart();
named.insert_optional(name, Some(expr)); 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); positional.push(result);
} }
trace_remaining("after positional", tail.clone(), source); trace_remaining("after positional", tail.clone(), source);
// TODO: Only do this if rest params are specified if let Some(syntax_type) = config.rest_positional {
let remainder = baseline_parse_tokens(tail, registry, source)?; let remainder = baseline_parse_tokens(tail, context, source, syntax_type)?;
positional.extend(remainder); positional.extend(remainder);
}
trace_remaining("after rest", tail.clone(), source); trace_remaining("after rest", tail.clone(), source);
@ -180,6 +182,8 @@ fn parse_command_tail(
positional => Some(positional), positional => Some(positional),
}; };
// TODO: Error if extra unconsumed positional arguments
let named = match named { let named = match named {
named if named.named.is_empty() => None, named if named.named.is_empty() => None,
named => Some(named), named => Some(named),

View File

@ -74,8 +74,8 @@ pub struct Signature {
pub name: String, pub name: String,
#[new(default)] #[new(default)]
pub positional: Vec<PositionalType>, pub positional: Vec<PositionalType>,
#[new(value = "false")] #[new(value = "None")]
pub rest_positional: bool, pub rest_positional: Option<SyntaxType>,
#[new(default)] #[new(default)]
pub named: IndexMap<String, NamedType>, pub named: IndexMap<String, NamedType>,
#[new(value = "false")] #[new(value = "false")]
@ -130,8 +130,8 @@ impl Signature {
self self
} }
pub fn rest(mut self) -> Signature { pub fn rest(mut self, ty: SyntaxType) -> Signature {
self.rest_positional = true; self.rest_positional = Some(ty);
self self
} }
} }
@ -279,10 +279,10 @@ impl Signature {
crate fn parse_args( crate fn parse_args(
&self, &self,
call: &Tagged<CallNode>, call: &Tagged<CallNode>,
registry: &CommandRegistry, context: &Context,
source: &Text, source: &Text,
) -> Result<hir::Call, ShellError> { ) -> Result<hir::Call, ShellError> {
let args = parse_command(self, registry, call, source)?; let args = parse_command(self, context, call, source)?;
trace!("parsed args: {:?}", args); trace!("parsed args: {:?}", args);

View File

@ -1,7 +1,6 @@
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
ShellError, Signature, Tagged, Value, SyntaxType, Tagged, Value,
}; };
struct Add { struct Add {
@ -43,17 +42,12 @@ impl Add {
impl Plugin for Add { impl Plugin for Add {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("add")
name: "add".to_string(), .required("Field", SyntaxType::String)
positional: vec![ .required("Value", SyntaxType::String)
PositionalType::mandatory_any("Field"), .rest(SyntaxType::String).filter())
PositionalType::mandatory_any("Value"),
],
is_filter: true,
named: IndexMap::new(),
rest_positional: true,
})
} }
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
if let Some(args) = call_info.args.positional { if let Some(args) = call_info.args.positional {
match &args[0] { match &args[0] {

View File

@ -1,9 +1,6 @@
#![feature(option_flattening)] #![feature(option_flattening)]
use crossterm::{cursor, terminal, Attribute, RawScreen}; use crossterm::{cursor, terminal, Attribute, RawScreen};
use indexmap::IndexMap; use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, SpanSource, Tagged, Value};
use nu::{
serve_plugin, CallInfo, NamedType, Plugin, ShellError, Signature, SpanSource, Tagged, Value,
};
use pretty_hex::*; use pretty_hex::*;
struct BinaryView; struct BinaryView;
@ -16,15 +13,7 @@ impl BinaryView {
impl Plugin for BinaryView { impl Plugin for BinaryView {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
let mut named = IndexMap::new(); Ok(Signature::build("binaryview").switch("lores"))
named.insert("lores".to_string(), NamedType::Switch);
Ok(Signature {
name: "binaryview".to_string(),
positional: vec![],
is_filter: false,
named,
rest_positional: false,
})
} }
fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) { fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) {

View File

@ -1,7 +1,6 @@
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
ShellError, Signature, Tagged, Value, SyntaxType, Tagged, Value,
}; };
struct Edit { struct Edit {
@ -42,17 +41,12 @@ impl Edit {
impl Plugin for Edit { impl Plugin for Edit {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("edit")
name: "edit".to_string(), .required("Field", SyntaxType::String)
positional: vec![ .required("Value", SyntaxType::String)
PositionalType::mandatory_any("Field"), .filter())
PositionalType::mandatory_any("Value"),
],
is_filter: true,
named: IndexMap::new(),
rest_positional: true,
})
} }
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
if let Some(args) = call_info.args.positional { if let Some(args) = call_info.args.positional {
match &args[0] { match &args[0] {

View File

@ -1,7 +1,6 @@
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
Signature, Tagged, TaggedItem, Value, SyntaxType, Tagged, TaggedItem, Value,
}; };
enum Action { enum Action {
@ -116,19 +115,14 @@ impl Inc {
impl Plugin for Inc { impl Plugin for Inc {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
let mut named = IndexMap::new(); Ok(Signature::build("inc")
named.insert("major".to_string(), NamedType::Switch); .switch("major")
named.insert("minor".to_string(), NamedType::Switch); .switch("minor")
named.insert("patch".to_string(), NamedType::Switch); .switch("patch")
.rest(SyntaxType::String)
Ok(Signature { .filter())
name: "inc".to_string(),
positional: vec![],
is_filter: true,
named,
rest_positional: true,
})
} }
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
if call_info.args.has("major") { if call_info.args.has("major") {
self.for_semver(SemVerAction::Major); self.for_semver(SemVerAction::Major);

View File

@ -1,7 +1,7 @@
use indexmap::IndexMap; use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
Tagged, Value, SyntaxType, Tagged, Value,
}; };
struct Skip { struct Skip {
@ -20,7 +20,7 @@ impl Plugin for Skip {
positional: vec![], positional: vec![],
is_filter: true, is_filter: true,
named: IndexMap::new(), named: IndexMap::new(),
rest_positional: true, rest_positional: Some(SyntaxType::Number),
}) })
} }
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {

View File

@ -1,7 +1,6 @@
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
Signature, Tagged, Value, SyntaxType, Tagged, Value,
}; };
use regex::Regex; use regex::Regex;
@ -40,10 +39,12 @@ impl Str {
let applied = match self.action.as_ref() { let applied = match self.action.as_ref() {
Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()), Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()),
Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()), Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()),
Some(Action::ToInteger) => match input.trim().parse::<i64>() { Some(Action::ToInteger) => match input.trim() {
other => match other.parse::<i64>() {
Ok(v) => Value::int(v), Ok(v) => Value::int(v),
Err(_) => Value::string(input), Err(_) => Value::string(input),
}, },
},
Some(Action::Replace(ref mode)) => match mode { Some(Action::Replace(ref mode)) => match mode {
ReplaceAction::Direct => Value::string(self.first_param()), ReplaceAction::Direct => Value::string(self.first_param()),
ReplaceAction::FindAndReplace => { ReplaceAction::FindAndReplace => {
@ -138,9 +139,7 @@ impl Str {
Some(ref f) => { Some(ref f) => {
let replacement = match value.item.get_data_by_path(value.tag(), f) { let replacement = match value.item.get_data_by_path(value.tag(), f) {
Some(result) => self.strutils(result.map(|x| x.clone()))?, Some(result) => self.strutils(result.map(|x| x.clone()))?,
None => { None => return Ok(Tagged::from_item(Value::nothing(), value.tag)),
return Err(ShellError::string("str could not find field to replace"))
}
}; };
match value match value
.item .item
@ -168,20 +167,14 @@ impl Str {
impl Plugin for Str { impl Plugin for Str {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
let mut named = IndexMap::new(); Ok(Signature::build("str")
named.insert("downcase".to_string(), NamedType::Switch); .switch("downcase")
named.insert("upcase".to_string(), NamedType::Switch); .switch("upcase")
named.insert("to-int".to_string(), NamedType::Switch); .switch("to-int")
named.insert("replace".to_string(), NamedType::Switch); .switch("replace")
named.insert("find-replace".to_string(), NamedType::Switch); .switch("find-replace")
.rest(SyntaxType::Member)
Ok(Signature { .filter())
name: "str".to_string(),
positional: vec![],
is_filter: true,
named,
rest_positional: true,
})
} }
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {

View File

@ -1,4 +1,3 @@
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
Tag, Tagged, Value, Tag, Tagged, Value,
@ -14,6 +13,7 @@ impl Sum {
fn sum(&mut self, value: Tagged<Value>) -> Result<(), ShellError> { fn sum(&mut self, value: Tagged<Value>) -> Result<(), ShellError> {
match value.item { match value.item {
Value::Primitive(Primitive::Nothing) => Ok(()),
Value::Primitive(Primitive::Int(i)) => { Value::Primitive(Primitive::Int(i)) => {
match self.total { match self.total {
Some(Tagged { Some(Tagged {
@ -64,14 +64,9 @@ impl Sum {
impl Plugin for Sum { impl Plugin for Sum {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("sum").filter())
name: "sum".to_string(),
positional: vec![],
is_filter: true,
named: IndexMap::new(),
rest_positional: true,
})
} }
fn begin_filter(&mut self, _: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, _: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![]) Ok(vec![])
} }

View File

@ -1,10 +1,9 @@
use futures::executor::block_on; use futures::executor::block_on;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use heim::{disk, memory, net}; use heim::{disk, memory, net};
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
Tag, Tagged, TaggedDictBuilder, Value, SyntaxType, Tag, Tagged, TaggedDictBuilder, Value,
}; };
use std::ffi::OsStr; use std::ffi::OsStr;
@ -251,14 +250,9 @@ async fn sysinfo(tag: Tag) -> Vec<Tagged<Value>> {
impl Plugin for Sys { impl Plugin for Sys {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("sys").rest(SyntaxType::Any))
name: "sys".to_string(),
positional: vec![],
is_filter: true,
named: IndexMap::new(),
rest_positional: true,
})
} }
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> { fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
Ok(block_on(sysinfo(Tag::unknown_origin(callinfo.name_span))) Ok(block_on(sysinfo(Tag::unknown_origin(callinfo.name_span)))
.into_iter() .into_iter()

View File

@ -2,7 +2,6 @@
use crossterm::{cursor, terminal, RawScreen}; use crossterm::{cursor, terminal, RawScreen};
use crossterm::{InputEvent, KeyEvent}; use crossterm::{InputEvent, KeyEvent};
use indexmap::IndexMap;
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ShellError, Signature, SourceMap, SpanSource, serve_plugin, CallInfo, Plugin, Primitive, ShellError, Signature, SourceMap, SpanSource,
Tagged, Value, Tagged, Value,
@ -29,13 +28,7 @@ impl TextView {
impl Plugin for TextView { impl Plugin for TextView {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("textview"))
name: "textview".to_string(),
positional: vec![],
is_filter: false,
named: IndexMap::new(),
rest_positional: false,
})
} }
fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) { fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) {

View File

@ -1,5 +1,4 @@
use derive_new::new; use derive_new::new;
use indexmap::IndexMap;
use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, Tagged, Value}; use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, Tagged, Value};
use ptree::item::StringItem; use ptree::item::StringItem;
use ptree::output::print_tree_with; use ptree::output::print_tree_with;
@ -81,13 +80,7 @@ struct TreeViewer;
impl Plugin for TreeViewer { impl Plugin for TreeViewer {
fn config(&mut self) -> Result<Signature, ShellError> { fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature { Ok(Signature::build("tree"))
name: "tree".to_string(),
positional: vec![],
is_filter: false,
named: IndexMap::new(),
rest_positional: true,
})
} }
fn sink(&mut self, _call_info: CallInfo, input: Vec<Tagged<Value>>) { fn sink(&mut self, _call_info: CallInfo, input: Vec<Tagged<Value>>) {

View File

@ -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::cli::MaybeOwned;
crate use crate::commands::command::{ crate use crate::commands::command::{
CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext, CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext,

View File

@ -72,12 +72,16 @@ impl Shell for FilesystemShell {
"filesystem".to_string() "filesystem".to_string()
} }
fn homedir(&self) -> Option<PathBuf> {
dirs::home_dir()
}
fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> { fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let cwd = self.path(); let cwd = self.path();
let mut full_path = PathBuf::from(self.path()); let mut full_path = PathBuf::from(self.path());
match &args.nth(0) { 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) => { Some(v) => {
let target = v.as_string()?; let target = v.as_path()?;
let path = PathBuf::from(self.path()); let path = PathBuf::from(self.path());
match dunce::canonicalize(path.join(target).as_path()) { match dunce::canonicalize(path.join(target).as_path()) {
Ok(p) => p, Ok(p) => p,

View File

@ -7,9 +7,11 @@ use crate::context::SourceMap;
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::prelude::*; use crate::prelude::*;
use crate::stream::OutputStream; use crate::stream::OutputStream;
use std::path::PathBuf;
pub trait Shell: std::fmt::Debug { pub trait Shell: std::fmt::Debug {
fn name(&self, source_map: &SourceMap) -> String; fn name(&self, source_map: &SourceMap) -> String;
fn homedir(&self) -> Option<PathBuf>;
fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>; fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>; fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
fn cp(&self, args: CopyArgs, name: Span, path: &str) -> Result<OutputStream, ShellError>; fn cp(&self, args: CopyArgs, name: Span, path: &str) -> Result<OutputStream, ShellError>;

View File

@ -9,6 +9,7 @@ use crate::shell::filesystem_shell::FilesystemShell;
use crate::shell::shell::Shell; use crate::shell::shell::Shell;
use crate::stream::OutputStream; use crate::stream::OutputStream;
use std::error::Error; use std::error::Error;
use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -102,16 +103,24 @@ impl ShellManager {
self.set_path(self.path()); self.set_path(self.path());
} }
pub fn homedir(&self) -> Option<PathBuf> {
let env = self.shells.lock().unwrap();
env[self.current_shell].homedir()
}
pub fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> { pub fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let env = self.shells.lock().unwrap(); let env = self.shells.lock().unwrap();
env[self.current_shell].ls(args) env[self.current_shell].ls(args)
} }
pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> { pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let env = self.shells.lock().unwrap(); let env = self.shells.lock().unwrap();
env[self.current_shell].cd(args) env[self.current_shell].cd(args)
} }
pub fn cp( pub fn cp(
&self, &self,
args: CopyArgs, args: CopyArgs,

View File

@ -69,6 +69,10 @@ impl Shell for ValueShell {
) )
} }
fn homedir(&self) -> Option<PathBuf> {
dirs::home_dir()
}
fn ls(&self, _args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> { fn ls(&self, _args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
Ok(self Ok(self
.members() .members()

View File

@ -105,7 +105,10 @@ impl FileStructure {
self.root = path.to_path_buf(); self.root = path.to_path_buf();
} }
pub fn paths_applying_with<F>(&mut self, to: F) -> Result<Vec<(PathBuf, PathBuf)>, Box<dyn std::error::Error>> pub fn paths_applying_with<F>(
&mut self,
to: F,
) -> Result<Vec<(PathBuf, PathBuf)>, Box<dyn std::error::Error>>
where where
F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>>, F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>>,
{ {
@ -155,6 +158,7 @@ impl FileStructure {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use super::{FileStructure, Res}; use super::{FileStructure, Res};
use std::path::PathBuf; use std::path::PathBuf;
@ -175,7 +179,8 @@ mod tests {
fn prepares_and_decorates_source_files_for_copying() { fn prepares_and_decorates_source_files_for_copying() {
let mut res = FileStructure::new(); 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!( assert_eq!(
res.resources, res.resources,

View File

@ -18,7 +18,7 @@ fn ls_lists_regular_files() {
nu!( nu!(
output, output,
cwd(&full_path), 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"); assert_eq!(output, "30");