forked from extern/nushell
Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
88f06c81b2 | |||
e6b315f05b | |||
01ef6b0732 | |||
c7e11a5a28 | |||
ce0231049e | |||
0f7b270740 | |||
72cf57dd99 | |||
e4fdb36511 | |||
2ffb14c7d0 | |||
eec94e4016 | |||
6412bfd58d | |||
522a828687 | |||
6b8c6dec0e | |||
2b0212880e | |||
a16a91ede8 | |||
c2a9bc3bf4 | |||
e5a79d09df | |||
7974e09eeb | |||
52d2d2b888 | |||
ee778d2b03 | |||
928188b18e | |||
59d516064c | |||
bd5836e25d | |||
e3da037b80 | |||
08a09e2273 | |||
85d6b24be3 | |||
ed583bd79b | |||
e0fc09ac52 | |||
38b2846024 | |||
57c62de66f | |||
dd4935fb23 | |||
18dd009ca8 | |||
c0dda36217 | |||
75b72f844e | |||
fbddc12c02 | |||
8e7e8c17e1 | |||
8ac9d781fd | |||
c86cf31aac | |||
2c513d1883 | |||
04702530a3 | |||
c9f424977e | |||
183c8407de | |||
d0618b0b32 | |||
c4daa2e40f | |||
0a198b9bd0 | |||
6a604491f5 |
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
cross build --target ${{ matrix.arch }} --release
|
||||
# leave only the executable file
|
||||
rm -rd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
|
||||
rm -frd target/${{ matrix.arch }}/release/{*/*,*.d,*.rlib,.fingerprint}
|
||||
find . -empty -delete
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
|
11
CONTRIBUTING.md
Normal file
11
CONTRIBUTING.md
Normal file
@ -0,0 +1,11 @@
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
||||
|
||||
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
|
||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||
<!--WIP-->
|
||||
|
944
Cargo.lock
generated
944
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "nu"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A new kind of shell"
|
||||
description = "A new type of shell"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@ -18,30 +18,28 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.12.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.12.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.12.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.12.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.12.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.12.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.12.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_average = { version = "0.12.0", path = "./crates/nu_plugin_average", optional=true }
|
||||
nu_plugin_binaryview = { version = "0.12.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.12.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.12.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.12.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.12.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.12.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_str = { version = "0.12.0", path = "./crates/nu_plugin_str", optional=true }
|
||||
nu_plugin_sys = { version = "0.12.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.12.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.12.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
nu-macros = { version = "0.12.0", path = "./crates/nu-macros" }
|
||||
nu-cli = { version = "0.13.0", path = "./crates/nu-cli" }
|
||||
nu-source = { version = "0.13.0", path = "./crates/nu-source" }
|
||||
nu-plugin = { version = "0.13.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.13.0", path = "./crates/nu-protocol" }
|
||||
nu-errors = { version = "0.13.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.13.0", path = "./crates/nu-parser" }
|
||||
nu-value-ext = { version = "0.13.0", path = "./crates/nu-value-ext" }
|
||||
nu_plugin_average = { version = "0.13.0", path = "./crates/nu_plugin_average", optional=true }
|
||||
nu_plugin_binaryview = { version = "0.13.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_fetch = { version = "0.13.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_inc = { version = "0.13.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.13.0", path = "./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version = "0.13.0", path = "./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version = "0.13.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_str = { version = "0.13.0", path = "./crates/nu_plugin_str", optional=true }
|
||||
nu_plugin_sys = { version = "0.13.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version = "0.13.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_tree = { version = "0.13.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||
|
||||
crossterm = { version = "0.16.0", optional = true }
|
||||
onig_sys = { version = "=69.1.0", optional = true }
|
||||
crossterm = { version = "0.17.2", optional = true }
|
||||
semver = { version = "0.9.0", optional = true }
|
||||
syntect = { version = "3.2.0", optional = true }
|
||||
syntect = { version = "4.1", default-features = false, features = ["default-fancy"], optional = true}
|
||||
url = { version = "2.1.1", optional = true }
|
||||
|
||||
clap = "2.33.0"
|
||||
@ -52,23 +50,22 @@ log = "0.4.8"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
nu-test-support = { version = "0.12.0", path = "./crates/nu-test-support" }
|
||||
nu-test-support = { version = "0.13.0", path = "./crates/nu-test-support" }
|
||||
|
||||
[build-dependencies]
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
nu-build = { version = "0.12.0", path = "./crates/nu-build" }
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
nu-build = { version = "0.13.0", path = "./crates/nu-build" }
|
||||
|
||||
[features]
|
||||
# Test executables
|
||||
test-bins = []
|
||||
|
||||
default = ["sys", "ps", "textview", "inc", "str"]
|
||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli"]
|
||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "post", "fetch", "clipboard-cli", "trash-support"]
|
||||
|
||||
# Default
|
||||
textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"]
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
@ -85,6 +82,7 @@ tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
|
||||
[[bin]]
|
||||
name = "fail"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nu-build"
|
||||
version = "0.12.0"
|
||||
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
|
||||
version = "0.13.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
edition = "2018"
|
||||
description = "Core build system for nushell"
|
||||
license = "MIT"
|
||||
@ -10,7 +10,7 @@ license = "MIT"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.103", features = ["derive"] }
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.44"
|
||||
toml = "0.5.5"
|
||||
serde_json = "1.0.51"
|
||||
toml = "0.5.6"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nu-cli"
|
||||
version = "0.12.0"
|
||||
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
|
||||
version = "0.13.0"
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
@ -10,14 +10,13 @@ license = "MIT"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-source = { version = "0.12.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.12.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.12.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.12.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.12.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.12.0", path = "../nu-value-ext" }
|
||||
nu-macros = { version = "0.12.0", path = "../nu-macros" }
|
||||
nu-test-support = { version = "0.12.0", path = "../nu-test-support" }
|
||||
nu-source = { version = "0.13.0", path = "../nu-source" }
|
||||
nu-plugin = { version = "0.13.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.13.0", path = "../nu-protocol" }
|
||||
nu-errors = { version = "0.13.0", path = "../nu-errors" }
|
||||
nu-parser = { version = "0.13.0", path = "../nu-parser" }
|
||||
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
|
||||
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = "1.2.1"
|
||||
@ -36,26 +35,23 @@ ctrlc = "3.1.4"
|
||||
derive-new = "0.5.8"
|
||||
dirs = "2.0.2"
|
||||
dunce = "1.0.0"
|
||||
filesize = "0.1.0"
|
||||
filesize = "0.2.0"
|
||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.4"
|
||||
futures_codec = "0.4"
|
||||
getset = "0.1.0"
|
||||
git2 = { version = "0.13.0", default_features = false }
|
||||
git2 = { version = "0.13.1", default_features = false }
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = "0.3"
|
||||
ichwh = "0.3.4"
|
||||
indexmap = { version = "1.3.2", features = ["serde-1"] }
|
||||
itertools = "0.9.0"
|
||||
language-reporting = "0.4.0"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
nom = "5.0.1"
|
||||
nom-tracable = "0.4.1"
|
||||
nom_locate = "1.0.0"
|
||||
num-bigint = { version = "0.2.6", features = ["serde"] }
|
||||
num-traits = "0.2.11"
|
||||
parking_lot = "0.10.0"
|
||||
@ -67,13 +63,13 @@ ptree = {version = "0.2" }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.10.0"
|
||||
rustyline = "6.0.0"
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
roxmltree = "0.10.1"
|
||||
rustyline = "6.1.1"
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.3"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.48"
|
||||
serde_json = "1.0.51"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
shellexpand = "2.0.0"
|
||||
@ -83,28 +79,30 @@ term = "0.5.2"
|
||||
termcolor = "1.1.0"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
toml = "0.5.6"
|
||||
trash = "1.0.0"
|
||||
typetag = "0.1.4"
|
||||
umask = "0.1"
|
||||
unicode-xid = "0.2.0"
|
||||
|
||||
trash = { version = "1.0.0", optional = true }
|
||||
clipboard = { version = "0.5", optional = true }
|
||||
starship = { version = "0.38.0", optional = true }
|
||||
starship = { version = "0.39.0", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.9"
|
||||
users = "0.10.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
features = ["bundled", "blob"]
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
|
||||
[build-dependencies]
|
||||
nu-build = { version = "0.12.0", path = "../nu-build" }
|
||||
nu-build = { version = "0.13.0", path = "../nu-build" }
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
|
||||
[features]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
clipboard-cli = ["clipboard"]
|
||||
trash-support = ["trash"]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::external::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::commands::classified::pipeline::run_pipeline;
|
||||
use crate::commands::plugin::JsonRpc;
|
||||
use crate::commands::plugin::{PluginCommand, PluginSink};
|
||||
use crate::commands::whole_stream_command;
|
||||
@ -10,13 +10,10 @@ use crate::prelude::*;
|
||||
use futures_codec::FramedRead;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::{
|
||||
ClassifiedCommand, ClassifiedPipeline, ExternalCommand, PipelineShape, SpannedToken,
|
||||
TokensIterator,
|
||||
};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, log_enabled, trace};
|
||||
use log::{debug, trace};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{
|
||||
self, config::Configurer, config::EditMode, At, Cmd, ColorMode, CompletionType, Config, Editor,
|
||||
@ -261,6 +258,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(What),
|
||||
whole_stream_command(Which),
|
||||
whole_stream_command(Debug),
|
||||
per_item_command(Alias),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
@ -307,6 +305,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Range),
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
per_item_command(Each),
|
||||
// Table manipulation
|
||||
whole_stream_command(Shuffle),
|
||||
whole_stream_command(Wrap),
|
||||
@ -345,6 +344,8 @@ pub fn create_default_context(
|
||||
whole_stream_command(FromYML),
|
||||
whole_stream_command(FromIcs),
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
whole_stream_command(RunExternalCommand),
|
||||
]);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -369,10 +370,64 @@ pub fn create_default_context(
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
pub async fn run_vec_of_pipelines(
|
||||
pipelines: Vec<String>,
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = crate::EnvironmentSyncer::new();
|
||||
let mut context = crate::create_default_context(&mut syncer)?;
|
||||
|
||||
let _ = crate::load_plugins(&mut context);
|
||||
|
||||
let cc = context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pipeline in pipelines {
|
||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut Context,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
||||
|
||||
@ -401,7 +456,9 @@ pub async fn run_pipeline_standalone(
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
std::process::exit(1);
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
@ -444,6 +501,34 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
@ -614,9 +699,9 @@ async fn process_line(
|
||||
Ok(line) => {
|
||||
let line = chomp_newline(line);
|
||||
|
||||
let result = match nu_parser::parse(&line) {
|
||||
let result = match nu_parser::lite_parse(&line, 0) {
|
||||
Err(err) => {
|
||||
return LineResult::Error(line.to_string(), err);
|
||||
return LineResult::Error(line.to_string(), err.into());
|
||||
}
|
||||
|
||||
Ok(val) => val,
|
||||
@ -625,9 +710,12 @@ async fn process_line(
|
||||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let pipeline = classify_pipeline(&result, &ctx, &Text::from(line));
|
||||
let classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
|
||||
if let Some(failure) = pipeline.failed {
|
||||
debug!("{:#?}", classified_block);
|
||||
//println!("{:#?}", pipeline);
|
||||
|
||||
if let Some(failure) = classified_block.failed {
|
||||
return LineResult::Error(line.to_string(), failure.into());
|
||||
}
|
||||
|
||||
@ -637,15 +725,43 @@ async fn process_line(
|
||||
// ...and it doesn't have any arguments
|
||||
// ...and we're in the CLI
|
||||
// ...then change to this directory
|
||||
if cli_mode && pipeline.commands.list.len() == 1 {
|
||||
if let ClassifiedCommand::External(ExternalCommand {
|
||||
if cli_mode
|
||||
&& classified_block.block.block.len() == 1
|
||||
&& classified_block.block.block[0].list.len() == 1
|
||||
{
|
||||
if let ClassifiedCommand::Internal(InternalCommand {
|
||||
ref name, ref args, ..
|
||||
}) = pipeline.commands.list[0]
|
||||
}) = classified_block.block.block[0].list[0]
|
||||
{
|
||||
if dunce::canonicalize(name).is_ok()
|
||||
&& PathBuf::from(name).is_dir()
|
||||
&& ichwh::which(name).await.unwrap_or(None).is_none()
|
||||
&& args.list.is_empty()
|
||||
let internal_name = name;
|
||||
let name = args
|
||||
.positional
|
||||
.as_ref()
|
||||
.and_then(|potionals| {
|
||||
potionals.get(0).map(|e| {
|
||||
if let Expression::Literal(Literal::String(ref s)) = e.expr {
|
||||
&s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or("");
|
||||
|
||||
if internal_name == "run_external"
|
||||
&& args
|
||||
.positional
|
||||
.as_ref()
|
||||
.map(|ref v| v.len() == 1)
|
||||
.unwrap_or(true)
|
||||
&& args
|
||||
.named
|
||||
.as_ref()
|
||||
.map(NamedArguments::is_empty)
|
||||
.unwrap_or(true)
|
||||
&& dunce::canonicalize(&name).is_ok()
|
||||
&& PathBuf::from(&name).is_dir()
|
||||
&& ichwh::which(&name).await.unwrap_or(None).is_none()
|
||||
{
|
||||
// Here we work differently if we're in Windows because of the expected Windows behavior
|
||||
#[cfg(windows)]
|
||||
@ -687,6 +803,7 @@ async fn process_line(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input_stream = if redirect_stdin {
|
||||
let file = futures::io::AllowStdIo::new(std::io::stdin());
|
||||
let stream = FramedRead::new(file, MaybeTextCodec).map(|line| {
|
||||
@ -707,13 +824,13 @@ async fn process_line(
|
||||
panic!("Internal error: could not read lines of text from stdin")
|
||||
}
|
||||
});
|
||||
Some(stream.to_input_stream())
|
||||
stream.to_input_stream()
|
||||
} else {
|
||||
None
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
match run_pipeline(pipeline, ctx, input_stream, line).await {
|
||||
Ok(Some(input)) => {
|
||||
match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await {
|
||||
Ok(input) => {
|
||||
// Running a pipeline gives us back a stream that we can then
|
||||
// work through. At the top level, we just want to pull on the
|
||||
// values to compute them.
|
||||
@ -724,9 +841,8 @@ async fn process_line(
|
||||
shell_manager: ctx.shell_manager.clone(),
|
||||
host: ctx.host.clone(),
|
||||
ctrl_c: ctx.ctrl_c.clone(),
|
||||
commands: ctx.registry.clone(),
|
||||
registry: ctx.registry.clone(),
|
||||
name: Tag::unknown(),
|
||||
source: Text::from(String::new()),
|
||||
};
|
||||
|
||||
if let Ok(mut output_stream) = crate::commands::autoview::autoview(context) {
|
||||
@ -749,7 +865,6 @@ async fn process_line(
|
||||
|
||||
LineResult::Success(line.to_string())
|
||||
}
|
||||
Ok(None) => LineResult::Success(line.to_string()),
|
||||
Err(err) => LineResult::Error(line.to_string(), err),
|
||||
}
|
||||
}
|
||||
@ -762,39 +877,32 @@ async fn process_line(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn classify_pipeline(
|
||||
pipeline: &SpannedToken,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> ClassifiedPipeline {
|
||||
let pipeline_list = vec![pipeline.clone()];
|
||||
let expand_context = context.expand_context(source);
|
||||
let mut iterator = TokensIterator::new(&pipeline_list, expand_context, pipeline.span());
|
||||
|
||||
let result = iterator.expand_infallible(PipelineShape);
|
||||
|
||||
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
|
||||
outln!("");
|
||||
let _ = ptree::print_tree(&iterator.expand_tracer().print(source.clone()));
|
||||
outln!("");
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, host: &dyn Host, source: &Text) {
|
||||
let diag = err.into_diagnostic();
|
||||
|
||||
let writer = host.err_termcolor();
|
||||
let mut source = source.to_string();
|
||||
source.push_str(" ");
|
||||
let files = nu_parser::Files::new(source);
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _ = language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag,
|
||||
&language_reporting::DefaultConfig,
|
||||
);
|
||||
});
|
||||
if let Some(diag) = err.into_diagnostic() {
|
||||
let writer = host.err_termcolor();
|
||||
let mut source = source.to_string();
|
||||
source.push_str(" ");
|
||||
let files = nu_parser::Files::new(source);
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _ = language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag,
|
||||
&language_reporting::DefaultConfig,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
||||
let context = crate::context::Context::basic().unwrap();
|
||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ pub(crate) mod macros;
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoview;
|
||||
@ -20,6 +21,7 @@ pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod edit;
|
||||
pub(crate) mod enter;
|
||||
@ -74,6 +76,8 @@ pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
pub(crate) mod rm;
|
||||
pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
@ -114,6 +118,7 @@ pub(crate) use command::{
|
||||
WholeStreamCommand,
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use calc::Calc;
|
||||
pub(crate) use compact::Compact;
|
||||
@ -124,6 +129,7 @@ pub(crate) use date::Date;
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use edit::Edit;
|
||||
pub(crate) mod kill;
|
||||
@ -184,6 +190,7 @@ pub(crate) use reject::Reject;
|
||||
pub(crate) use rename::Rename;
|
||||
pub(crate) use reverse::Reverse;
|
||||
pub(crate) use rm::Remove;
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
|
65
crates/nu-cli/src/commands/alias.rs
Normal file
65
crates/nu-cli/src/commands/alias.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
impl PerItemCommand for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "the name of the alias")
|
||||
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
||||
.required("block", SyntaxShape::Block, "the block to run on each row")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
_input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let call_info = call_info.clone();
|
||||
let stream = async_stream! {
|
||||
match (call_info.args.expect_nth(0)?, call_info.args.expect_nth(1)?, call_info.args.expect_nth(2)?) {
|
||||
(Value {value: UntaggedValue::Primitive(Primitive::String(name)), .. },
|
||||
Value { value: UntaggedValue::Table(list), .. },
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag
|
||||
}) => {
|
||||
let mut args: Vec<String> = vec![];
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
args.push(format!("${}", string));
|
||||
} else {
|
||||
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
|
||||
}
|
||||
}
|
||||
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
|
||||
}
|
||||
_ => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected `name [args] {block}",
|
||||
"needs a name, args, and a block",
|
||||
call_info.name_tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
@ -45,5 +45,5 @@ fn append(
|
||||
after.push_back(row);
|
||||
let after = futures::stream::iter(after);
|
||||
|
||||
Ok(OutputStream::from_input(input.values.chain(after)))
|
||||
Ok(OutputStream::from_input(input.chain(after)))
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@ -29,10 +29,9 @@ impl WholeStreamCommand for Autoview {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
autoview(RunnableContext {
|
||||
input: args.input,
|
||||
commands: registry.clone(),
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
source: args.call_info.source,
|
||||
ctrl_c: args.ctrl_c,
|
||||
name: args.call_info.name_tag,
|
||||
})
|
||||
@ -42,9 +41,8 @@ impl WholeStreamCommand for Autoview {
|
||||
pub struct RunnableContextWithoutInput {
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub source: Text,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub commands: CommandRegistry,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
@ -53,9 +51,8 @@ impl RunnableContextWithoutInput {
|
||||
let new_context = RunnableContextWithoutInput {
|
||||
shell_manager: context.shell_manager,
|
||||
host: context.host,
|
||||
source: context.source,
|
||||
ctrl_c: context.ctrl_c,
|
||||
commands: context.commands,
|
||||
registry: context.registry,
|
||||
name: context.name,
|
||||
};
|
||||
(context.input, new_context)
|
||||
@ -95,7 +92,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.commands);
|
||||
let result = table.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
}
|
||||
}
|
||||
@ -109,7 +106,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.commands);
|
||||
let result = text.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
@ -129,7 +126,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(UntaggedValue::string(s).into_value(Tag { anchor, span }));
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.commands);
|
||||
let result = text.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
@ -159,13 +156,19 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
} => {
|
||||
out!("{}", n);
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||
..
|
||||
} => {
|
||||
out!("{}", b);
|
||||
}
|
||||
|
||||
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
|
||||
if let Some(binary) = binary {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.commands);
|
||||
let result = binary.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
@ -251,7 +254,7 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.commands);
|
||||
let result = table.run(command_args, &context.registry);
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
@ -282,15 +285,16 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: hir::Call {
|
||||
head: Box::new(SpannedExpression::new(
|
||||
Expression::Literal(Literal::String(span)),
|
||||
Expression::Literal(Literal::String(String::new())),
|
||||
span,
|
||||
)),
|
||||
positional: None,
|
||||
named: None,
|
||||
span,
|
||||
is_last: true,
|
||||
},
|
||||
source: context.source.clone(),
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::empty(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_macros::signature;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Cd;
|
||||
@ -12,17 +11,11 @@ impl WholeStreamCommand for Cd {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
signature! {
|
||||
def cd {
|
||||
"the directory to change to"
|
||||
directory(optional Path) - "the directory to change to"
|
||||
}
|
||||
}
|
||||
// Signature::build("cd").optional(
|
||||
// "directory",
|
||||
// SyntaxShape::Path,
|
||||
// "the directory to change to",
|
||||
// )
|
||||
Signature::build("cd").optional(
|
||||
"directory",
|
||||
SyntaxShape::Path,
|
||||
"the directory to change to",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
96
crates/nu-cli/src/commands/classified/block.rs
Normal file
96
crates/nu-cli/src/commands/classified/block.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
//use crate::commands::classified::external::run_external_command;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for pipeline in &block.block {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => return Err(e),
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, scope).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
mut input: InputStream,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut iter = commands.list.clone().into_iter().peekable();
|
||||
|
||||
loop {
|
||||
let item: Option<ClassifiedCommand> = iter.next();
|
||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
||||
|
||||
input = match (item, next) {
|
||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Expr(expr)), _) => {
|
||||
run_expression_block(*expr, ctx, input, scope)?
|
||||
}
|
||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
||||
|
||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
||||
run_internal_command(left, ctx, input, scope)?
|
||||
}
|
||||
|
||||
(None, _) => break,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use derive_new::new;
|
||||
use nu_parser::hir;
|
||||
use nu_protocol::hir;
|
||||
|
||||
#[derive(new, Debug, Eq, PartialEq)]
|
||||
#[derive(new, Debug)]
|
||||
pub(crate) struct Command {
|
||||
pub(crate) args: hir::Call,
|
||||
}
|
||||
|
29
crates/nu-cli/src/commands/classified/expr.rs
Normal file
29
crates/nu-cli/src/commands/classified/expr.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
|
||||
use log::{log_enabled, trace};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
use nu_protocol::Scope;
|
||||
|
||||
pub(crate) fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||
}
|
||||
|
||||
let scope = scope.clone();
|
||||
let registry = context.registry().clone();
|
||||
let stream = input.map(move |row| {
|
||||
let scope = scope.clone().set_it(row);
|
||||
evaluate_baseline_expr(&expr, ®istry, &scope)
|
||||
});
|
||||
|
||||
Ok(stream.to_input_stream())
|
||||
}
|
@ -1,20 +1,22 @@
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::futures::ThreadedReceiver;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::executor::block_on_stream;
|
||||
use futures::stream::StreamExt;
|
||||
use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::commands::classified::external::ExternalArg;
|
||||
use nu_parser::ExternalCommand;
|
||||
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::as_column_path;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use nu_protocol::hir::ExternalCommand;
|
||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub enum StringOrBinary {
|
||||
String(String),
|
||||
@ -80,7 +82,7 @@ impl futures_codec::Decoder for MaybeTextCodec {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<String, ShellError> {
|
||||
pub fn nu_value_to_string(name_tag: &Tag, from: &Value) -> Result<String, ShellError> {
|
||||
match &from.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(i)) => Ok(i.to_string()),
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
@ -89,7 +91,7 @@ pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<Str
|
||||
unsupported => Err(ShellError::labeled_error(
|
||||
format!("needs string data (given: {})", unsupported.type_name()),
|
||||
"expected a string",
|
||||
&command.name_tag,
|
||||
name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@ -97,9 +99,10 @@ pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<Str
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: Option<InputStream>,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
if !did_find_command(&command.name).await {
|
||||
@ -110,265 +113,71 @@ pub(crate) async fn run_external_command(
|
||||
));
|
||||
}
|
||||
|
||||
if command.has_it_argument() || command.has_nu_argument() {
|
||||
run_with_iterator_arg(command, context, input, is_last)
|
||||
if command.has_it_argument() {
|
||||
run_with_iterator_arg(command, context, input, scope, is_last)
|
||||
} else {
|
||||
run_with_stdin(command, context, input, is_last)
|
||||
run_with_stdin(command, context, input, scope, is_last)
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_column_path_for_fetching_it_variable(
|
||||
argument: &ExternalArg,
|
||||
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||
// We have "$it.[contents of interest]"
|
||||
// and start slicing from "$it.[member+]"
|
||||
// ^ here.
|
||||
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
|
||||
|
||||
to_column_path(&key, &argument.tag)
|
||||
}
|
||||
|
||||
fn prepare_column_path_for_fetching_nu_variable(
|
||||
argument: &ExternalArg,
|
||||
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||
// We have "$nu.[contents of interest]"
|
||||
// and start slicing from "$nu.[member+]"
|
||||
// ^ here.
|
||||
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
|
||||
|
||||
to_column_path(&key, &argument.tag)
|
||||
}
|
||||
|
||||
fn to_column_path(
|
||||
path_members: &str,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
as_column_path(
|
||||
&UntaggedValue::Table(
|
||||
path_members
|
||||
.split('.')
|
||||
.map(|x| {
|
||||
let member = match x.parse::<u64>() {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
Err(_) => UntaggedValue::string(x),
|
||||
};
|
||||
|
||||
member.into_value(&tag)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.into_value(&tag),
|
||||
)
|
||||
}
|
||||
|
||||
fn run_with_iterator_arg(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: Option<InputStream>,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let mut inputs: InputStream = if let Some(input) = input {
|
||||
trace_stream!(target: "nu::trace_stream::external::it", "input" = input)
|
||||
} else {
|
||||
InputStream::empty()
|
||||
};
|
||||
let mut inputs: InputStream =
|
||||
trace_stream!(target: "nu::trace_stream::external::it", "input" = input);
|
||||
|
||||
let name_tag = command.name_tag.clone();
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
while let Some(value) = inputs.next().await {
|
||||
let name = command.name.clone();
|
||||
let name_tag = command.name_tag.clone();
|
||||
let home_dir = dirs::home_dir();
|
||||
let path = &path;
|
||||
let args = command.args.clone();
|
||||
// Evaluate the expressions into values, and from values into strings for each iteration
|
||||
let mut command_args = vec![];
|
||||
let scope = scope.clone().set_it(value);
|
||||
for arg in command.args.iter() {
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, &scope)?;
|
||||
command_args.push(nu_value_to_string(&name_tag, &value)?);
|
||||
}
|
||||
|
||||
let it_replacement = {
|
||||
if command.has_it_argument() {
|
||||
let empty_arg = ExternalArg {
|
||||
arg: "".to_string(),
|
||||
tag: name_tag.clone()
|
||||
};
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let arg = expand_tilde(arg.deref(), dirs::home_dir);
|
||||
|
||||
let key = args.iter()
|
||||
.find(|arg| arg.looks_like_it())
|
||||
.unwrap_or_else(|| &empty_arg);
|
||||
|
||||
if args.iter().all(|arg| !arg.is_it()) {
|
||||
let key = match prepare_column_path_for_fetching_it_variable(&key) {
|
||||
Ok(keypath) => keypath,
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match crate::commands::get::get_column_path(&key, &value) {
|
||||
Ok(field) => {
|
||||
match nu_value_to_string(&command, &field) {
|
||||
Ok(val) => Some(val),
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match nu_value_to_string(&command, &value) {
|
||||
Ok(val) => Some(val),
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let nu_replacement = {
|
||||
if command.has_nu_argument() {
|
||||
let empty_arg = ExternalArg {
|
||||
arg: "".to_string(),
|
||||
tag: name_tag.clone()
|
||||
};
|
||||
|
||||
let key = args.iter()
|
||||
.find(|arg| arg.looks_like_nu())
|
||||
.unwrap_or_else(|| &empty_arg);
|
||||
|
||||
let nu_var = match crate::evaluate::variables::nu(&name_tag) {
|
||||
Ok(variables) => variables,
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if args.iter().all(|arg| !arg.is_nu()) {
|
||||
let key = match prepare_column_path_for_fetching_nu_variable(&key) {
|
||||
Ok(keypath) => keypath,
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match crate::commands::get::get_column_path(&key, &nu_var) {
|
||||
Ok(field) => {
|
||||
match nu_value_to_string(&command, &field) {
|
||||
Ok(val) => Some(val),
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match nu_value_to_string(&command, &nu_var) {
|
||||
Ok(val) => Some(val),
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let process_args = args.iter().filter_map(|arg| {
|
||||
if arg.chars().all(|c| c.is_whitespace()) {
|
||||
None
|
||||
} else {
|
||||
let arg = if arg.looks_like_it() {
|
||||
if let Some(mut value) = it_replacement.to_owned() {
|
||||
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
value = {
|
||||
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
||||
add_quotes(&value)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(value)
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
format!(r#""{}""#, unquoted)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if arg.looks_like_nu() {
|
||||
if let Some(mut value) = nu_replacement.to_owned() {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
value = {
|
||||
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
||||
add_quotes(&value)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
} else {
|
||||
Some(arg.to_string())
|
||||
};
|
||||
|
||||
arg
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
}).collect::<Vec<String>>();
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
match spawn(&command, &path, &process_args[..], None, is_last) {
|
||||
Ok(res) => {
|
||||
if let Some(mut res) = res {
|
||||
while let Some(item) = res.next().await {
|
||||
yield Ok(item)
|
||||
}
|
||||
match spawn(&command, &path, &process_args[..], InputStream::empty(), is_last) {
|
||||
Ok(mut res) => {
|
||||
while let Some(item) = res.next().await {
|
||||
yield Ok(item)
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
@ -382,34 +191,35 @@ fn run_with_iterator_arg(
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(stream.to_input_stream()))
|
||||
Ok(stream.to_input_stream())
|
||||
}
|
||||
|
||||
fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
input: Option<InputStream>,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
is_last: bool,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let input = input
|
||||
.map(|input| trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input));
|
||||
let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
|
||||
|
||||
let process_args = command
|
||||
.args
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, scope)?;
|
||||
command_args.push(value.as_string()?);
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let arg = expand_tilde(arg.deref(), dirs::home_dir);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && argument_is_quoted(&arg) {
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
format!(r#""{}""#, unquoted)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
||||
add_quotes(&arg)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
@ -432,9 +242,9 @@ fn spawn(
|
||||
command: &ExternalCommand,
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: Option<InputStream>,
|
||||
input: InputStream,
|
||||
is_last: bool,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
let mut process = {
|
||||
@ -444,6 +254,8 @@ fn spawn(
|
||||
process.arg("/c");
|
||||
process.arg(&command.name);
|
||||
for arg in args {
|
||||
// Clean the args before we use them:
|
||||
let arg = arg.replace("|", "\\|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
@ -469,7 +281,7 @@ fn spawn(
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
if input.is_some() {
|
||||
if !input.is_empty() {
|
||||
process.stdin(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||
}
|
||||
@ -488,7 +300,7 @@ fn spawn(
|
||||
let stdout_name_tag = command.name_tag;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Some(input) = input {
|
||||
if !input.is_empty() {
|
||||
let mut stdin_write = stdin
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
@ -610,7 +422,15 @@ fn spawn(
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
// than what other shells will do.
|
||||
if child.wait().is_err() {
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => match exit_status.code() {
|
||||
Some(e) if e != 0 => true,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
@ -620,17 +440,21 @@ fn spawn(
|
||||
"command failed",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::external_non_zero()),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ThreadedReceiver::new(rx);
|
||||
Ok(Some(stream.to_input_stream()))
|
||||
Ok(stream.to_input_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Failed to spawn process",
|
||||
@ -654,6 +478,7 @@ async fn did_find_command(name: &str) -> bool {
|
||||
let cmd_builtins = [
|
||||
"call", "cls", "color", "date", "dir", "echo", "find", "hostname", "pause",
|
||||
"start", "time", "title", "ver", "copy", "mkdir", "rename", "rd", "rmdir", "type",
|
||||
"mklink",
|
||||
];
|
||||
|
||||
cmd_builtins.contains(&name)
|
||||
@ -714,10 +539,11 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
mod tests {
|
||||
use super::{
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
run_external_command, Context,
|
||||
run_external_command, Context, InputStream,
|
||||
};
|
||||
use futures::executor::block_on;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Scope;
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
|
||||
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
@ -736,11 +562,14 @@ mod tests {
|
||||
async fn non_existent_run() -> Result<(), ShellError> {
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(run_external_command(cmd, &mut ctx, None, false)
|
||||
.await
|
||||
.is_err());
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, &Scope::empty(), false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,42 +1,37 @@
|
||||
use crate::commands::command::per_item_command;
|
||||
use crate::commands::run_alias::AliasCommand;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
use log::{log_enabled, trace};
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::InternalCommand;
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_protocol::hir::InternalCommand;
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
|
||||
pub(crate) fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut Context,
|
||||
input: Option<InputStream>,
|
||||
source: Text,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
trace!(target: "nu::run::internal", "{}", command.args.debug(&source));
|
||||
}
|
||||
|
||||
let objects: InputStream = if let Some(input) = input {
|
||||
trace_stream!(target: "nu::trace_stream::internal", "input" = input)
|
||||
} else {
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
let result = {
|
||||
context.run_command(
|
||||
internal_command?,
|
||||
command.name_tag.clone(),
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&source,
|
||||
scope,
|
||||
objects,
|
||||
)
|
||||
};
|
||||
|
||||
let result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
|
||||
let mut result = result.values;
|
||||
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
|
||||
let mut context = context.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
@ -64,14 +59,15 @@ pub(crate) fn run_internal_command(
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_parser::hir::Call {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: command.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown()
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
source: source.clone(),
|
||||
name_tag: command.name_tag,
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: Scope::empty(),
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);
|
||||
@ -124,6 +120,15 @@ pub(crate) fn run_internal_command(
|
||||
FilesystemShell::with_location(location, context.registry().clone()),
|
||||
));
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![
|
||||
per_item_command(AliasCommand::new(
|
||||
name,
|
||||
args,
|
||||
block,
|
||||
))
|
||||
]);
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
}
|
||||
@ -142,7 +147,8 @@ pub(crate) fn run_internal_command(
|
||||
value: UntaggedValue::Error(err),
|
||||
..
|
||||
})) => {
|
||||
context.error(err);
|
||||
context.error(err.clone());
|
||||
yield Err(err);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -175,5 +181,5 @@ pub(crate) fn run_internal_command(
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(stream.to_input_stream()))
|
||||
Ok(stream.to_input_stream())
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
pub(crate) mod block;
|
||||
mod dynamic;
|
||||
pub(crate) mod expr;
|
||||
pub(crate) mod external;
|
||||
pub(crate) mod internal;
|
||||
pub(crate) mod pipeline;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use dynamic::Command as DynamicCommand;
|
||||
|
@ -1,50 +0,0 @@
|
||||
use crate::commands::classified::external::run_external_command;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::stream::InputStream;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::{ClassifiedCommand, ClassifiedPipeline};
|
||||
use nu_source::Text;
|
||||
|
||||
pub(crate) async fn run_pipeline(
|
||||
pipeline: ClassifiedPipeline,
|
||||
ctx: &mut Context,
|
||||
mut input: Option<InputStream>,
|
||||
line: &str,
|
||||
) -> Result<Option<InputStream>, ShellError> {
|
||||
let mut iter = pipeline.commands.list.into_iter().peekable();
|
||||
|
||||
loop {
|
||||
let item: Option<ClassifiedCommand> = iter.next();
|
||||
let next: Option<&ClassifiedCommand> = iter.peek();
|
||||
|
||||
input = match (item, next) {
|
||||
(Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => {
|
||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Expr(_)), _) | (_, Some(ClassifiedCommand::Expr(_))) => {
|
||||
return Err(ShellError::unimplemented("Expression-only commands"))
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
|
||||
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
|
||||
|
||||
(Some(ClassifiedCommand::Internal(left)), _) => {
|
||||
run_internal_command(left, ctx, input, Text::from(line))?
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::External(left)), None) => {
|
||||
run_external_command(left, ctx, input, true).await?
|
||||
}
|
||||
|
||||
(Some(ClassifiedCommand::External(left)), _) => {
|
||||
run_external_command(left, ctx, input, false).await?
|
||||
}
|
||||
|
||||
(None, _) => break,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
@ -41,7 +41,7 @@ pub mod clipboard {
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let mut clip_stream = inner_clip(values, name).await;
|
||||
while let Some(value) = clip_stream.next().await {
|
||||
|
@ -6,7 +6,7 @@ use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::hir;
|
||||
use nu_protocol::hir;
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnValue, Scope, Signature, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
@ -15,17 +15,28 @@ use std::sync::atomic::AtomicBool;
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct UnevaluatedCallInfo {
|
||||
pub args: hir::Call,
|
||||
pub source: Text,
|
||||
pub name_tag: Tag,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub fn evaluate(
|
||||
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope)?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn evaluate_with_new_it(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
it: &Value,
|
||||
) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, scope, &self.source)?;
|
||||
let mut scope = self.scope.clone();
|
||||
scope = scope.set_it(it.clone());
|
||||
let args = evaluate_args(&self.args, registry, &scope)?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
@ -114,7 +125,7 @@ impl CommandArgs {
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = self.call_info.evaluate(registry, &Scope::empty())?;
|
||||
let call_info = self.call_info.evaluate(registry)?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
@ -134,7 +145,12 @@ impl CommandArgs {
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = self.call_info.evaluate(registry, scope)?;
|
||||
let call_info = UnevaluatedCallInfo {
|
||||
name_tag: self.call_info.name_tag,
|
||||
args: self.call_info.args,
|
||||
scope: scope.clone(),
|
||||
};
|
||||
let call_info = call_info.evaluate(registry)?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
@ -145,10 +161,6 @@ impl CommandArgs {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn source(&self) -> Text {
|
||||
self.call_info.source.clone()
|
||||
}
|
||||
|
||||
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
@ -156,7 +168,6 @@ impl CommandArgs {
|
||||
) -> Result<RunnableArgs<T, O>, ShellError> {
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let host = self.host.clone();
|
||||
let source = self.source();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let args = self.evaluate_once(registry)?;
|
||||
let call_info = args.call_info.clone();
|
||||
@ -168,8 +179,7 @@ impl CommandArgs {
|
||||
args: T::deserialize(&mut deserializer)?,
|
||||
context: RunnableContext {
|
||||
input,
|
||||
commands: registry.clone(),
|
||||
source,
|
||||
registry: registry.clone(),
|
||||
shell_manager,
|
||||
name: name_tag,
|
||||
host,
|
||||
@ -193,7 +203,6 @@ impl CommandArgs {
|
||||
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let host = self.host.clone();
|
||||
let source = self.source();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let args = self.evaluate_once(registry)?;
|
||||
let call_info = args.call_info.clone();
|
||||
@ -206,8 +215,7 @@ impl CommandArgs {
|
||||
args: T::deserialize(&mut deserializer)?,
|
||||
context: RunnableContext {
|
||||
input,
|
||||
commands: registry.clone(),
|
||||
source,
|
||||
registry: registry.clone(),
|
||||
shell_manager,
|
||||
name: name_tag,
|
||||
host,
|
||||
@ -229,15 +237,14 @@ pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub source: Text,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub commands: CommandRegistry,
|
||||
pub registry: CommandRegistry,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Arc<Command>> {
|
||||
self.commands.get_command(name)
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,6 +381,14 @@ impl EvaluatedCommandArgs {
|
||||
self.call_info.args.nth(pos)
|
||||
}
|
||||
|
||||
/// Get the nth positional argument, error if not possible
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||
match self.call_info.args.nth(pos) {
|
||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
||||
Some(item) => Ok(item),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||
self.call_info.args.get(name)
|
||||
}
|
||||
@ -515,12 +530,17 @@ impl Command {
|
||||
|
||||
let out = args
|
||||
.input
|
||||
.values
|
||||
.map(move |x| {
|
||||
let call_info = raw_args
|
||||
.clone()
|
||||
.call_info
|
||||
.evaluate(®istry, &Scope::it_value(x.clone()));
|
||||
let call_info = UnevaluatedCallInfo {
|
||||
args: raw_args.call_info.args.clone(),
|
||||
name_tag: raw_args.call_info.name_tag.clone(),
|
||||
scope: raw_args.call_info.scope.clone().set_it(x.clone()),
|
||||
}
|
||||
.evaluate(®istry);
|
||||
// let call_info = raw_args
|
||||
// .clone()
|
||||
// .call_info
|
||||
// .evaluate(®istry, &Scope::it_value(x.clone()));
|
||||
|
||||
match call_info {
|
||||
Ok(call_info) => match command.run(&call_info, ®istry, &raw_args, x) {
|
||||
@ -576,9 +596,9 @@ impl WholeStreamCommand for FnFilterCommand {
|
||||
let registry: CommandRegistry = registry.clone();
|
||||
let func = self.func;
|
||||
|
||||
let result = input.values.map(move |it| {
|
||||
let result = input.map(move |it| {
|
||||
let registry = registry.clone();
|
||||
let call_info = match call_info.clone().evaluate(®istry, &Scope::it_value(it)) {
|
||||
let call_info = match call_info.clone().evaluate_with_new_it(®istry, &it) {
|
||||
Err(err) => return OutputStream::from(vec![Err(err)]).values,
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ pub fn compact(
|
||||
CompactArgs { rest: columns }: CompactArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let objects = input.values.filter(move |item| {
|
||||
let objects = input.filter(move |item| {
|
||||
let keep = if columns.is_empty() {
|
||||
item.is_some()
|
||||
} else {
|
||||
|
@ -41,7 +41,7 @@ impl WholeStreamCommand for Config {
|
||||
)
|
||||
.named(
|
||||
"set_into",
|
||||
SyntaxShape::Member,
|
||||
SyntaxShape::String,
|
||||
"sets a variable from values in the pipeline",
|
||||
Some('i'),
|
||||
)
|
||||
@ -124,7 +124,7 @@ pub fn config(
|
||||
yield ReturnSuccess::value(UntaggedValue::Row(result.into()).into_value(&value.tag));
|
||||
}
|
||||
else if let Some(v) = set_into {
|
||||
let rows: Vec<Value> = input.values.collect().await;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let key = v.to_string();
|
||||
|
||||
if rows.len() == 0 {
|
||||
|
@ -37,7 +37,7 @@ pub fn count(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let rows: Vec<Value> = input.values.collect().await;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
yield ReturnSuccess::value(UntaggedValue::int(rows.len()).into_value(name))
|
||||
};
|
||||
|
@ -37,7 +37,6 @@ fn debug_value(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<impl ToOutputStream, ShellError> {
|
||||
Ok(input
|
||||
.values
|
||||
.map(move |v| {
|
||||
if raw {
|
||||
ReturnSuccess::value(
|
||||
|
@ -47,7 +47,6 @@ fn default(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = input
|
||||
.values
|
||||
.map(move |item| {
|
||||
let mut result = VecDeque::new();
|
||||
|
||||
|
86
crates/nu-cli/src/commands/each.rs
Normal file
86
crates/nu-cli/src/commands/each.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::stream::once;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Each;
|
||||
|
||||
impl PerItemCommand for Each {
|
||||
fn name(&self) -> &str {
|
||||
"each"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each row",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let call_info = call_info.clone();
|
||||
let registry = registry.clone();
|
||||
let raw_args = raw_args.clone();
|
||||
let stream = async_stream! {
|
||||
match call_info.args.expect_nth(0)? {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag
|
||||
} => {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let input_clone = input.clone();
|
||||
let input_stream = once(async { Ok(input) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
block,
|
||||
&mut context,
|
||||
input_stream,
|
||||
&Scope::new(input_clone),
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
yield Err(error.clone());
|
||||
return;
|
||||
}
|
||||
|
||||
while let Some(result) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(result));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a block",
|
||||
"each needs a block",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
@ -97,14 +97,15 @@ impl PerItemCommand for Enter {
|
||||
ctrl_c: raw_args.ctrl_c,
|
||||
shell_manager: raw_args.shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_parser::hir::Call {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: raw_args.call_info.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown()
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
source: raw_args.call_info.source,
|
||||
name_tag: raw_args.call_info.name_tag,
|
||||
scope: raw_args.call_info.scope.clone()
|
||||
},
|
||||
};
|
||||
let mut result = converter.run(
|
||||
|
@ -45,7 +45,7 @@ pub fn evaluate_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
|
@ -48,7 +48,5 @@ fn first(
|
||||
1
|
||||
};
|
||||
|
||||
Ok(OutputStream::from_input(
|
||||
context.input.values.take(rows_desired),
|
||||
))
|
||||
Ok(OutputStream::from_input(context.input.take(rows_desired)))
|
||||
}
|
||||
|
@ -9,11 +9,6 @@ use nu_source::Tagged;
|
||||
use nu_value_ext::{as_column_path, get_data_by_column_path};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use nom::{
|
||||
bytes::complete::{tag, take_while},
|
||||
IResult,
|
||||
};
|
||||
|
||||
pub struct Format;
|
||||
|
||||
impl PerItemCommand for Format {
|
||||
@ -45,14 +40,8 @@ impl PerItemCommand for Format {
|
||||
let pattern_tag = pattern.tag.clone();
|
||||
let pattern = pattern.as_string()?;
|
||||
|
||||
let format_pattern = format(&pattern).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Could not create format pattern",
|
||||
"could not create format pattern",
|
||||
&pattern_tag,
|
||||
)
|
||||
})?;
|
||||
let commands = format_pattern.1;
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = format_pattern;
|
||||
|
||||
let output = match value {
|
||||
value
|
||||
@ -66,7 +55,7 @@ impl PerItemCommand for Format {
|
||||
for command in &commands {
|
||||
match command {
|
||||
FormatCommand::Text(s) => {
|
||||
output.push_str(s);
|
||||
output.push_str(&s);
|
||||
}
|
||||
FormatCommand::Column(c) => {
|
||||
let key = to_column_path(&c, &pattern_tag)?;
|
||||
@ -104,32 +93,43 @@ enum FormatCommand {
|
||||
Column(String),
|
||||
}
|
||||
|
||||
fn format(input: &str) -> IResult<&str, Vec<FormatCommand>> {
|
||||
fn format(input: &str) -> Vec<FormatCommand> {
|
||||
let mut output = vec![];
|
||||
|
||||
let mut loop_input = input;
|
||||
let mut loop_input = input.chars();
|
||||
loop {
|
||||
let (input, before) = take_while(|c| c != '{')(loop_input)?;
|
||||
let mut before = String::new();
|
||||
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '{' {
|
||||
break;
|
||||
}
|
||||
before.push(c);
|
||||
}
|
||||
|
||||
if !before.is_empty() {
|
||||
output.push(FormatCommand::Text(before.to_string()));
|
||||
}
|
||||
if input != "" {
|
||||
// Look for column as we're now at one
|
||||
let (input, _) = tag("{")(input)?;
|
||||
let (input, column) = take_while(|c| c != '}')(input)?;
|
||||
let (input, _) = tag("}")(input)?;
|
||||
// Look for column as we're now at one
|
||||
let mut column = String::new();
|
||||
|
||||
output.push(FormatCommand::Column(column.to_string()));
|
||||
loop_input = input;
|
||||
} else {
|
||||
loop_input = input;
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '}' {
|
||||
break;
|
||||
}
|
||||
column.push(c);
|
||||
}
|
||||
if loop_input == "" {
|
||||
|
||||
if !column.is_empty() {
|
||||
output.push(FormatCommand::Column(column.to_string()));
|
||||
}
|
||||
|
||||
if before.is_empty() && column.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((loop_input, output))
|
||||
output
|
||||
}
|
||||
|
||||
fn to_column_path(
|
||||
|
@ -1,12 +1,45 @@
|
||||
use crate::prelude::*;
|
||||
use csv::{ErrorKind, ReaderBuilder};
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::hir::syntax_shape::{ExpandContext, SignatureRegistry};
|
||||
use nu_parser::utils::{parse_line_with_separator as parse, LineSeparatedShape};
|
||||
use nu_parser::TokensIterator;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::nom_input;
|
||||
use nu_protocol::{ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
use derive_new::new;
|
||||
fn from_delimited_string_to_value(
|
||||
s: String,
|
||||
headerless: bool,
|
||||
separator: char,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, csv::Error> {
|
||||
let mut reader = ReaderBuilder::new()
|
||||
.has_headers(!headerless)
|
||||
.delimiter(separator as u8)
|
||||
.from_reader(s.as_bytes());
|
||||
let tag = tag.into();
|
||||
|
||||
let headers = if headerless {
|
||||
(1..=reader.headers()?.len())
|
||||
.map(|i| format!("Column{}", i))
|
||||
.collect::<Vec<String>>()
|
||||
} else {
|
||||
reader.headers()?.iter().map(String::from).collect()
|
||||
};
|
||||
|
||||
let mut rows = vec![];
|
||||
for row in reader.records() {
|
||||
let mut tagged_row = TaggedDictBuilder::new(&tag);
|
||||
for (value, header) in row?.iter().zip(headers.iter()) {
|
||||
if let Ok(i) = value.parse::<i64>() {
|
||||
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
||||
} else if let Ok(f) = value.parse::<f64>() {
|
||||
tagged_row.insert_value(header, UntaggedValue::decimal(f).into_value(&tag))
|
||||
} else {
|
||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
||||
}
|
||||
}
|
||||
rows.push(tagged_row.into_value());
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::Table(rows).into_value(&tag))
|
||||
}
|
||||
|
||||
pub fn from_delimited_data(
|
||||
headerless: bool,
|
||||
@ -20,20 +53,19 @@ pub fn from_delimited_data(
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
|
||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||
Ok(rows) => {
|
||||
for row in rows {
|
||||
match row {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
Ok(x) => match x {
|
||||
Value { value: UntaggedValue::Table(list), .. } => {
|
||||
for l in list {
|
||||
yield ReturnSuccess::value(l);
|
||||
}
|
||||
}
|
||||
x => yield ReturnSuccess::value(x),
|
||||
},
|
||||
Err(err) => {
|
||||
let line_one = format!("Could not parse as {}", format_name);
|
||||
let line_one = match pretty_csv_error(err) {
|
||||
Some(pretty) => format!("Could not parse as {} ({})", format_name,pretty),
|
||||
None => format!("Could not parse as {}", format_name),
|
||||
};
|
||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
line_one,
|
||||
@ -49,121 +81,25 @@ pub fn from_delimited_data(
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, new)]
|
||||
pub struct EmptyRegistry {
|
||||
#[new(default)]
|
||||
signatures: indexmap::IndexMap<String, Signature>,
|
||||
}
|
||||
|
||||
impl EmptyRegistry {}
|
||||
|
||||
impl SignatureRegistry for EmptyRegistry {
|
||||
fn has(&self, _name: &str) -> bool {
|
||||
false
|
||||
}
|
||||
fn get(&self, _name: &str) -> Option<Signature> {
|
||||
None
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn from_delimited_string_to_value(
|
||||
s: String,
|
||||
headerless: bool,
|
||||
sep: char,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut entries = s.lines();
|
||||
|
||||
let mut fields = vec![];
|
||||
let mut out = vec![];
|
||||
|
||||
if let Some(first_entry) = entries.next() {
|
||||
let tokens = match parse(&sep.to_string(), nom_input(first_entry)) {
|
||||
Ok((_, tokens)) => tokens,
|
||||
Err(err) => return Err(ShellError::parse_error(err)),
|
||||
};
|
||||
|
||||
let tokens_span = tokens.span;
|
||||
let source: nu_source::Text = tokens_span.slice(&first_entry).into();
|
||||
|
||||
if !headerless {
|
||||
fields = tokens
|
||||
.item
|
||||
.iter()
|
||||
.filter(|token| !token.is_separator())
|
||||
.map(|field| field.source(&source).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
let registry = Box::new(EmptyRegistry::new());
|
||||
let ctx = ExpandContext::new(registry, &source, None);
|
||||
|
||||
let mut iterator = TokensIterator::new(&tokens.item, ctx, tokens_span);
|
||||
let (results, tokens_identified) = iterator.expand(LineSeparatedShape);
|
||||
let results = results?;
|
||||
|
||||
let mut row = TaggedDictBuilder::new(&tag);
|
||||
|
||||
if headerless {
|
||||
let fallback_columns = (1..=tokens_identified)
|
||||
.map(|i| format!("Column{}", i))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
for (idx, field) in results.into_iter().enumerate() {
|
||||
let key = if headerless {
|
||||
&fallback_columns[idx]
|
||||
} else {
|
||||
&fields[idx]
|
||||
};
|
||||
|
||||
row.insert_value(key, field.into_value(&tag));
|
||||
}
|
||||
|
||||
out.push(row.into_value())
|
||||
}
|
||||
}
|
||||
|
||||
for entry in entries {
|
||||
let tokens = match parse(&sep.to_string(), nom_input(entry)) {
|
||||
Ok((_, tokens)) => tokens,
|
||||
Err(err) => return Err(ShellError::parse_error(err)),
|
||||
};
|
||||
let tokens_span = tokens.span;
|
||||
|
||||
let source: nu_source::Text = tokens_span.slice(&entry).into();
|
||||
let registry = Box::new(EmptyRegistry::new());
|
||||
let ctx = ExpandContext::new(registry, &source, None);
|
||||
|
||||
let mut iterator = TokensIterator::new(&tokens.item, ctx, tokens_span);
|
||||
let (results, tokens_identified) = iterator.expand(LineSeparatedShape);
|
||||
let results = results?;
|
||||
|
||||
let mut row = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let fallback_columns = (1..=tokens_identified)
|
||||
.map(|i| format!("Column{}", i))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
for (idx, field) in results.into_iter().enumerate() {
|
||||
let key = if headerless {
|
||||
&fallback_columns[idx]
|
||||
fn pretty_csv_error(err: csv::Error) -> Option<String> {
|
||||
match err.kind() {
|
||||
ErrorKind::UnequalLengths {
|
||||
pos,
|
||||
expected_len,
|
||||
len,
|
||||
} => {
|
||||
if let Some(pos) = pos {
|
||||
Some(format!(
|
||||
"Line {}: expected {} fields, found {}",
|
||||
pos.line(),
|
||||
expected_len,
|
||||
len
|
||||
))
|
||||
} else {
|
||||
match fields.get(idx) {
|
||||
Some(key) => key,
|
||||
None => &fallback_columns[idx],
|
||||
}
|
||||
};
|
||||
|
||||
row.insert_value(key, field.into_value(&tag));
|
||||
Some(format!("Expected {} fields, found {}", expected_len, len))
|
||||
}
|
||||
}
|
||||
|
||||
out.push(row.into_value())
|
||||
ErrorKind::Seek => Some("Internal error while parsing csv".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
@ -197,7 +197,6 @@ pub fn get(
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
let stream = input
|
||||
.values
|
||||
.map(move |item| {
|
||||
let mut result = VecDeque::new();
|
||||
|
||||
|
@ -43,7 +43,7 @@ pub fn group_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
|
@ -35,7 +35,7 @@ pub fn headers(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let rows: Vec<Value> = input.values.collect().await;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
if rows.len() < 1 {
|
||||
yield Err(ShellError::untagged_runtime_error("Couldn't find headers, was the input a properly formatted, non-empty table?"));
|
||||
|
@ -76,6 +76,12 @@ impl PerItemCommand for Help {
|
||||
return Ok(
|
||||
get_help(&command.name(), &command.usage(), command.signature()).into(),
|
||||
);
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
let help = futures::stream::iter(help);
|
||||
Ok(help.to_output_stream())
|
||||
|
@ -30,7 +30,7 @@ impl WholeStreamCommand for Histogram {
|
||||
"the name of the column to graph by",
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::Member,
|
||||
SyntaxShape::String,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
}
|
||||
@ -53,7 +53,7 @@ pub fn histogram(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let Tagged { item: group_by, .. } = column_name.clone();
|
||||
|
||||
|
@ -47,7 +47,7 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
|
||||
let mut leftover_string = String::new();
|
||||
let stream = async_stream! {
|
||||
loop {
|
||||
match input.values.next().await {
|
||||
match input.next().await {
|
||||
Some(Value { value: UntaggedValue::Primitive(Primitive::String(st)), ..}) => {
|
||||
let mut st = leftover_string.clone() + &st;
|
||||
leftover.clear();
|
||||
|
@ -46,7 +46,7 @@ pub fn map_max_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
|
||||
if values.is_empty() {
|
||||
|
@ -22,7 +22,7 @@ impl WholeStreamCommand for Nth {
|
||||
Signature::build("nth")
|
||||
.required(
|
||||
"row number",
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::Int,
|
||||
"the number of the row to return",
|
||||
)
|
||||
.rest(SyntaxShape::Any, "Optionally return more rows")
|
||||
@ -49,7 +49,6 @@ fn nth(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = input
|
||||
.values
|
||||
.enumerate()
|
||||
.map(move |(idx, item)| {
|
||||
let row_number = vec![row_number.clone()];
|
||||
|
@ -6,10 +6,6 @@ use nu_protocol::{
|
||||
CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use nom::{
|
||||
bytes::complete::{tag, take_while},
|
||||
IResult,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -18,32 +14,44 @@ enum ParseCommand {
|
||||
Column(String),
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> IResult<&str, Vec<ParseCommand>> {
|
||||
fn parse(input: &str) -> Vec<ParseCommand> {
|
||||
let mut output = vec![];
|
||||
|
||||
let mut loop_input = input;
|
||||
//let mut loop_input = input;
|
||||
let mut loop_input = input.chars();
|
||||
loop {
|
||||
let (input, before) = take_while(|c| c != '{')(loop_input)?;
|
||||
let mut before = String::new();
|
||||
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '{' {
|
||||
break;
|
||||
}
|
||||
before.push(c);
|
||||
}
|
||||
|
||||
if !before.is_empty() {
|
||||
output.push(ParseCommand::Text(before.to_string()));
|
||||
}
|
||||
if input != "" {
|
||||
// Look for column as we're now at one
|
||||
let (input, _) = tag("{")(input)?;
|
||||
let (input, column) = take_while(|c| c != '}')(input)?;
|
||||
let (input, _) = tag("}")(input)?;
|
||||
// Look for column as we're now at one
|
||||
let mut column = String::new();
|
||||
|
||||
output.push(ParseCommand::Column(column.to_string()));
|
||||
loop_input = input;
|
||||
} else {
|
||||
loop_input = input;
|
||||
while let Some(c) = loop_input.next() {
|
||||
if c == '}' {
|
||||
break;
|
||||
}
|
||||
column.push(c);
|
||||
}
|
||||
if loop_input == "" {
|
||||
|
||||
if !column.is_empty() {
|
||||
output.push(ParseCommand::Column(column.to_string()));
|
||||
}
|
||||
|
||||
if before.is_empty() && column.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((loop_input, output))
|
||||
output
|
||||
}
|
||||
|
||||
fn column_names(commands: &[ParseCommand]) -> Vec<String> {
|
||||
@ -103,16 +111,10 @@ impl PerItemCommand for Parse {
|
||||
//let value_tag = value.tag();
|
||||
let pattern = call_info.args.expect_nth(0)?.as_string()?;
|
||||
|
||||
let parse_pattern = parse(&pattern).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Could not create parse pattern",
|
||||
"could not create parse pattern",
|
||||
&value.tag,
|
||||
)
|
||||
})?;
|
||||
let parse_regex = build_regex(&parse_pattern.1);
|
||||
let parse_pattern = parse(&pattern);
|
||||
let parse_regex = build_regex(&parse_pattern);
|
||||
|
||||
let column_names = column_names(&parse_pattern.1);
|
||||
let column_names = column_names(&parse_pattern);
|
||||
let regex = Regex::new(&parse_regex).map_err(|_| {
|
||||
ShellError::labeled_error("Could not parse regex", "could not parse regex", &value.tag)
|
||||
})?;
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures_util::pin_mut;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, PathMember, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape,
|
||||
@ -44,7 +43,9 @@ impl WholeStreamCommand for Pick {
|
||||
|
||||
fn pick(
|
||||
PickArgs { rest: mut fields }: PickArgs,
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
RunnableContext {
|
||||
mut input, name, ..
|
||||
}: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if fields.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
@ -64,13 +65,10 @@ fn pick(
|
||||
.collect::<Vec<ColumnPath>>();
|
||||
|
||||
let stream = async_stream! {
|
||||
let values = input.values;
|
||||
pin_mut!(values);
|
||||
|
||||
let mut empty = true;
|
||||
let mut bring_back: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
|
||||
|
||||
while let Some(value) = values.next().await {
|
||||
while let Some(value) = input.next().await {
|
||||
for path in &column_paths {
|
||||
let path_members_span = span_for_spanned_list(path.members().iter().map(|p| p.span));
|
||||
|
||||
|
@ -3,7 +3,7 @@ use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
@ -71,10 +71,13 @@ pub fn filter_plugin(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
trace!("filter_plugin :: {}", path);
|
||||
|
||||
let args = args.evaluate_once_with_scope(
|
||||
registry,
|
||||
&Scope::it_value(UntaggedValue::string("$it").into_untagged_value()),
|
||||
)?;
|
||||
let scope = &args
|
||||
.call_info
|
||||
.scope
|
||||
.clone()
|
||||
.set_it(UntaggedValue::string("$it").into_untagged_value());
|
||||
|
||||
let args = args.evaluate_once_with_scope(registry, &scope)?;
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
@ -95,7 +98,7 @@ pub fn filter_plugin(
|
||||
trace!("filtering :: {:?}", call_info);
|
||||
|
||||
let stream = bos
|
||||
.chain(args.input.values)
|
||||
.chain(args.input)
|
||||
.chain(eos)
|
||||
.map(move |v| match v {
|
||||
Value {
|
||||
@ -340,7 +343,7 @@ pub fn sink_plugin(
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let request = JsonRpc::new("sink", (call_info.clone(), input));
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
|
@ -43,5 +43,5 @@ fn prepend(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let prepend = futures::stream::iter(vec![row]);
|
||||
|
||||
Ok(OutputStream::from_input(prepend.chain(input.values)))
|
||||
Ok(prepend.chain(input).to_output_stream())
|
||||
}
|
||||
|
@ -50,7 +50,5 @@ fn range(
|
||||
let from = *from as usize;
|
||||
let to = *to as usize;
|
||||
|
||||
Ok(OutputStream::from_input(
|
||||
input.values.skip(from).take(to - from + 1),
|
||||
))
|
||||
Ok(input.skip(from).take(to - from + 1).to_output_stream())
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ pub fn reduce_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
|
@ -18,7 +18,7 @@ impl WholeStreamCommand for Reject {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("reject").rest(SyntaxShape::Member, "the names of columns to remove")
|
||||
Signature::build("reject").rest(SyntaxShape::String, "the names of columns to remove")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -48,9 +48,7 @@ fn reject(
|
||||
|
||||
let fields: Vec<_> = fields.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let stream = input
|
||||
.values
|
||||
.map(move |item| reject_fields(&item, &fields, &item.tag));
|
||||
let stream = input.map(move |item| reject_fields(&item, &fields, &item.tag));
|
||||
|
||||
Ok(stream.from_input_stream())
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ impl WholeStreamCommand for Rename {
|
||||
"the name of the column to rename for",
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::Member,
|
||||
SyntaxShape::String,
|
||||
"Additional column name(s) to rename for",
|
||||
)
|
||||
}
|
||||
@ -54,7 +54,6 @@ pub fn rename(
|
||||
let new_column_names = new_column_names.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
let stream = input
|
||||
.values
|
||||
.map(move |item| {
|
||||
let mut result = VecDeque::new();
|
||||
|
||||
|
@ -32,7 +32,7 @@ fn reverse(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let (input, _args) = args.parts();
|
||||
|
||||
let input = input.values.collect::<Vec<_>>();
|
||||
let input = input.collect::<Vec<_>>();
|
||||
|
||||
let output = input.map(move |mut vec| {
|
||||
vec.reverse();
|
||||
|
@ -12,6 +12,7 @@ pub struct Remove;
|
||||
pub struct RemoveArgs {
|
||||
pub rest: Vec<Tagged<PathBuf>>,
|
||||
pub recursive: Tagged<bool>,
|
||||
#[allow(unused)]
|
||||
pub trash: Tagged<bool>,
|
||||
}
|
||||
|
||||
|
108
crates/nu-cli/src/commands/run_alias.rs
Normal file
108
crates/nu-cli/src/commands/run_alias.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, Value};
|
||||
|
||||
#[derive(new)]
|
||||
pub struct AliasCommand {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
impl PerItemCommand for AliasCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
let mut alias = Signature::build(&self.name);
|
||||
|
||||
for arg in &self.args {
|
||||
alias = alias.optional(arg, SyntaxShape::Any, "");
|
||||
}
|
||||
|
||||
alias
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
registry: &CommandRegistry,
|
||||
raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = call_info.name_tag.clone();
|
||||
let call_info = call_info.clone();
|
||||
let registry = registry.clone();
|
||||
let raw_args = raw_args.clone();
|
||||
let block = self.block.clone();
|
||||
|
||||
let mut scope = Scope::it_value(input.clone());
|
||||
if let Some(positional) = &call_info.args.positional {
|
||||
for (pos, arg) in positional.iter().enumerate() {
|
||||
scope = scope.set_var(self.args[pos].to_string(), arg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let stream = async_stream! {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
|
||||
let input_clone = Ok(input.clone());
|
||||
let input_stream = futures::stream::once(async { input_clone }).boxed().to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input_stream,
|
||||
&scope
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(stream) if stream.is_empty() => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Expected a block",
|
||||
"alias needs a block",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
Ok(mut stream) => {
|
||||
// We collect first to ensure errors are put into the context
|
||||
while let Some(result) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(result));
|
||||
}
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(x) = errors.first() {
|
||||
//yield Err(error.clone());
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Alias failed to run",
|
||||
"alias failed to run",
|
||||
tag.clone(),
|
||||
x.to_string(),
|
||||
tag
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
//yield Err(e);
|
||||
yield Err(ShellError::labeled_error_with_secondary(
|
||||
"Alias failed to run",
|
||||
"alias failed to run",
|
||||
tag.clone(),
|
||||
e.to_string(),
|
||||
tag
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
122
crates/nu-cli/src/commands/run_external.rs
Normal file
122
crates/nu-cli/src/commands/run_external.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use crate::commands::classified::external;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Expression, ExternalArgs, ExternalCommand, Literal, SpannedExpression};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RunExternalArgs {}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct RunExternalCommand;
|
||||
|
||||
fn spanned_expression_to_string(expr: SpannedExpression) -> String {
|
||||
if let SpannedExpression {
|
||||
expr: Expression::Literal(Literal::String(s)),
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
s
|
||||
} else {
|
||||
"notacommand!!!".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for RunExternalCommand {
|
||||
fn name(&self) -> &str {
|
||||
"run_external"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).rest(SyntaxShape::Any, "external command arguments")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let positionals = args.call_info.args.positional.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error("positional arguments unexpectedly empty")
|
||||
})?;
|
||||
|
||||
let mut positionals = positionals.into_iter();
|
||||
|
||||
let name = positionals
|
||||
.next()
|
||||
.map(spanned_expression_to_string)
|
||||
.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(
|
||||
"run_external unexpectedly missing external name positional arg",
|
||||
)
|
||||
})?;
|
||||
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
name_tag: args.call_info.name_tag.clone(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
};
|
||||
|
||||
let mut external_context;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
external_context = Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
};
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
external_context = Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
};
|
||||
}
|
||||
let scope = args.call_info.scope.clone();
|
||||
|
||||
let is_last = args.call_info.args.is_last;
|
||||
let input = args.input;
|
||||
let stream = async_stream! {
|
||||
let result = external::run_external_command(
|
||||
command,
|
||||
&mut external_context,
|
||||
input,
|
||||
&scope,
|
||||
is_last,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.next().await {
|
||||
yield Ok(ReturnSuccess::Value(value));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -168,7 +168,7 @@ fn save(
|
||||
shell_manager,
|
||||
host,
|
||||
ctrl_c,
|
||||
commands: registry,
|
||||
registry,
|
||||
..
|
||||
}: RunnableContext,
|
||||
raw_args: RawCommandArgs,
|
||||
@ -177,7 +177,7 @@ fn save(
|
||||
let name_tag = name.clone();
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = input.values.collect().await;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
if path.is_none() {
|
||||
// If there is no filename, check the metadata for the anchor filename
|
||||
if input.len() > 0 {
|
||||
@ -228,14 +228,15 @@ fn save(
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_parser::hir::Call {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: raw_args.call_info.args.head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown()
|
||||
span: Span::unknown(),
|
||||
is_last: false,
|
||||
},
|
||||
source: raw_args.call_info.source,
|
||||
name_tag: raw_args.call_info.name_tag,
|
||||
scope: Scope::empty(), // FIXME?
|
||||
}
|
||||
};
|
||||
let mut result = converter.run(new_args.with_input(input), ®istry);
|
||||
|
@ -36,9 +36,9 @@ fn shells(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
|
||||
dict.insert_untagged(" ", "X".to_string());
|
||||
dict.insert_untagged("active", "X".to_string());
|
||||
} else {
|
||||
dict.insert_untagged(" ", " ".to_string());
|
||||
dict.insert_untagged("active", " ".to_string());
|
||||
}
|
||||
dict.insert_untagged("name", shell.name());
|
||||
dict.insert_untagged("path", shell.path());
|
||||
|
@ -48,7 +48,7 @@ fn shuffle(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let mut values: Vec<Value> = input.values.collect().await;
|
||||
let mut values: Vec<Value> = input.collect().await;
|
||||
|
||||
let out = if let Some(n) = limit {
|
||||
let (shuffled, _) = values.partial_shuffle(&mut thread_rng(), *n as usize);
|
||||
|
@ -33,7 +33,6 @@ fn size(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream,
|
||||
let name_span = tag.span;
|
||||
|
||||
Ok(input
|
||||
.values
|
||||
.map(move |v| {
|
||||
if let Ok(s) = v.as_string() {
|
||||
ReturnSuccess::value(count(&s, &v.tag))
|
||||
|
@ -41,7 +41,5 @@ fn skip(SkipArgs { rows }: SkipArgs, context: RunnableContext) -> Result<OutputS
|
||||
1
|
||||
};
|
||||
|
||||
Ok(OutputStream::from_input(
|
||||
context.input.values.skip(rows_desired),
|
||||
))
|
||||
Ok(OutputStream::from_input(context.input.skip(rows_desired)))
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Evaluate, Scope, Signature, SyntaxShape};
|
||||
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SkipWhile;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SkipWhileArgs {
|
||||
condition: Evaluate,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SkipWhile {
|
||||
fn name(&self) -> &str {
|
||||
"skip-while"
|
||||
@ -20,7 +16,7 @@ impl WholeStreamCommand for SkipWhile {
|
||||
Signature::build("skip-while")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Block,
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to continue skipping",
|
||||
)
|
||||
.filter()
|
||||
@ -35,26 +31,66 @@ impl WholeStreamCommand for SkipWhile {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, skip_while)?.run()
|
||||
}
|
||||
}
|
||||
let registry = registry.clone();
|
||||
let call_info = args.evaluate_once(®istry)?;
|
||||
|
||||
pub fn skip_while(
|
||||
SkipWhileArgs { condition }: SkipWhileArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let objects = input.values.skip_while(move |item| {
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result = condition.invoke(&Scope::new(item.clone()));
|
||||
trace!("RESULT = {:?}", result);
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
let objects = call_info.input.skip_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result = evaluate_baseline_expr(&*condition, ®istry, &Scope::new(item.clone()));
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ pub fn split_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.len() > 1 || values.is_empty() {
|
||||
yield Err(ShellError::labeled_error(
|
||||
|
@ -30,7 +30,7 @@ impl WholeStreamCommand for SplitColumn {
|
||||
"the character that denotes what separates columns",
|
||||
)
|
||||
.switch("collapse-empty", "remove empty columns", Some('c'))
|
||||
.rest(SyntaxShape::Member, "column names to give the new columns")
|
||||
.rest(SyntaxShape::String, "column names to give the new columns")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -57,7 +57,6 @@ fn split_column(
|
||||
let name_span = name.span;
|
||||
|
||||
Ok(input
|
||||
.values
|
||||
.map(move |v| {
|
||||
if let Ok(s) = v.as_string() {
|
||||
let splitter = separator.replace("\\n", "\n");
|
||||
|
@ -43,7 +43,6 @@ fn split_row(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = input
|
||||
.values
|
||||
.map(move |v| {
|
||||
if let Ok(s) = v.as_string() {
|
||||
let splitter = separator.item.replace("\\n", "\n");
|
||||
|
@ -27,10 +27,9 @@ impl WholeStreamCommand for Sum {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sum(RunnableContext {
|
||||
input: args.input,
|
||||
commands: registry.clone(),
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
source: args.call_info.source,
|
||||
ctrl_c: args.ctrl_c,
|
||||
name: args.call_info.name_tag,
|
||||
})
|
||||
|
@ -69,7 +69,7 @@ fn t_sort_by(
|
||||
RunnableContext { input, name, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::new(async_stream! {
|
||||
let values: Vec<Value> = input.values.collect().await;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let column_grouped_by_name = if let Some(grouped_by) = group_by {
|
||||
Some(grouped_by.item().clone())
|
||||
|
@ -30,7 +30,6 @@ impl WholeStreamCommand for Tags {
|
||||
fn tags(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.values
|
||||
.map(move |v| {
|
||||
let mut tags = TaggedDictBuilder::new(v.tag());
|
||||
{
|
||||
|
@ -266,7 +266,7 @@ fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_span = name_tag.span;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let to_process_input = if input.len() > 1 {
|
||||
let tag = input[0].tag.clone();
|
||||
|
@ -175,7 +175,7 @@ pub fn to_delimited_data(
|
||||
let name_span = name_tag.span;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = input.values.collect().await;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
let to_process_input = if input.len() > 1 {
|
||||
let tag = input[0].tag.clone();
|
||||
|
@ -35,7 +35,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_tag = args.name_tag();
|
||||
//let name_span = name_tag.span;
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = "<html><body>".to_string();
|
||||
|
||||
|
@ -136,7 +136,7 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_tag = args.name_tag();
|
||||
let name_span = name_tag.span;
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let to_process_input = if input.len() > 1 {
|
||||
let tag = input[0].tag.clone();
|
||||
|
@ -34,7 +34,7 @@ fn to_html(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_tag = args.name_tag();
|
||||
//let name_span = name_tag.span;
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = String::new();
|
||||
|
||||
|
@ -205,7 +205,7 @@ fn to_sqlite(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStre
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let name_tag = args.name_tag();
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
match sqlite_input_stream_to_bytes(input) {
|
||||
Ok(out) => yield ReturnSuccess::value(out),
|
||||
|
@ -98,7 +98,7 @@ fn to_toml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_tag = args.name_tag();
|
||||
let name_span = name_tag.span;
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let to_process_input = if input.len() > 1 {
|
||||
let tag = input[0].tag.clone();
|
||||
|
@ -33,7 +33,7 @@ fn to_url(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
|
||||
let input = args.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = input.values.collect().await;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
for value in input {
|
||||
match value {
|
||||
|
@ -130,7 +130,7 @@ fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
|
||||
let name_span = name_tag.span;
|
||||
|
||||
let stream = async_stream! {
|
||||
let input: Vec<Value> = args.input.values.collect().await;
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
let to_process_input = if input.len() > 1 {
|
||||
let tag = input[0].tag.clone();
|
||||
|
@ -29,10 +29,8 @@ impl WholeStreamCommand for Trim {
|
||||
}
|
||||
|
||||
fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
|
||||
Ok(input
|
||||
.values
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |v| {
|
||||
let string = String::extract(&v)?;
|
||||
ReturnSuccess::value(UntaggedValue::string(string.trim()).into_value(v.tag()))
|
||||
|
@ -37,7 +37,7 @@ fn uniq(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let uniq_values: IndexSet<_> = input.values.collect().await;
|
||||
let uniq_values: IndexSet<_> = input.collect().await;
|
||||
|
||||
for item in uniq_values.iter().map(|row| ReturnSuccess::value(row.clone())) {
|
||||
yield item;
|
||||
|
@ -1,8 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use futures_util::pin_mut;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, Signature, UntaggedValue};
|
||||
|
||||
@ -34,14 +32,11 @@ impl WholeStreamCommand for What {
|
||||
}
|
||||
|
||||
pub fn what(
|
||||
WhatArgs {}: WhatArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
_: WhatArgs,
|
||||
RunnableContext { mut input, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let stream = async_stream! {
|
||||
let values = input.values;
|
||||
pin_mut!(values);
|
||||
|
||||
while let Some(row) = values.next().await {
|
||||
while let Some(row) = input.next().await {
|
||||
let name = value::format_type(&row, 100);
|
||||
yield ReturnSuccess::value(UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)));
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
use crate::commands::PerItemCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{
|
||||
hir::ClassifiedCommand, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
|
||||
pub struct Where;
|
||||
|
||||
@ -14,7 +18,7 @@ impl PerItemCommand for Where {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("where").required(
|
||||
"condition",
|
||||
SyntaxShape::Block,
|
||||
SyntaxShape::Math,
|
||||
"the condition that must match",
|
||||
)
|
||||
}
|
||||
@ -26,37 +30,67 @@ impl PerItemCommand for Where {
|
||||
fn run(
|
||||
&self,
|
||||
call_info: &CallInfo,
|
||||
_registry: &CommandRegistry,
|
||||
registry: &CommandRegistry,
|
||||
_raw_args: &RawCommandArgs,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let condition = call_info.args.expect_nth(0)?;
|
||||
let stream = match condition {
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
..
|
||||
tag,
|
||||
} => {
|
||||
let result = block.invoke(&Scope::new(input.clone()));
|
||||
match result {
|
||||
Ok(v) => {
|
||||
if v.is_true() {
|
||||
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
|
||||
} else {
|
||||
VecDeque::new()
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"where needs a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition = evaluate_baseline_expr(&condition, registry, &Scope::new(input.clone()))?;
|
||||
|
||||
let stream = match condition.as_bool() {
|
||||
Ok(b) => {
|
||||
if b {
|
||||
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
|
||||
} else {
|
||||
VecDeque::new()
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(stream.into())
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ struct WhichArgs {
|
||||
|
||||
fn which(
|
||||
WhichArgs { application, all }: WhichArgs,
|
||||
RunnableContext { commands, .. }: RunnableContext,
|
||||
RunnableContext { registry, .. }: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let external = application.starts_with('^');
|
||||
let item = if external {
|
||||
@ -89,7 +89,7 @@ fn which(
|
||||
|
||||
let stream = async_stream! {
|
||||
if !external {
|
||||
let builtin = commands.has(&item);
|
||||
let builtin = registry.has(&item);
|
||||
if builtin {
|
||||
yield ReturnSuccess::value(entry_builtin!(item, application.tag.clone()));
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
|
||||
use crate::commands::{
|
||||
command::CommandArgs, command::RawCommandArgs, Command, UnevaluatedCallInfo,
|
||||
};
|
||||
use crate::env::host::Host;
|
||||
use crate::shell::shell_manager::ShellManager;
|
||||
use crate::stream::{InputStream, OutputStream};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::{hir, hir::syntax_shape::ExpandContext, hir::syntax_shape::SignatureRegistry};
|
||||
use nu_protocol::Signature;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::{hir, Scope, Signature};
|
||||
use nu_source::{Tag, Text};
|
||||
use parking_lot::Mutex;
|
||||
use std::error::Error;
|
||||
@ -40,12 +42,6 @@ impl CommandRegistry {
|
||||
}
|
||||
|
||||
impl CommandRegistry {
|
||||
pub(crate) fn empty() -> CommandRegistry {
|
||||
CommandRegistry {
|
||||
registry: Arc::new(Mutex::new(IndexMap::default())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_command(&self, name: &str) -> Option<Arc<Command>> {
|
||||
let registry = self.registry.lock();
|
||||
|
||||
@ -92,15 +88,28 @@ impl Context {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub(crate) fn expand_context<'context>(
|
||||
&'context self,
|
||||
source: &'context Text,
|
||||
) -> ExpandContext {
|
||||
ExpandContext::new(
|
||||
Box::new(self.registry.clone()),
|
||||
source,
|
||||
self.shell_manager.homedir(),
|
||||
)
|
||||
pub(crate) fn from_raw(raw_args: &RawCommandArgs, registry: &CommandRegistry) -> Context {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: raw_args.host.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: raw_args.host.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn basic() -> Result<Context, Box<dyn Error>> {
|
||||
@ -138,6 +147,14 @@ impl Context {
|
||||
self.with_errors(|errors| errors.push(error))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_errors(&mut self) {
|
||||
self.current_errors.lock().clear()
|
||||
}
|
||||
|
||||
pub(crate) fn get_errors(&self) -> Vec<ShellError> {
|
||||
self.current_errors.lock().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
|
||||
let errors = self.current_errors.clone();
|
||||
let mut errors = errors.lock();
|
||||
@ -187,18 +204,18 @@ impl Context {
|
||||
command: Arc<Command>,
|
||||
name_tag: Tag,
|
||||
args: hir::Call,
|
||||
source: &Text,
|
||||
scope: &Scope,
|
||||
input: InputStream,
|
||||
) -> OutputStream {
|
||||
let command_args = self.command_args(args, input, source, name_tag);
|
||||
let command_args = self.command_args(args, input, name_tag, scope);
|
||||
command.run(command_args, self.registry())
|
||||
}
|
||||
|
||||
fn call_info(&self, args: hir::Call, source: &Text, name_tag: Tag) -> UnevaluatedCallInfo {
|
||||
fn call_info(&self, args: hir::Call, name_tag: Tag, scope: &Scope) -> UnevaluatedCallInfo {
|
||||
UnevaluatedCallInfo {
|
||||
args,
|
||||
source: source.clone(),
|
||||
name_tag,
|
||||
scope: scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,14 +223,14 @@ impl Context {
|
||||
&self,
|
||||
args: hir::Call,
|
||||
input: InputStream,
|
||||
source: &Text,
|
||||
name_tag: Tag,
|
||||
scope: &Scope,
|
||||
) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host.clone(),
|
||||
ctrl_c: self.ctrl_c.clone(),
|
||||
shell_manager: self.shell_manager.clone(),
|
||||
call_info: self.call_info(args, source, name_tag),
|
||||
call_info: self.call_info(args, name_tag, scope),
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,13 @@
|
||||
pub(crate) mod shape;
|
||||
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::{hir, CompareOperator};
|
||||
use nu_protocol::{
|
||||
Evaluate, EvaluateTrait, Primitive, Scope, ShellTypeName, SpannedTypeName, TaggedDictBuilder,
|
||||
UntaggedValue, Value,
|
||||
hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::ValueExt;
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::Zero;
|
||||
@ -23,49 +18,18 @@ use std::time::SystemTime;
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
|
||||
pub struct Operation {
|
||||
pub(crate) left: Value,
|
||||
pub(crate) operator: CompareOperator,
|
||||
pub(crate) operator: hir::Operator,
|
||||
pub(crate) right: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
|
||||
pub struct Block {
|
||||
pub(crate) expressions: Vec<hir::SpannedExpression>,
|
||||
pub(crate) source: Text,
|
||||
pub(crate) commands: hir::Commands,
|
||||
pub(crate) tag: Tag,
|
||||
}
|
||||
|
||||
interfaces!(Block: dyn ObjectHash);
|
||||
|
||||
#[typetag::serde]
|
||||
impl EvaluateTrait for Block {
|
||||
fn invoke(&self, scope: &Scope) -> Result<Value, ShellError> {
|
||||
if self.expressions.is_empty() {
|
||||
return Ok(UntaggedValue::nothing().into_value(&self.tag));
|
||||
}
|
||||
|
||||
let mut last = Ok(UntaggedValue::nothing().into_value(&self.tag));
|
||||
|
||||
trace!(
|
||||
"EXPRS = {:?}",
|
||||
self.expressions
|
||||
.iter()
|
||||
.map(|e| format!("{:?}", e))
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
for expr in self.expressions.iter() {
|
||||
last = evaluate_baseline_expr(&expr, &CommandRegistry::empty(), &scope, &self.source)
|
||||
}
|
||||
|
||||
last
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Evaluate {
|
||||
let block = self.clone();
|
||||
Evaluate::new(block)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Switch {
|
||||
Present,
|
||||
@ -123,7 +87,8 @@ pub(crate) enum CompareValues {
|
||||
Decimals(BigDecimal, BigDecimal),
|
||||
String(String, String),
|
||||
Date(DateTime<Utc>, DateTime<Utc>),
|
||||
DateDuration(DateTime<Utc>, u64),
|
||||
DateDuration(DateTime<Utc>, i64),
|
||||
Booleans(bool, bool),
|
||||
}
|
||||
|
||||
impl CompareValues {
|
||||
@ -137,9 +102,14 @@ impl CompareValues {
|
||||
use std::time::Duration;
|
||||
|
||||
// Create the datetime we're comparing against, as duration is an offset from now
|
||||
let right: DateTime<Utc> = (SystemTime::now() - Duration::from_secs(*right)).into();
|
||||
let right: DateTime<Utc> = if *right < 0 {
|
||||
(SystemTime::now() + Duration::from_secs((*right * -1) as u64)).into()
|
||||
} else {
|
||||
(SystemTime::now() - Duration::from_secs(*right as u64)).into()
|
||||
};
|
||||
right.cmp(left)
|
||||
}
|
||||
CompareValues::Booleans(left, right) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,11 +146,15 @@ fn coerce_compare_primitive(
|
||||
(Decimal(left), Bytes(right)) => {
|
||||
CompareValues::Decimals(left.clone(), BigDecimal::from(*right))
|
||||
}
|
||||
(Bytes(left), Bytes(right)) => {
|
||||
CompareValues::Ints(BigInt::from(*left), BigInt::from(*right))
|
||||
}
|
||||
(Bytes(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
|
||||
(Bytes(left), Decimal(right)) => {
|
||||
CompareValues::Decimals(BigDecimal::from(*left), right.clone())
|
||||
}
|
||||
(Bytes(left), Nothing) => CompareValues::Ints(BigInt::from(*left), BigInt::from(0)),
|
||||
(Nothing, Nothing) => CompareValues::Ints(BigInt::from(0), BigInt::from(0)),
|
||||
(Nothing, Bytes(right)) => CompareValues::Ints(BigInt::from(0), BigInt::from(*right)),
|
||||
(Int(left), Nothing) => CompareValues::Ints(left.clone(), BigInt::from(0)),
|
||||
(Nothing, Int(right)) => CompareValues::Ints(BigInt::from(0), right.clone()),
|
||||
@ -192,6 +166,7 @@ fn coerce_compare_primitive(
|
||||
(Line(left), Line(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
(Date(left), Date(right)) => CompareValues::Date(*left, *right),
|
||||
(Date(left), Duration(right)) => CompareValues::DateDuration(*left, *right),
|
||||
(Boolean(left), Boolean(right)) => CompareValues::Booleans(*left, *right),
|
||||
_ => return Err((left.type_name(), right.type_name())),
|
||||
})
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ pub enum InlineShape {
|
||||
Pattern(String),
|
||||
Boolean(bool),
|
||||
Date(DateTime<Utc>),
|
||||
Duration(u64),
|
||||
Duration(i64),
|
||||
Path(PathBuf),
|
||||
Binary,
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
use nu_parser::Number;
|
||||
use nu_protocol::Primitive;
|
||||
use nu_protocol::{hir::Number, Primitive};
|
||||
|
||||
pub fn number(number: impl Into<Number>) -> Primitive {
|
||||
let number = number.into();
|
||||
|
@ -3,7 +3,8 @@ use crate::data::base::shape::{Column, InlineShape};
|
||||
use crate::data::primitive::style_primitive;
|
||||
use chrono::DateTime;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::CompareOperator;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{Primitive, Type, UntaggedValue};
|
||||
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};
|
||||
|
||||
@ -21,8 +22,91 @@ pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
||||
}
|
||||
|
||||
pub fn compute_values(
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
match (left, right) {
|
||||
(UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) {
|
||||
(Primitive::Bytes(x), Primitive::Bytes(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + y),
|
||||
Operator::Minus => Ok(x - y),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Bytes(result)))
|
||||
}
|
||||
(Primitive::Int(x), Primitive::Int(y)) => match operator {
|
||||
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
|
||||
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
|
||||
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
|
||||
Operator::Divide => {
|
||||
if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Int(x / y)))
|
||||
} else {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(
|
||||
bigdecimal::BigDecimal::from(x.clone())
|
||||
/ bigdecimal::BigDecimal::from(y.clone()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
(Primitive::Decimal(x), Primitive::Int(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Minus => Ok(x - bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Multiply => Ok(x * bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Divide => Ok(x / bigdecimal::BigDecimal::from(y.clone())),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Int(x), Primitive::Decimal(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(bigdecimal::BigDecimal::from(x.clone()) + y),
|
||||
Operator::Minus => Ok(bigdecimal::BigDecimal::from(x.clone()) - y),
|
||||
Operator::Multiply => Ok(bigdecimal::BigDecimal::from(x.clone()) * y),
|
||||
Operator::Divide => Ok(bigdecimal::BigDecimal::from(x.clone()) / y),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Decimal(x), Primitive::Decimal(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + y),
|
||||
Operator::Minus => Ok(x - y),
|
||||
Operator::Multiply => Ok(x * y),
|
||||
Operator::Divide => Ok(x / y),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Date(x), Primitive::Date(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Minus => Ok(x.signed_duration_since(*y).num_seconds()),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Duration(result)))
|
||||
}
|
||||
(Primitive::Date(x), Primitive::Duration(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x
|
||||
.checked_add_signed(chrono::Duration::seconds(*y as i64))
|
||||
.expect("Overflowing add of duration")),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(result)))
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compare_values(
|
||||
operator: CompareOperator,
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<bool, (&'static str, &'static str)> {
|
||||
@ -34,15 +118,16 @@ pub fn compare_values(
|
||||
use std::cmp::Ordering;
|
||||
|
||||
let result = match (operator, ordering) {
|
||||
(CompareOperator::Equal, Ordering::Equal) => true,
|
||||
(CompareOperator::NotEqual, Ordering::Less)
|
||||
| (CompareOperator::NotEqual, Ordering::Greater) => true,
|
||||
(CompareOperator::LessThan, Ordering::Less) => true,
|
||||
(CompareOperator::GreaterThan, Ordering::Greater) => true,
|
||||
(CompareOperator::GreaterThanOrEqual, Ordering::Greater)
|
||||
| (CompareOperator::GreaterThanOrEqual, Ordering::Equal) => true,
|
||||
(CompareOperator::LessThanOrEqual, Ordering::Less)
|
||||
| (CompareOperator::LessThanOrEqual, Ordering::Equal) => true,
|
||||
(Operator::Equal, Ordering::Equal) => true,
|
||||
(Operator::NotEqual, Ordering::Less) | (Operator::NotEqual, Ordering::Greater) => {
|
||||
true
|
||||
}
|
||||
(Operator::LessThan, Ordering::Less) => true,
|
||||
(Operator::GreaterThan, Ordering::Greater) => true,
|
||||
(Operator::GreaterThanOrEqual, Ordering::Greater)
|
||||
| (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
|
||||
(Operator::LessThanOrEqual, Ordering::Less)
|
||||
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use log::trace;
|
||||
use nu_errors::{CoerceInto, ShellError};
|
||||
use nu_protocol::{
|
||||
CallInfo, ColumnPath, Evaluate, Primitive, RangeInclusion, ShellTypeName, UntaggedValue, Value,
|
||||
hir::Block, CallInfo, ColumnPath, Primitive, RangeInclusion, ShellTypeName, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
use nu_source::{HasSpan, Spanned, SpannedItem, Tagged, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
@ -368,7 +369,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
|
||||
))
|
||||
}
|
||||
};
|
||||
return visit::<Evaluate, _>(block, name, fields, visitor);
|
||||
return visit::<Block, _>(block, name, fields, visitor);
|
||||
}
|
||||
|
||||
if name == "ColumnPath" {
|
||||
|
2
crates/nu-cli/src/env/environment.rs
vendored
2
crates/nu-cli/src/env/environment.rs
vendored
@ -275,7 +275,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
actual.path(),
|
||||
Some(
|
||||
UntaggedValue::table(&vec![
|
||||
UntaggedValue::table(&[
|
||||
UntaggedValue::string("/Users/andresrobalino/.volta/bin")
|
||||
.into_untagged_value(),
|
||||
UntaggedValue::string("/users/mosqueteros/bin").into_untagged_value(),
|
||||
|
@ -3,22 +3,19 @@ use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::hir;
|
||||
use nu_protocol::{EvaluatedArgs, Scope, UntaggedValue, Value};
|
||||
use nu_source::Text;
|
||||
use nu_protocol::{hir, EvaluatedArgs, Scope, UntaggedValue, Value};
|
||||
|
||||
pub(crate) fn evaluate_args(
|
||||
call: &hir::Call,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<EvaluatedArgs, ShellError> {
|
||||
let positional: Result<Option<Vec<_>>, _> = call
|
||||
.positional
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
p.iter()
|
||||
.map(|e| evaluate_baseline_expr(e, registry, scope, source))
|
||||
.map(|e| evaluate_baseline_expr(e, registry, scope))
|
||||
.collect()
|
||||
})
|
||||
.transpose();
|
||||
@ -36,11 +33,9 @@ pub(crate) fn evaluate_args(
|
||||
hir::NamedValue::PresentSwitch(tag) => {
|
||||
results.insert(name.clone(), UntaggedValue::boolean(true).into_value(tag));
|
||||
}
|
||||
hir::NamedValue::Value(expr) => {
|
||||
results.insert(
|
||||
name.clone(),
|
||||
evaluate_baseline_expr(expr, registry, scope, source)?,
|
||||
);
|
||||
hir::NamedValue::Value(_, expr) => {
|
||||
results
|
||||
.insert(name.clone(), evaluate_baseline_expr(expr, registry, scope)?);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
@ -1,28 +1,24 @@
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::base::Block;
|
||||
use crate::evaluate::operator::apply_operator;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::{ArgumentError, ShellError};
|
||||
use nu_parser::hir::{self, Expression, SpannedExpression};
|
||||
use nu_protocol::hir::{self, Expression, SpannedExpression};
|
||||
use nu_protocol::{
|
||||
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue,
|
||||
Value,
|
||||
ColumnPath, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Text;
|
||||
|
||||
pub(crate) fn evaluate_baseline_expr(
|
||||
expr: &SpannedExpression,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = Tag {
|
||||
span: expr.span,
|
||||
anchor: None,
|
||||
};
|
||||
match &expr.expr {
|
||||
Expression::Literal(literal) => Ok(evaluate_literal(literal, expr.span, source)),
|
||||
Expression::Literal(literal) => Ok(evaluate_literal(literal, expr.span)),
|
||||
Expression::ExternalWord => Err(ShellError::argument_error(
|
||||
"Invalid external word".spanned(tag.span),
|
||||
ArgumentError::InvalidExternalWord,
|
||||
@ -31,29 +27,35 @@ pub(crate) fn evaluate_baseline_expr(
|
||||
Expression::Synthetic(hir::Synthetic::String(s)) => {
|
||||
Ok(UntaggedValue::string(s).into_untagged_value())
|
||||
}
|
||||
Expression::Variable(var) => evaluate_reference(var, scope, source, tag),
|
||||
Expression::Command(_) => evaluate_command(tag, scope, source),
|
||||
Expression::ExternalCommand(external) => evaluate_external(external, scope, source),
|
||||
Expression::Variable(var) => evaluate_reference(var, scope, tag),
|
||||
Expression::Command(_) => evaluate_command(tag, scope),
|
||||
Expression::ExternalCommand(external) => evaluate_external(external, scope),
|
||||
Expression::Binary(binary) => {
|
||||
let left = evaluate_baseline_expr(binary.left(), registry, scope, source)?;
|
||||
let right = evaluate_baseline_expr(binary.right(), registry, scope, source)?;
|
||||
// TODO: If we want to add short-circuiting, we'll need to move these down
|
||||
let left = evaluate_baseline_expr(&binary.left, registry, scope)?;
|
||||
let right = evaluate_baseline_expr(&binary.right, registry, scope)?;
|
||||
|
||||
trace!("left={:?} right={:?}", left.value, right.value);
|
||||
|
||||
match apply_operator(**binary.op(), &left, &right) {
|
||||
Ok(result) => Ok(result.into_value(tag)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(binary.left().span),
|
||||
right_type.spanned(binary.right().span),
|
||||
)),
|
||||
match binary.op.expr {
|
||||
Expression::Literal(hir::Literal::Operator(op)) => {
|
||||
match apply_operator(op, &left, &right) {
|
||||
Ok(result) => Ok(result.into_value(tag)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(binary.left.span),
|
||||
right_type.spanned(binary.right.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Expression::Range(range) => {
|
||||
let left = range.left();
|
||||
let right = range.right();
|
||||
let left = &range.left;
|
||||
let right = &range.right;
|
||||
|
||||
let left = evaluate_baseline_expr(left, registry, scope, source)?;
|
||||
let right = evaluate_baseline_expr(right, registry, scope, source)?;
|
||||
let left = evaluate_baseline_expr(left, registry, scope)?;
|
||||
let right = evaluate_baseline_expr(right, registry, scope)?;
|
||||
let left_span = left.tag.span;
|
||||
let right_span = right.tag.span;
|
||||
|
||||
@ -72,23 +74,18 @@ pub(crate) fn evaluate_baseline_expr(
|
||||
let mut exprs = vec![];
|
||||
|
||||
for expr in list {
|
||||
let expr = evaluate_baseline_expr(expr, registry, scope, source)?;
|
||||
let expr = evaluate_baseline_expr(expr, registry, scope)?;
|
||||
exprs.push(expr);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::Table(exprs).into_value(tag))
|
||||
}
|
||||
Expression::Block(block) => Ok(UntaggedValue::Block(Evaluate::new(Block::new(
|
||||
block.clone(),
|
||||
source.clone(),
|
||||
tag.clone(),
|
||||
)))
|
||||
.into_value(&tag)),
|
||||
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
|
||||
Expression::Path(path) => {
|
||||
let value = evaluate_baseline_expr(path.head(), registry, scope, source)?;
|
||||
let value = evaluate_baseline_expr(&path.head, registry, scope)?;
|
||||
let mut item = value;
|
||||
|
||||
for member in path.tail() {
|
||||
for member in &path.tail {
|
||||
let next = item.get_data_by_member(member);
|
||||
|
||||
match next {
|
||||
@ -107,7 +104,7 @@ pub(crate) fn evaluate_baseline_expr(
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
&tag,
|
||||
&member.span,
|
||||
));
|
||||
} else {
|
||||
return Err(err);
|
||||
@ -123,42 +120,46 @@ pub(crate) fn evaluate_baseline_expr(
|
||||
Ok(item.value.into_value(tag))
|
||||
}
|
||||
Expression::Boolean(_boolean) => unimplemented!(),
|
||||
Expression::Garbage => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_literal(literal: &hir::Literal, span: Span, source: &Text) -> Value {
|
||||
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
|
||||
match &literal {
|
||||
hir::Literal::ColumnPath(path) => {
|
||||
let members = path
|
||||
.iter()
|
||||
.map(|member| member.to_path_member(source))
|
||||
.collect();
|
||||
let members = path.iter().map(|member| member.to_path_member()).collect();
|
||||
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
|
||||
.into_value(span)
|
||||
}
|
||||
hir::Literal::Number(int) => match int {
|
||||
nu_parser::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
|
||||
nu_parser::Number::Decimal(d) => UntaggedValue::decimal(d.clone()).into_value(span),
|
||||
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
|
||||
nu_protocol::hir::Number::Decimal(d) => {
|
||||
UntaggedValue::decimal(d.clone()).into_value(span)
|
||||
}
|
||||
},
|
||||
hir::Literal::Size(int, unit) => unit.compute(&int).into_value(span),
|
||||
hir::Literal::String(tag) => UntaggedValue::string(tag.slice(source)).into_value(span),
|
||||
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
|
||||
hir::Literal::GlobPattern(pattern) => UntaggedValue::pattern(pattern).into_value(span),
|
||||
hir::Literal::Bare => UntaggedValue::string(span.slice(source)).into_value(span),
|
||||
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
|
||||
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_reference(
|
||||
name: &hir::Variable,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
tag: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
fn evaluate_reference(name: &hir::Variable, scope: &Scope, tag: Tag) -> Result<Value, ShellError> {
|
||||
trace!("Evaluating {:?} with Scope {:?}", name, scope);
|
||||
match name {
|
||||
hir::Variable::It(_) => Ok(scope.it.value.clone().into_value(tag)),
|
||||
hir::Variable::Other(inner) => match inner.slice(source) {
|
||||
x if x == "nu" => crate::evaluate::variables::nu(tag),
|
||||
hir::Variable::Other(name, _) => match name {
|
||||
x if x == "$nu" => crate::evaluate::variables::nu(tag),
|
||||
x if x == "$true" => Ok(Value {
|
||||
value: UntaggedValue::boolean(true),
|
||||
tag,
|
||||
}),
|
||||
x if x == "$false" => Ok(Value {
|
||||
value: UntaggedValue::boolean(false),
|
||||
tag,
|
||||
}),
|
||||
x => Ok(scope
|
||||
.vars
|
||||
.get(x)
|
||||
@ -169,16 +170,15 @@ fn evaluate_reference(
|
||||
}
|
||||
|
||||
fn evaluate_external(
|
||||
external: &hir::ExternalCommand,
|
||||
external: &hir::ExternalStringCommand,
|
||||
_scope: &Scope,
|
||||
_source: &Text,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::syntax_error(
|
||||
"Unexpected external command".spanned(*external.name()),
|
||||
"Unexpected external command".spanned(external.name.span),
|
||||
))
|
||||
}
|
||||
|
||||
fn evaluate_command(tag: Tag, _scope: &Scope, _source: &Text) -> Result<Value, ShellError> {
|
||||
fn evaluate_command(tag: Tag, _scope: &Scope) -> Result<Value, ShellError> {
|
||||
Err(ShellError::syntax_error(
|
||||
"Unexpected command".spanned(tag.span),
|
||||
))
|
||||
|
@ -1,30 +1,43 @@
|
||||
use crate::data::value;
|
||||
use nu_parser::CompareOperator;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use std::ops::Not;
|
||||
|
||||
pub fn apply_operator(
|
||||
op: CompareOperator,
|
||||
op: Operator,
|
||||
left: &Value,
|
||||
right: &Value,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
match op {
|
||||
CompareOperator::Equal
|
||||
| CompareOperator::NotEqual
|
||||
| CompareOperator::LessThan
|
||||
| CompareOperator::GreaterThan
|
||||
| CompareOperator::LessThanOrEqual
|
||||
| CompareOperator::GreaterThanOrEqual => {
|
||||
Operator::Equal
|
||||
| Operator::NotEqual
|
||||
| Operator::LessThan
|
||||
| Operator::GreaterThan
|
||||
| Operator::LessThanOrEqual
|
||||
| Operator::GreaterThanOrEqual => {
|
||||
value::compare_values(op, left, right).map(UntaggedValue::boolean)
|
||||
}
|
||||
CompareOperator::Contains => contains(left, right).map(UntaggedValue::boolean),
|
||||
CompareOperator::NotContains => contains(left, right)
|
||||
Operator::Contains => string_contains(left, right).map(UntaggedValue::boolean),
|
||||
Operator::NotContains => string_contains(left, right)
|
||||
.map(Not::not)
|
||||
.map(UntaggedValue::boolean),
|
||||
Operator::Plus => value::compute_values(op, left, right),
|
||||
Operator::Minus => value::compute_values(op, left, right),
|
||||
Operator::Multiply => value::compute_values(op, left, right),
|
||||
Operator::Divide => value::compute_values(op, left, right),
|
||||
Operator::In => table_contains(left, right).map(UntaggedValue::boolean),
|
||||
Operator::And => match (left.as_bool(), right.as_bool()) {
|
||||
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left && right)),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
Operator::Or => match (left.as_bool(), right.as_bool()) {
|
||||
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left || right)),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(
|
||||
fn string_contains(
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<bool, (&'static str, &'static str)> {
|
||||
@ -48,3 +61,14 @@ fn contains(
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_contains(
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<bool, (&'static str, &'static str)> {
|
||||
let left = left.clone();
|
||||
match right {
|
||||
UntaggedValue::Table(values) => Ok(values.iter().any(|x| x.value == left)),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
pub(crate) mod entries;
|
||||
pub(crate) mod generic;
|
||||
pub(crate) mod list;
|
||||
pub(crate) mod table;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
pub(crate) use entries::EntriesView;
|
||||
pub(crate) use table::TableView;
|
||||
|
||||
pub(crate) trait RenderView {
|
||||
|
@ -1,50 +0,0 @@
|
||||
use crate::data::value;
|
||||
use crate::format::RenderView;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Value;
|
||||
|
||||
use derive_new::new;
|
||||
|
||||
// An entries list is printed like this:
|
||||
//
|
||||
// name : ...
|
||||
// name2 : ...
|
||||
// another_name : ...
|
||||
#[derive(new)]
|
||||
pub struct EntriesView {
|
||||
entries: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl EntriesView {
|
||||
pub(crate) fn from_value(value: &Value) -> EntriesView {
|
||||
let descs = value.data_descriptors();
|
||||
let mut entries = vec![];
|
||||
|
||||
for desc in descs {
|
||||
let value = value.get_data(&desc);
|
||||
|
||||
let formatted_value = value::format_leaf(value.borrow()).plain_string(75);
|
||||
|
||||
entries.push((desc.clone(), formatted_value))
|
||||
}
|
||||
|
||||
EntriesView::new(entries)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderView for EntriesView {
|
||||
fn render_view(&self, _host: &mut dyn Host) -> Result<(), ShellError> {
|
||||
if self.entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(max_name_size) = self.entries.iter().map(|(n, _)| n.len()).max() {
|
||||
for (name, value) in &self.entries {
|
||||
outln!("{:width$} : {}", name, value, width = max_name_size)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::format::{EntriesView, RenderView, TableView};
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{format_primitive, UntaggedValue, Value};
|
||||
|
||||
// A list is printed one line at a time with an optional separator between groups
|
||||
#[derive(new)]
|
||||
pub struct GenericView<'value> {
|
||||
value: &'value Value,
|
||||
}
|
||||
|
||||
impl RenderView for GenericView<'_> {
|
||||
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
|
||||
let tag = &self.value.tag;
|
||||
match &self.value.value {
|
||||
UntaggedValue::Primitive(p) => {
|
||||
host.stdout(&format_primitive(p, None));
|
||||
Ok(())
|
||||
}
|
||||
UntaggedValue::Table(l) => {
|
||||
let view = TableView::from_list(l, 0);
|
||||
|
||||
if let Some(view) = view {
|
||||
view.render_view(host)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
o @ UntaggedValue::Row(_) => {
|
||||
let view = EntriesView::from_value(&o.clone().into_value(tag));
|
||||
view.render_view(host)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
b @ UntaggedValue::Block(_) => {
|
||||
let printed = format_leaf(b).plain_string(host.width());
|
||||
let view = EntriesView::from_value(&UntaggedValue::string(printed).into_value(tag));
|
||||
view.render_view(host)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
UntaggedValue::Error(e) => Err(e.clone()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
use crate::format::RenderView;
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
// A list is printed one line at a time with an optional separator between groups
|
||||
|
||||
#[derive(new)]
|
||||
pub struct ListView {
|
||||
list: Vec<Vec<String>>,
|
||||
sep: String,
|
||||
}
|
||||
|
||||
impl RenderView for ListView {
|
||||
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
|
||||
for output in &self.list {
|
||||
let string: String = output.iter().map(|l| format!("{}\n", l)).collect();
|
||||
host.stdout(&format!("{}{}", string, self.sep));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ use textwrap::fill;
|
||||
|
||||
use prettytable::format::{Alignment, FormatBuilder, LinePosition, LineSeparator};
|
||||
use prettytable::{color, Attr, Cell, Row, Table};
|
||||
use term::color::Color;
|
||||
|
||||
type Entries = Vec<Vec<(String, &'static str)>>;
|
||||
|
||||
@ -74,7 +73,19 @@ impl TableView {
|
||||
}
|
||||
}
|
||||
|
||||
fn are_table_indexes_disabled() -> bool {
|
||||
let config = crate::data::config::config(Tag::unknown());
|
||||
match config {
|
||||
Ok(config) => {
|
||||
let disable_indexes = config.get("disable_table_indexes");
|
||||
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx: usize) -> Entries {
|
||||
let disable_indexes = are_table_indexes_disabled();
|
||||
let mut entries = vec![];
|
||||
|
||||
if headers.is_empty() {
|
||||
@ -118,12 +129,16 @@ fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx:
|
||||
.collect();
|
||||
|
||||
// Indices are green, bold, right-aligned:
|
||||
row.insert(0, ((starting_idx + idx).to_string(), "Fgbr"));
|
||||
if !disable_indexes {
|
||||
row.insert(0, ((starting_idx + idx).to_string(), "Fgbr"));
|
||||
}
|
||||
|
||||
entries.push(row);
|
||||
}
|
||||
|
||||
headers.insert(0, "#".to_owned());
|
||||
if !disable_indexes {
|
||||
headers.insert(0, "#".to_owned());
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
@ -389,7 +404,7 @@ impl RenderView for TableView {
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_color(s: String) -> Option<Color> {
|
||||
fn str_to_color(s: String) -> Option<color::Color> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(color::GREEN),
|
||||
"r" | "red" => Some(color::RED),
|
||||
|
@ -7,6 +7,12 @@ extern crate indexmap;
|
||||
#[macro_use]
|
||||
mod prelude;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate quickcheck;
|
||||
#[cfg(test)]
|
||||
#[macro_use(quickcheck)]
|
||||
extern crate quickcheck_macros;
|
||||
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod context;
|
||||
@ -17,17 +23,19 @@ mod evaluate;
|
||||
mod format;
|
||||
mod futures;
|
||||
mod git;
|
||||
mod path;
|
||||
mod shell;
|
||||
mod stream;
|
||||
mod utils;
|
||||
|
||||
pub use crate::cli::{cli, create_default_context, load_plugins, run_pipeline_standalone};
|
||||
pub use crate::cli::{
|
||||
cli, create_default_context, load_plugins, run_pipeline_standalone, run_vec_of_pipelines,
|
||||
};
|
||||
pub use crate::data::dict::TaggedListBuilder;
|
||||
pub use crate::data::primitive;
|
||||
pub use crate::data::value;
|
||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||
pub use crate::env::host::BasicHost;
|
||||
pub use nu_parser::TokenTreeBuilder;
|
||||
pub use nu_value_ext::ValueExt;
|
||||
pub use num_traits::cast::ToPrimitive;
|
||||
|
||||
|
103
crates/nu-cli/src/path.rs
Normal file
103
crates/nu-cli/src/path.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use std::io;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
pub fn absolutize<P, Q>(relative_to: P, path: Q) -> PathBuf
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let path = relative_to.as_ref().join(path);
|
||||
|
||||
let (relative_to, path) = {
|
||||
let components: Vec<_> = path.components().collect();
|
||||
let separator = components
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, c)| c == &&Component::CurDir || c == &&Component::ParentDir);
|
||||
|
||||
if let Some((index, _)) = separator {
|
||||
let (absolute, relative) = components.split_at(index);
|
||||
let absolute: PathBuf = absolute.iter().collect();
|
||||
let relative: PathBuf = relative.iter().collect();
|
||||
|
||||
(absolute, relative)
|
||||
} else {
|
||||
(relative_to.as_ref().to_path_buf(), path)
|
||||
}
|
||||
};
|
||||
|
||||
let path = if path.is_relative() {
|
||||
let mut result = relative_to;
|
||||
path.components().for_each(|component| match component {
|
||||
Component::ParentDir => {
|
||||
result.pop();
|
||||
}
|
||||
Component::Normal(normal) => result.push(normal),
|
||||
_ => {}
|
||||
});
|
||||
|
||||
result
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
dunce::simplified(&path).to_path_buf()
|
||||
}
|
||||
|
||||
pub fn canonicalize<P, Q>(relative_to: P, path: Q) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let canonicalized = absolutize(relative_to, path);
|
||||
let path = match std::fs::read_link(&canonicalized) {
|
||||
Ok(resolved) => resolved,
|
||||
Err(e) => {
|
||||
if canonicalized.exists() {
|
||||
canonicalized
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(dunce::simplified(&path).to_path_buf())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn absolutize_two_dots() {
|
||||
let relative_to = Path::new("/foo/bar");
|
||||
let path = Path::new("..");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/foo"), // missing path
|
||||
absolutize(relative_to, path)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_should_succeed() -> io::Result<()> {
|
||||
let relative_to = Path::new("/foo/bar");
|
||||
let path = Path::new("../..");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/"), // existing path
|
||||
canonicalize(relative_to, path)?,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_should_fail() {
|
||||
let relative_to = Path::new("/foo/bar/baz"); // '/foo' is missing
|
||||
let path = Path::new("../..");
|
||||
|
||||
assert!(canonicalize(relative_to, path).is_err());
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ macro_rules! trace_stream {
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let objects = $expr.values.inspect(move |o| {
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
"{} = {}",
|
||||
@ -49,7 +49,7 @@ macro_rules! trace_out_stream {
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let objects = $expr.values.inspect(move |o| {
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
"{} = {}",
|
||||
@ -91,10 +91,10 @@ pub(crate) use async_stream::stream as async_stream;
|
||||
pub(crate) use bigdecimal::BigDecimal;
|
||||
pub(crate) use futures::stream::BoxStream;
|
||||
pub(crate) use futures::{FutureExt, Stream, StreamExt};
|
||||
pub(crate) use nu_protocol::{EvaluateTrait, MaybeOwned};
|
||||
pub(crate) use nu_protocol::MaybeOwned;
|
||||
pub(crate) use nu_source::{
|
||||
b, AnchorLocation, DebugDocBuilder, HasSpan, PrettyDebug, PrettyDebugWithSource, Span,
|
||||
SpannedItem, Tag, TaggedItem, Text,
|
||||
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
|
||||
TaggedItem, Text,
|
||||
};
|
||||
pub(crate) use nu_value_ext::ValueExt;
|
||||
pub(crate) use num_bigint::BigInt;
|
||||
@ -132,17 +132,13 @@ where
|
||||
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
|
||||
{
|
||||
fn to_input_stream(self) -> InputStream {
|
||||
InputStream {
|
||||
values: self
|
||||
.map(|item| match item.into() {
|
||||
Ok(result) => result,
|
||||
Err(err) => match HasFallibleSpan::maybe_span(&err) {
|
||||
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
|
||||
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
|
||||
},
|
||||
})
|
||||
.boxed(),
|
||||
}
|
||||
InputStream::from_stream(self.map(|item| match item.into() {
|
||||
Ok(result) => result,
|
||||
Err(err) => match HasFallibleSpan::maybe_span(&err) {
|
||||
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
|
||||
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
use crate::context::CommandRegistry;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_source::{HasSpan, Text};
|
||||
use rustyline::completion::{Completer, FilenameCompleter};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -20,14 +18,6 @@ impl NuCompleter {
|
||||
pos: usize,
|
||||
context: &rustyline::Context,
|
||||
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
|
||||
let text = Text::from(line);
|
||||
let expand_context =
|
||||
ExpandContext::new(Box::new(self.commands.clone()), &text, self.homedir.clone());
|
||||
|
||||
#[allow(unused)]
|
||||
// smarter completions
|
||||
let shapes = nu_parser::pipeline_shapes(line, expand_context);
|
||||
|
||||
let commands: Vec<String> = self.commands.names();
|
||||
|
||||
let line_chars: Vec<_> = line[..pos].chars().collect();
|
||||
@ -44,7 +34,12 @@ impl NuCompleter {
|
||||
|
||||
// See if we're a flag
|
||||
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
|
||||
completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos);
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
|
||||
completions =
|
||||
self.get_matching_arguments(&lite_block, &line_chars, line, replace_pos, pos);
|
||||
} else {
|
||||
completions = self.file_completer.complete(line, pos, context)?.1;
|
||||
}
|
||||
} else {
|
||||
completions = self.file_completer.complete(line, pos, context)?.1;
|
||||
|
||||
@ -69,15 +64,15 @@ impl NuCompleter {
|
||||
|
||||
for command in commands.iter() {
|
||||
let mut pos = replace_pos;
|
||||
let mut matched = true;
|
||||
let mut matched = false;
|
||||
if pos < line_chars.len() {
|
||||
for chr in command.chars() {
|
||||
if line_chars[pos] != chr {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
if pos == line_chars.len() {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -96,6 +91,7 @@ impl NuCompleter {
|
||||
|
||||
fn get_matching_arguments(
|
||||
&self,
|
||||
lite_block: &nu_parser::LiteBlock,
|
||||
line_chars: &[char],
|
||||
line: &str,
|
||||
replace_pos: usize,
|
||||
@ -108,39 +104,24 @@ impl NuCompleter {
|
||||
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
|
||||
line_copy.replace_range(replace_pos..pos, &replace_string);
|
||||
|
||||
if let Ok(val) = nu_parser::parse(&line_copy) {
|
||||
let source = Text::from(line);
|
||||
let pipeline_list = vec![val.clone()];
|
||||
let result = nu_parser::classify_block(&lite_block, &self.commands);
|
||||
|
||||
let expand_context = nu_parser::ExpandContext {
|
||||
homedir: None,
|
||||
registry: Box::new(self.commands.clone()),
|
||||
source: &source,
|
||||
};
|
||||
for pipeline in &result.block.block {
|
||||
for command in &pipeline.list {
|
||||
if let nu_protocol::hir::ClassifiedCommand::Internal(
|
||||
nu_protocol::hir::InternalCommand { args, .. },
|
||||
) = command
|
||||
{
|
||||
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
|
||||
if let Some(named) = &args.named {
|
||||
for (name, _) in named.iter() {
|
||||
let full_flag = format!("--{}", name);
|
||||
|
||||
let mut iterator =
|
||||
nu_parser::TokensIterator::new(&pipeline_list, expand_context, val.span());
|
||||
|
||||
let result = iterator.expand_infallible(nu_parser::PipelineShape);
|
||||
|
||||
if result.failed.is_none() {
|
||||
for command in result.commands.list {
|
||||
if let nu_parser::ClassifiedCommand::Internal(nu_parser::InternalCommand {
|
||||
args,
|
||||
..
|
||||
}) = command
|
||||
{
|
||||
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
|
||||
if let Some(named) = args.named {
|
||||
for (name, _) in named.iter() {
|
||||
let full_flag = format!("--{}", name);
|
||||
|
||||
if full_flag.starts_with(&substring) {
|
||||
matching_arguments.push(rustyline::completion::Pair {
|
||||
display: full_flag.clone(),
|
||||
replacement: full_flag,
|
||||
});
|
||||
}
|
||||
if full_flag.starts_with(&substring) {
|
||||
matching_arguments.push(rustyline::completion::Pair {
|
||||
display: full_flag.clone(),
|
||||
replacement: full_flag,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,17 @@ use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::data::dir_entry_dict;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::utils::FileStructure;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::hint::{Hinter, HistoryHinter};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use trash as SendToTrash;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
@ -78,29 +77,6 @@ impl FilesystemShell {
|
||||
hinter: HistoryHinter {},
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize(&self, path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
|
||||
let path = if path.as_ref().is_relative() {
|
||||
let components = path.as_ref().components();
|
||||
let mut result = PathBuf::from(self.path());
|
||||
for component in components {
|
||||
match component {
|
||||
Component::CurDir => { /* ignore current dir */ }
|
||||
Component::ParentDir => {
|
||||
result.pop();
|
||||
}
|
||||
Component::Normal(normal) => result.push(normal),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
} else {
|
||||
path.as_ref().into()
|
||||
};
|
||||
|
||||
dunce::canonicalize(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shell for FilesystemShell {
|
||||
@ -148,13 +124,13 @@ impl Shell for FilesystemShell {
|
||||
};
|
||||
|
||||
let mut paths = glob::glob(&path.to_string_lossy())
|
||||
.map_err(|e| ShellError::labeled_error("Glob error", e.to_string(), &p_tag))?
|
||||
.map_err(|e| ShellError::labeled_error(e.to_string(), "invalid pattern", &p_tag))?
|
||||
.peekable();
|
||||
|
||||
if paths.peek().is_none() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Invalid File or Pattern",
|
||||
"invalid file or pattern",
|
||||
"No matches found",
|
||||
"no matches found",
|
||||
&p_tag,
|
||||
));
|
||||
}
|
||||
@ -212,7 +188,7 @@ impl Shell for FilesystemShell {
|
||||
if target == Path::new("-") {
|
||||
PathBuf::from(&self.last_path)
|
||||
} else {
|
||||
let path = self.canonicalize(target).map_err(|_| {
|
||||
let path = canonicalize(self.path(), target).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot change to directory",
|
||||
"directory not found",
|
||||
@ -278,134 +254,81 @@ impl Shell for FilesystemShell {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = name;
|
||||
|
||||
let mut source = PathBuf::from(path);
|
||||
let mut destination = PathBuf::from(path);
|
||||
|
||||
source.push(&src.item);
|
||||
destination.push(&dst.item);
|
||||
let path = Path::new(path);
|
||||
let source = path.join(&src.item);
|
||||
let destination = path.join(&dst.item);
|
||||
|
||||
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
|
||||
Ok(files) => files.collect(),
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Invalid pattern",
|
||||
e.to_string(),
|
||||
"invalid pattern",
|
||||
src.tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if sources.len() == 1 {
|
||||
if let Ok(entry) = &sources[0] {
|
||||
if entry.is_dir() && !recursive.item {
|
||||
return Err(ShellError::labeled_error(
|
||||
"is a directory (not copied). Try using \"--recursive\".",
|
||||
"is a directory (not copied). Try using \"--recursive\".",
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
if sources.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No matches found",
|
||||
"no matches found",
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
|
||||
let mut sources: FileStructure = FileStructure::new();
|
||||
if sources.len() > 1 && !destination.is_dir() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Destination must be a directory when copying multiple files",
|
||||
"is not a directory",
|
||||
dst.tag,
|
||||
));
|
||||
}
|
||||
|
||||
let any_source_is_dir = sources.iter().any(|f| match f {
|
||||
Ok(f) => f.is_dir(),
|
||||
Err(_) => false,
|
||||
});
|
||||
|
||||
if any_source_is_dir && !recursive.item {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Directories must be copied using \"--recursive\"",
|
||||
"resolves to a directory (not copied)",
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
|
||||
for entry in sources {
|
||||
if let Ok(entry) = entry {
|
||||
let mut sources = FileStructure::new();
|
||||
sources.walk_decorate(&entry)?;
|
||||
|
||||
if entry.is_file() {
|
||||
let strategy = |(source_file, _depth_level)| {
|
||||
let sources = sources.paths_applying_with(|(source_file, _depth_level)| {
|
||||
if destination.is_dir() {
|
||||
let mut new_dst = dunce::canonicalize(destination.clone())?;
|
||||
let mut dest = canonicalize(&path, &dst.item)?;
|
||||
if let Some(name) = entry.file_name() {
|
||||
new_dst.push(name);
|
||||
dest.push(name);
|
||||
}
|
||||
Ok((source_file, new_dst))
|
||||
Ok((source_file, dest))
|
||||
} else {
|
||||
Ok((source_file, destination.clone()))
|
||||
}
|
||||
};
|
||||
})?;
|
||||
|
||||
let sources = sources.paths_applying_with(strategy)?;
|
||||
|
||||
for (ref src, ref dst) in sources {
|
||||
for (src, dst) in sources {
|
||||
if src.is_file() {
|
||||
match std::fs::copy(src, dst) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
name_tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
std::fs::copy(src, dst).map_err(|e| {
|
||||
ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry.is_dir() {
|
||||
if !destination.exists() {
|
||||
match std::fs::create_dir_all(&destination) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
dst.tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
|
||||
let strategy = |(source_file, depth_level)| {
|
||||
let mut new_dst = destination.clone();
|
||||
let path = dunce::canonicalize(&source_file)?;
|
||||
|
||||
let mut comps: Vec<_> = path
|
||||
.components()
|
||||
.map(|fragment| fragment.as_os_str())
|
||||
.rev()
|
||||
.take(1 + depth_level)
|
||||
.collect();
|
||||
|
||||
comps.reverse();
|
||||
|
||||
for fragment in comps.iter() {
|
||||
new_dst.push(fragment);
|
||||
}
|
||||
|
||||
Ok((PathBuf::from(&source_file), new_dst))
|
||||
};
|
||||
|
||||
let sources = sources.paths_applying_with(strategy)?;
|
||||
|
||||
let dst_tag = dst.tag;
|
||||
for (ref src, ref dst) in sources {
|
||||
if src.is_dir() && !dst.exists() {
|
||||
match std::fs::create_dir_all(dst) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
dst_tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
}
|
||||
|
||||
if src.is_file() {
|
||||
match std::fs::copy(src, dst) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
name_tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if entry.is_dir() {
|
||||
let destination = if !destination.exists() {
|
||||
destination.clone()
|
||||
} else {
|
||||
match entry.file_name() {
|
||||
Some(name) => destination.push(name),
|
||||
Some(name) => destination.join(name),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Copy aborted. Not a valid path",
|
||||
@ -414,131 +337,46 @@ impl Shell for FilesystemShell {
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match std::fs::create_dir_all(&destination) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
dst.tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
std::fs::create_dir_all(&destination).map_err(|e| {
|
||||
ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag)
|
||||
})?;
|
||||
|
||||
let strategy = |(source_file, depth_level)| {
|
||||
let mut new_dst = dunce::canonicalize(&destination)?;
|
||||
let path = dunce::canonicalize(&source_file)?;
|
||||
let sources = sources.paths_applying_with(|(source_file, depth_level)| {
|
||||
let mut dest = destination.clone();
|
||||
let path = canonicalize(&path, &source_file)?;
|
||||
|
||||
let mut comps: Vec<_> = path
|
||||
.components()
|
||||
.map(|fragment| fragment.as_os_str())
|
||||
.rev()
|
||||
.take(1 + depth_level)
|
||||
.collect();
|
||||
let comps: Vec<_> = path
|
||||
.components()
|
||||
.map(|fragment| fragment.as_os_str())
|
||||
.rev()
|
||||
.take(1 + depth_level)
|
||||
.collect();
|
||||
|
||||
comps.reverse();
|
||||
for fragment in comps.into_iter().rev() {
|
||||
dest.push(fragment);
|
||||
}
|
||||
|
||||
for fragment in comps.iter() {
|
||||
new_dst.push(fragment);
|
||||
}
|
||||
Ok((PathBuf::from(&source_file), dest))
|
||||
})?;
|
||||
|
||||
Ok((PathBuf::from(&source_file), new_dst))
|
||||
};
|
||||
let dst_tag = &dst.tag;
|
||||
for (src, dst) in sources {
|
||||
if src.is_dir() && !dst.exists() {
|
||||
std::fs::create_dir_all(&dst).map_err(|e| {
|
||||
ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag)
|
||||
})?;
|
||||
}
|
||||
|
||||
let sources = sources.paths_applying_with(strategy)?;
|
||||
|
||||
let dst_tag = dst.tag;
|
||||
for (ref src, ref dst) in sources {
|
||||
if src.is_dir() && !dst.exists() {
|
||||
match std::fs::create_dir_all(dst) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
dst_tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
}
|
||||
|
||||
if src.is_file() {
|
||||
match std::fs::copy(src, dst) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
name_tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
}
|
||||
if src.is_file() {
|
||||
std::fs::copy(&src, &dst).map_err(|e| {
|
||||
ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if destination.exists() {
|
||||
if !sources.iter().all(|x| match x {
|
||||
Ok(f) => f.is_file(),
|
||||
Err(_) => false,
|
||||
}) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Copy aborted (directories found). Recursive copying in patterns not supported yet (try copying the directory directly)",
|
||||
"recursive copying in patterns not supported",
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
|
||||
for entry in sources {
|
||||
if let Ok(entry) = entry {
|
||||
let mut to = PathBuf::from(&destination);
|
||||
|
||||
match entry.file_name() {
|
||||
Some(name) => to.push(name),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Copy aborted. Not a valid path",
|
||||
"not a valid path",
|
||||
dst.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if entry.is_file() {
|
||||
match std::fs::copy(&entry, &to) {
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let destination_file_name = {
|
||||
match destination.file_name() {
|
||||
Some(name) => PathBuf::from(name),
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Copy aborted. Not a valid destination",
|
||||
"not a valid destination",
|
||||
dst.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Err(ShellError::labeled_error(
|
||||
format!("Copy aborted. (Does {:?} exist?)", destination_file_name),
|
||||
format!("copy aborted (does {:?} exist?)", destination_file_name),
|
||||
dst.tag,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
@ -550,7 +388,7 @@ impl Shell for FilesystemShell {
|
||||
name: Tag,
|
||||
path: &str,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let full_path = PathBuf::from(path);
|
||||
let path = Path::new(path);
|
||||
|
||||
if directories.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
@ -561,11 +399,7 @@ impl Shell for FilesystemShell {
|
||||
}
|
||||
|
||||
for dir in directories.iter() {
|
||||
let create_at = {
|
||||
let mut loc = full_path.clone();
|
||||
loc.push(&dir.item);
|
||||
loc
|
||||
};
|
||||
let create_at = path.join(&dir.item);
|
||||
|
||||
let dir_res = std::fs::create_dir_all(create_at);
|
||||
if let Err(reason) = dir_res {
|
||||
@ -588,11 +422,9 @@ impl Shell for FilesystemShell {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = name;
|
||||
|
||||
let mut source = PathBuf::from(path);
|
||||
let mut destination = PathBuf::from(path);
|
||||
|
||||
source.push(&src.item);
|
||||
destination.push(&dst.item);
|
||||
let path = Path::new(path);
|
||||
let source = path.join(&src.item);
|
||||
let mut destination = path.join(&dst.item);
|
||||
|
||||
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
|
||||
Ok(files) => files.collect(),
|
||||
@ -607,11 +439,12 @@ impl Shell for FilesystemShell {
|
||||
|
||||
if sources.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Invalid File or Pattern.",
|
||||
"Invalid File or Pattern",
|
||||
"Invalid file or pattern.",
|
||||
"invalid file or pattern",
|
||||
src.tag,
|
||||
));
|
||||
}
|
||||
|
||||
let destination_file_name = {
|
||||
match destination.file_name() {
|
||||
Some(name) => PathBuf::from(name),
|
||||
@ -978,7 +811,7 @@ impl Shell for FilesystemShell {
|
||||
RemoveArgs {
|
||||
rest: targets,
|
||||
recursive,
|
||||
trash,
|
||||
trash: _trash,
|
||||
}: RemoveArgs,
|
||||
name: Tag,
|
||||
path: &str,
|
||||
@ -993,18 +826,23 @@ impl Shell for FilesystemShell {
|
||||
));
|
||||
}
|
||||
|
||||
let path = Path::new(path);
|
||||
let mut all_targets: HashMap<PathBuf, Tag> = HashMap::new();
|
||||
for target in targets {
|
||||
if target.item.to_str() == Some(".") || target.item.to_str() == Some("..") {
|
||||
let all_dots = target
|
||||
.item
|
||||
.to_str()
|
||||
.map_or(false, |v| v.chars().all(|c| c == '.'));
|
||||
|
||||
if all_dots {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Remove aborted. \".\" or \"..\" may not be removed.",
|
||||
"\".\" or \"..\" may not be removed",
|
||||
"Cannot remove any parent directory",
|
||||
"cannot remove any parent directory",
|
||||
target.tag,
|
||||
));
|
||||
}
|
||||
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push(&target.item);
|
||||
let path = path.join(&target.item);
|
||||
match glob::glob(&path.to_string_lossy()) {
|
||||
Ok(files) => {
|
||||
for file in files {
|
||||
@ -1015,9 +853,8 @@ impl Shell for FilesystemShell {
|
||||
.or_insert_with(|| target.tag.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("Could not remove {:}", path.to_string_lossy());
|
||||
return Err(ShellError::labeled_error(
|
||||
msg,
|
||||
format!("Could not remove {:}", path.to_string_lossy()),
|
||||
e.to_string(),
|
||||
&target.tag,
|
||||
));
|
||||
@ -1027,7 +864,7 @@ impl Shell for FilesystemShell {
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
format!("Remove aborted. {:}", e.to_string()),
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
&name_tag,
|
||||
))
|
||||
@ -1036,91 +873,77 @@ impl Shell for FilesystemShell {
|
||||
}
|
||||
|
||||
if all_targets.is_empty() {
|
||||
Err(ShellError::labeled_error(
|
||||
"Remove aborted. No valid paths",
|
||||
return Err(ShellError::labeled_error(
|
||||
"No valid paths",
|
||||
"no valid paths",
|
||||
name_tag,
|
||||
))
|
||||
} else {
|
||||
let stream = async_stream! {
|
||||
for (f, tag) in all_targets.iter() {
|
||||
let is_empty = match f.read_dir() {
|
||||
Ok(mut p) => p.next().is_none(),
|
||||
Err(_) => false
|
||||
};
|
||||
));
|
||||
}
|
||||
|
||||
let valid_target =
|
||||
f.exists() && (!f.is_dir() || (is_empty || recursive.item));
|
||||
if valid_target {
|
||||
if trash.item {
|
||||
match SendToTrash::remove(f) {
|
||||
Err(e) => {
|
||||
let msg = format!(
|
||||
"Could not delete {:}",
|
||||
f.to_string_lossy()
|
||||
);
|
||||
let label = format!("{:?}", e);
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
label,
|
||||
tag,
|
||||
))
|
||||
},
|
||||
Ok(()) => {
|
||||
let val = format!("deleted {:}", f.to_string_lossy()).into();
|
||||
yield Ok(ReturnSuccess::Value(val))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let success = if f.is_dir() {
|
||||
std::fs::remove_dir_all(f)
|
||||
} else {
|
||||
let stream = async_stream! {
|
||||
for (f, tag) in all_targets.iter() {
|
||||
let is_empty = || match f.read_dir() {
|
||||
Ok(mut p) => p.next().is_none(),
|
||||
Err(_) => false
|
||||
};
|
||||
|
||||
if let Ok(metadata) = f.symlink_metadata() {
|
||||
if metadata.is_file() || metadata.file_type().is_symlink() || recursive.item || is_empty() {
|
||||
let result;
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
result = if _trash.item {
|
||||
trash::remove(f)
|
||||
.map_err(|e| f.to_string_lossy())
|
||||
} else if metadata.is_file() {
|
||||
std::fs::remove_file(f)
|
||||
.map_err(|e| f.to_string_lossy())
|
||||
} else {
|
||||
std::fs::remove_dir_all(f)
|
||||
.map_err(|e| f.to_string_lossy())
|
||||
};
|
||||
match success {
|
||||
Err(e) => {
|
||||
let msg = format!(
|
||||
"Could not delete {:}",
|
||||
f.to_string_lossy()
|
||||
);
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
e.to_string(),
|
||||
tag,
|
||||
))
|
||||
},
|
||||
Ok(()) => {
|
||||
let val = format!("deleted {:}", f.to_string_lossy()).into();
|
||||
yield Ok(ReturnSuccess::Value(
|
||||
val,
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "trash-support"))]
|
||||
{
|
||||
result = if metadata.is_file() {
|
||||
std::fs::remove_file(f)
|
||||
.map_err(|e| f.to_string_lossy())
|
||||
} else {
|
||||
std::fs::remove_dir_all(f)
|
||||
.map_err(|e| f.to_string_lossy())
|
||||
};
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
let msg = format!("Could not delete {:}", e);
|
||||
yield Err(ShellError::labeled_error(msg, e, tag))
|
||||
} else {
|
||||
let val = format!("deleted {:}", f.to_string_lossy()).into();
|
||||
yield Ok(ReturnSuccess::Value(val))
|
||||
}
|
||||
} else {
|
||||
if f.is_dir() {
|
||||
let msg = format!(
|
||||
"Cannot remove {:}. try --recursive",
|
||||
f.to_string_lossy()
|
||||
);
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
"cannot remove non-empty directory",
|
||||
tag,
|
||||
))
|
||||
} else {
|
||||
let msg = format!("Invalid file: {:}", f.to_string_lossy());
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
"invalid file",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
let msg = format!(
|
||||
"Cannot remove {:}. try --recursive",
|
||||
f.to_string_lossy()
|
||||
);
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
"cannot remove non-empty directory",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let msg = format!("no such file or directory: {:}", f.to_string_lossy());
|
||||
yield Err(ShellError::labeled_error(
|
||||
msg,
|
||||
"no such file or directory",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
|
||||
fn path(&self) -> String {
|
||||
@ -1174,13 +997,7 @@ impl Shell for FilesystemShell {
|
||||
self.completer.complete(line, pos, ctx)
|
||||
}
|
||||
|
||||
fn hint(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
_expand_context: ExpandContext,
|
||||
) -> Option<String> {
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
self.hinter.hint(line, pos, ctx)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use crate::data::command_dict;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ExpandContext;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
@ -249,13 +248,7 @@ impl Shell for HelpShell {
|
||||
Ok((replace_pos, completions))
|
||||
}
|
||||
|
||||
fn hint(
|
||||
&self,
|
||||
_line: &str,
|
||||
_pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
_context: ExpandContext,
|
||||
) -> Option<String> {
|
||||
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::context::Context;
|
||||
use ansi_term::{Color, Style};
|
||||
use log::log_enabled;
|
||||
use nu_parser::{FlatShape, PipelineShape, ShapeResult, Token, TokensIterator};
|
||||
use nu_protocol::{errln, outln};
|
||||
use nu_source::{nom_input, HasSpan, Tag, Tagged, Text};
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::FlatShape;
|
||||
use nu_source::{Span, Spanned, Tag, Tagged};
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::Highlighter;
|
||||
@ -38,10 +37,7 @@ impl Completer for Helper {
|
||||
|
||||
impl Hinter for Helper {
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
let text = Text::from(line);
|
||||
self.context
|
||||
.shell_manager
|
||||
.hint(line, pos, ctx, self.context.expand_context(&text))
|
||||
self.context.shell_manager.hint(line, pos, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,49 +61,19 @@ impl Highlighter for Helper {
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let tokens = nu_parser::pipeline(nom_input(line));
|
||||
let lite_block = nu_parser::lite_parse(line, 0);
|
||||
|
||||
match tokens {
|
||||
match lite_block {
|
||||
Err(_) => Cow::Borrowed(line),
|
||||
Ok((_rest, v)) => {
|
||||
let pipeline = match v.as_pipeline() {
|
||||
Err(_) => return Cow::Borrowed(line),
|
||||
Ok(v) => v,
|
||||
};
|
||||
Ok(lb) => {
|
||||
let classified =
|
||||
nu_parser::classify_block(&lb, &self.context.registry().clone_box());
|
||||
|
||||
let text = Text::from(line);
|
||||
let expand_context = self.context.expand_context(&text);
|
||||
|
||||
let tokens = vec![Token::Pipeline(pipeline).into_spanned(v.span())];
|
||||
let mut tokens = TokensIterator::new(&tokens[..], expand_context, v.span());
|
||||
|
||||
let shapes = {
|
||||
// We just constructed a token list that only contains a pipeline, so it can't fail
|
||||
let result = tokens.expand_infallible(PipelineShape);
|
||||
|
||||
if let Some(failure) = result.failed {
|
||||
errln!(
|
||||
"BUG: PipelineShape didn't find a pipeline :: {:#?}",
|
||||
failure
|
||||
);
|
||||
}
|
||||
|
||||
tokens.finish_tracer();
|
||||
|
||||
tokens.state().shapes()
|
||||
};
|
||||
|
||||
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
|
||||
outln!("");
|
||||
let _ =
|
||||
ptree::print_tree(&tokens.expand_tracer().clone().print(Text::from(line)));
|
||||
outln!("");
|
||||
}
|
||||
|
||||
let mut painter = Painter::new();
|
||||
let shapes = nu_parser::shapes(&classified.block);
|
||||
let mut painter = Painter::new(line);
|
||||
|
||||
for shape in shapes {
|
||||
painter.paint_shape(&shape, line);
|
||||
painter.paint_shape(&shape);
|
||||
}
|
||||
|
||||
Cow::Owned(painter.into_string())
|
||||
@ -133,73 +99,94 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
}
|
||||
|
||||
struct Painter {
|
||||
current: Style,
|
||||
buffer: String,
|
||||
original: Vec<u8>,
|
||||
styles: Vec<Style>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
fn new() -> Painter {
|
||||
fn new(original: &str) -> Painter {
|
||||
let bytes: Vec<u8> = original.bytes().collect();
|
||||
let bytes_count = bytes.len();
|
||||
Painter {
|
||||
current: Style::default(),
|
||||
buffer: String::new(),
|
||||
original: bytes,
|
||||
styles: vec![Color::White.normal(); bytes_count],
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_shape(&mut self, shape: &Spanned<FlatShape>) {
|
||||
let style = match &shape.item {
|
||||
FlatShape::OpenDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::CloseDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
|
||||
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
|
||||
FlatShape::Type => Color::Blue.bold(),
|
||||
FlatShape::Operator => Color::Yellow.normal(),
|
||||
FlatShape::DotDot => Color::Yellow.bold(),
|
||||
FlatShape::Dot => Style::new().fg(Color::White),
|
||||
FlatShape::InternalCommand => Color::Cyan.bold(),
|
||||
FlatShape::ExternalCommand => Color::Cyan.normal(),
|
||||
FlatShape::ExternalWord => Color::Green.bold(),
|
||||
FlatShape::BareMember => Color::Yellow.bold(),
|
||||
FlatShape::StringMember => Color::Yellow.bold(),
|
||||
FlatShape::String => Color::Green.normal(),
|
||||
FlatShape::Path => Color::Cyan.normal(),
|
||||
FlatShape::GlobPattern => Color::Cyan.bold(),
|
||||
FlatShape::Word => Color::Green.normal(),
|
||||
FlatShape::Pipe => Color::Purple.bold(),
|
||||
FlatShape::Flag => Color::Blue.bold(),
|
||||
FlatShape::ShorthandFlag => Color::Blue.bold(),
|
||||
FlatShape::Int => Color::Purple.bold(),
|
||||
FlatShape::Decimal => Color::Purple.bold(),
|
||||
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
|
||||
FlatShape::Comment => Color::Green.bold(),
|
||||
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
|
||||
FlatShape::Size { number, unit } => {
|
||||
self.paint(Color::Purple.bold(), number);
|
||||
self.paint(Color::Cyan.bold(), unit);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.paint(style, &shape.span);
|
||||
}
|
||||
|
||||
fn paint(&mut self, style: Style, span: &Span) {
|
||||
for pos in span.start()..span.end() {
|
||||
self.styles[pos] = style;
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self) -> String {
|
||||
self.buffer
|
||||
}
|
||||
let mut idx_start = 0;
|
||||
let mut idx_end = 1;
|
||||
|
||||
fn paint_shape(&mut self, shape: &ShapeResult, line: &str) {
|
||||
let style = match &shape {
|
||||
ShapeResult::Success(shape) => match shape.item {
|
||||
FlatShape::OpenDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::CloseDelimiter(_) => Color::White.normal(),
|
||||
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
|
||||
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
|
||||
FlatShape::Type => Color::Blue.bold(),
|
||||
FlatShape::CompareOperator => Color::Yellow.normal(),
|
||||
FlatShape::DotDot => Color::Yellow.bold(),
|
||||
FlatShape::Dot => Style::new().fg(Color::White),
|
||||
FlatShape::InternalCommand => Color::Cyan.bold(),
|
||||
FlatShape::ExternalCommand => Color::Cyan.normal(),
|
||||
FlatShape::ExternalWord => Color::Green.bold(),
|
||||
FlatShape::BareMember => Color::Yellow.bold(),
|
||||
FlatShape::StringMember => Color::Yellow.bold(),
|
||||
FlatShape::String => Color::Green.normal(),
|
||||
FlatShape::Path => Color::Cyan.normal(),
|
||||
FlatShape::GlobPattern => Color::Cyan.bold(),
|
||||
FlatShape::Word => Color::Green.normal(),
|
||||
FlatShape::Pipe => Color::Purple.bold(),
|
||||
FlatShape::Flag => Color::Blue.bold(),
|
||||
FlatShape::ShorthandFlag => Color::Blue.bold(),
|
||||
FlatShape::Int => Color::Purple.bold(),
|
||||
FlatShape::Decimal => Color::Purple.bold(),
|
||||
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
|
||||
FlatShape::Comment => Color::Green.bold(),
|
||||
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
|
||||
FlatShape::Size { number, unit } => {
|
||||
let number = number.slice(line);
|
||||
let unit = unit.slice(line);
|
||||
if self.original.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let mut builder = String::new();
|
||||
|
||||
self.paint(Color::Purple.bold(), number);
|
||||
self.paint(Color::Cyan.bold(), unit);
|
||||
return;
|
||||
let mut current_style = self.styles[0];
|
||||
|
||||
while idx_end < self.styles.len() {
|
||||
if self.styles[idx_end] != current_style {
|
||||
// Emit, as we changed styles
|
||||
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
|
||||
|
||||
builder.push_str(&format!("{}", current_style.paint(intermediate)));
|
||||
|
||||
current_style = self.styles[idx_end];
|
||||
idx_start = idx_end;
|
||||
idx_end += 1;
|
||||
} else {
|
||||
idx_end += 1;
|
||||
}
|
||||
},
|
||||
ShapeResult::Fallback { shape, .. } => match shape.item {
|
||||
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
|
||||
_ => Style::new().fg(Color::White).on(Color::Red),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
self.paint(style, shape.span().slice(line));
|
||||
}
|
||||
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
|
||||
builder.push_str(&format!("{}", current_style.paint(intermediate)));
|
||||
|
||||
fn paint(&mut self, style: Style, body: &str) {
|
||||
let infix = self.current.infix(style);
|
||||
self.current = style;
|
||||
self.buffer
|
||||
.push_str(&format!("{}{}", infix, style.paint(body)));
|
||||
builder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user