mirror of
https://github.com/nushell/nushell.git
synced 2025-07-07 10:01:26 +02:00
Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
ebba89ea31 | |||
8388afc9d9 | |||
b133724b38 | |||
09429d08aa | |||
9b577b8679 | |||
7a595827f1 | |||
332e12ded0 | |||
a508e15efe | |||
a5b6bb6209 | |||
1882a32b83 | |||
798766b4b5 | |||
193c4cc6d5 | |||
422b6ca871 | |||
2b13ac3856 | |||
4c10351579 | |||
dd27aaef1b | |||
6eb4a0e87b | |||
15f3a545f0 | |||
365f76ad19 | |||
df2845a9b4 | |||
8453261211 | |||
1dc8f3300e | |||
10d4edc7af | |||
50cbf91bc5 | |||
d05f9b3b1e | |||
f5fad393d0 | |||
d19a5f4c2f | |||
04451af776 | |||
232aca76a4 | |||
0178b53289 | |||
e05e6b42fe | |||
dd79afb503 | |||
599bb9797d | |||
c355585112 | |||
45f32c9541 | |||
7528094e12 | |||
dcfa135ab9 | |||
e9bb4f25eb | |||
0f7a9bbd31 | |||
73e65df5f6 | |||
a63a5adafa | |||
2eb4f8d28a | |||
d9ae66791a | |||
2c5939dc7d | |||
3150e70fc7 | |||
c9ffd6afc0 | |||
986b427038 | |||
c973850571 | |||
5a725f9651 | |||
79cc725aff | |||
e2cbc4e853 | |||
b5a27f0ccb | |||
bdb12f4bff | |||
c9c29f9e4c | |||
32951f1161 | |||
56f85b3108 | |||
16f85f32a2 | |||
2ae2f2ea9d | |||
4696c9069b | |||
1ffbb66e64 | |||
8dc7b8a7cd | |||
666e6a7b57 |
@ -71,7 +71,7 @@ steps:
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
|
653
Cargo.lock
generated
653
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
66
Cargo.toml
66
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,48 +18,48 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
|
||||
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_plugin_binaryview = {version = "0.19.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.19.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.19.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.19.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.19.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
|
||||
crossterm = {version = "0.17.5", optional = true}
|
||||
crossterm = {version = "0.17", optional = true}
|
||||
semver = {version = "0.10.0", optional = true}
|
||||
url = {version = "2.1.1", optional = true}
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
clap = "2.33.3"
|
||||
ctrlc = "3.1.6"
|
||||
dunce = "1.0.1"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
quick-xml = "0.18.1"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.19.0", path = "./crates/nu-test-support"}
|
||||
nu-test-support = {version = "0.20.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
@ -75,9 +75,11 @@ default = [
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"rich-benchmark",
|
||||
]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
||||
stable = ["default"]
|
||||
@ -105,6 +107,8 @@ ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
|
@ -4,103 +4,102 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = {version = "0.19.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.19.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.19.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.19.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.19.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
|
||||
nu-data = {version = "0.20.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.20.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = {version = "2", package = "app_dirs2"}
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.36"
|
||||
async-trait = "0.1.40"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
chrono = {version = "0.4.11", features = ["serde"]}
|
||||
clap = "2.33.1"
|
||||
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "0.5.6"
|
||||
calamine = "0.16.1"
|
||||
chrono = {version = "0.4.15", features = ["serde"]}
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1"
|
||||
ctrlc = {version = "3.1.4", optional = true}
|
||||
csv = "1.1.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "2.0.2", optional = true}
|
||||
dirs = {version = "2.0.2", optional = true}
|
||||
directories = {version = "3.0.1", optional = true}
|
||||
dirs = {version = "3.0.1", optional = true}
|
||||
dtparse = "1.1.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
filesize = "0.2.0"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
fs_extra = "1.2.0"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
futures-util = "0.3.5"
|
||||
futures_codec = "0.4"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = {version = "0.13.6", default_features = false, optional = true}
|
||||
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
heim = {version = "0.1.0-beta.3", optional = true}
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ical = "0.6.0"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.4.0", features = ["serde-1"]}
|
||||
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
natural = "0.5.0"
|
||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.11"
|
||||
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.12"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
ptree = {version = "0.2", optional = true}
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = {version = "0.3.0", optional = true}
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
rand = "0.7.3"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.13.0"
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
rustyline = {version = "6.3.0", optional = true}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0.57"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.5.2", optional = true}
|
||||
term = {version = "0.6.1", optional = true}
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.1"
|
||||
unicode-segmentation = "1.6.0"
|
||||
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.2", optional = true}
|
||||
zip = {version = "0.5.6", optional = true}
|
||||
zip = {version = "0.5.7", optional = true}
|
||||
|
||||
clipboard = {version = "0.5", optional = true}
|
||||
encoding_rs = "0.8.23"
|
||||
quick-xml = "0.18.1"
|
||||
rayon = "1.3.1"
|
||||
trash = {version = "1.0.1", optional = true}
|
||||
url = {version = "2.1.1"}
|
||||
Inflector = "0.11"
|
||||
clipboard = {version = "0.5.0", optional = true}
|
||||
encoding_rs = "0.8.24"
|
||||
quick-xml = "0.18.1"
|
||||
rayon = "1.4.0"
|
||||
trash = {version = "1.1.1", optional = true}
|
||||
url = "2.1.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
@ -112,17 +111,18 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.23.1"
|
||||
version = "0.24.0"
|
||||
|
||||
[build-dependencies]
|
||||
git2 = {version = "0.13", optional = true}
|
||||
|
||||
git2 = {version = "0.13.11", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["clipboard"]
|
||||
rich-benchmark = ["heim"]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
|
@ -1,38 +1,30 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::context::Context;
|
||||
use crate::git::current_branch;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan() {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
@ -64,21 +56,16 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn create_default_context(
|
||||
syncer: &mut crate::EnvironmentSyncer,
|
||||
interactive: bool,
|
||||
) -> Result<Context, Box<dyn Error>> {
|
||||
syncer.load_environment();
|
||||
|
||||
let mut context = Context::basic()?;
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||
let mut context = EvaluationContext::basic()?;
|
||||
|
||||
{
|
||||
use crate::commands::*;
|
||||
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(NuPlugin),
|
||||
// System/file operations
|
||||
whole_stream_command(Exec),
|
||||
whole_stream_command(Pwd),
|
||||
whole_stream_command(Ls),
|
||||
whole_stream_command(Du),
|
||||
@ -136,7 +123,6 @@ pub fn create_default_context(
|
||||
whole_stream_command(SplitRow),
|
||||
whole_stream_command(SplitChars),
|
||||
whole_stream_command(Lines),
|
||||
whole_stream_command(Trim),
|
||||
whole_stream_command(Echo),
|
||||
whole_stream_command(Parse),
|
||||
whole_stream_command(Str),
|
||||
@ -204,6 +190,8 @@ pub fn create_default_context(
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(EachGroup),
|
||||
whole_stream_command(EachWindow),
|
||||
whole_stream_command(IsEmpty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Move),
|
||||
@ -265,6 +253,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(RandomDice),
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
whole_stream_command(RandomUUID),
|
||||
whole_stream_command(RandomInteger),
|
||||
// Path
|
||||
whole_stream_command(PathBasename),
|
||||
whole_stream_command(PathCommand),
|
||||
@ -282,7 +271,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(UrlQuery),
|
||||
]);
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
{
|
||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||
}
|
||||
@ -295,284 +284,76 @@ 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 = create_default_context(&mut syncer, false)?;
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(false)?;
|
||||
let config = syncer.get_config();
|
||||
|
||||
let _ = register_plugins(&mut context);
|
||||
context.configure(&config, |_, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
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);
|
||||
}
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = register_plugins(ctx);
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
|
||||
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;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
let config = match config::config(Tag::unknown()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Config could not be loaded.");
|
||||
if let ShellError {
|
||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
||||
..
|
||||
} = e
|
||||
{
|
||||
eprintln!("{}", diagnostic.message);
|
||||
}
|
||||
IndexMap::new()
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(config) = config::config(Tag::unknown()) {
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => {
|
||||
rustyline::config::EditMode::Emacs
|
||||
}
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => {
|
||||
rustyline::config::BellStyle::None
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => {
|
||||
rustyline::ColorMode::Disabled
|
||||
}
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(rl, config)
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
pub async fn cli(
|
||||
mut syncer: EnvironmentSyncer,
|
||||
mut context: Context,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let configuration = nu_data::config::NuConfig::new();
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let configuration = syncer.get_config();
|
||||
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
|
||||
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||
rl.set_helper(helper);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
|
||||
let _ = register_plugins(&mut context);
|
||||
|
||||
let (mut rl, config) = create_rustyline_configuration();
|
||||
|
||||
// we are ok if history does not exist
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let skip_welcome_message = config
|
||||
.get("skip_welcome_message")
|
||||
let skip_welcome_message = configuration
|
||||
.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
if !skip_welcome_message {
|
||||
@ -587,44 +368,8 @@ pub async fn cli(
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
}
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_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);
|
||||
@ -633,12 +378,8 @@ pub async fn cli(
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
let hinter = init_hinter(&config);
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = config.get("prompt") {
|
||||
if let Some(prompt) = configuration.var("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
@ -695,6 +436,7 @@ pub async fn cli(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
use crate::git::current_branch;
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
@ -722,14 +464,28 @@ pub async fn cli(
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
let line = process_line(readline, &mut context, false, true).await;
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(s) => process_line(&s, &mut context, false, true).await,
|
||||
x => x,
|
||||
};
|
||||
|
||||
// Check the config to see if we need to update the path
|
||||
// TODO: make sure config is cached so we don't path this load every call
|
||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
if syncer.did_config_change() {
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
}
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
});
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
@ -752,7 +508,7 @@ pub async fn cli(
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = config::config(Tag::unknown())?
|
||||
.get("ctrlc_exit")
|
||||
.map(|s| s.value.expect_string() == "true")
|
||||
.map(|s| s.value.is_true())
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
@ -769,6 +525,13 @@ pub async fn cli(
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::CtrlD => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
@ -782,15 +545,283 @@ pub async fn cli(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
||||
// Show hints unless explicitly disabled in config
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_startup_commands(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(commands) = config.var("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, context, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"expected a table of pipeline strings as startup commands",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut EvaluationContext,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(&pipeline, context, redirect_stdin, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn nu_line_editor_helper(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
@ -806,11 +837,12 @@ fn chomp_newline(s: &str) -> &str {
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
CtrlC,
|
||||
Break,
|
||||
CtrlC,
|
||||
CtrlD,
|
||||
}
|
||||
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, ShellError> {
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||
let line = if line.ends_with('\n') {
|
||||
&line[..line.len() - 1]
|
||||
} else {
|
||||
@ -842,15 +874,14 @@ pub async fn parse_and_eval(line: &str, ctx: &mut Context) -> Result<String, She
|
||||
|
||||
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||
pub async fn process_line(
|
||||
readline: Result<String, ReadlineError>,
|
||||
ctx: &mut Context,
|
||||
line: &str,
|
||||
ctx: &mut EvaluationContext,
|
||||
redirect_stdin: bool,
|
||||
cli_mode: bool,
|
||||
) -> LineResult {
|
||||
match &readline {
|
||||
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
|
||||
|
||||
Ok(line) => {
|
||||
if line.trim() == "" {
|
||||
LineResult::Success(line.to_string())
|
||||
} else {
|
||||
let line = chomp_newline(line);
|
||||
ctx.raw_input = line.to_string();
|
||||
|
||||
@ -1038,13 +1069,6 @@ pub async fn process_line(
|
||||
Err(err) => LineResult::Error(line.to_string(), err),
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::Break,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
@ -1069,7 +1093,7 @@ 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 context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||
}
|
||||
true
|
||||
|
64
crates/nu-cli/src/command_registry.rs
Normal file
64
crates/nu-cli/src/command_registry.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::commands::Command;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::Signature;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CommandRegistry {
|
||||
registry: Arc<Mutex<IndexMap<String, Command>>>,
|
||||
}
|
||||
|
||||
impl SignatureRegistry for CommandRegistry {
|
||||
fn has(&self, name: &str) -> bool {
|
||||
let registry = self.registry.lock();
|
||||
registry.contains_key(name)
|
||||
}
|
||||
fn get(&self, name: &str) -> Option<Signature> {
|
||||
let registry = self.registry.lock();
|
||||
registry.get(name).map(|command| command.signature())
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandRegistry {
|
||||
pub fn new() -> CommandRegistry {
|
||||
CommandRegistry {
|
||||
registry: Arc::new(Mutex::new(IndexMap::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandRegistry {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
let registry = self.registry.lock();
|
||||
|
||||
registry.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
||||
self.get_command(name).ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(format!("Could not load command: {}", name))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
let registry = self.registry.lock();
|
||||
|
||||
registry.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, name: impl Into<String>, command: Command) {
|
||||
let mut registry = self.registry.lock();
|
||||
registry.insert(name.into(), command);
|
||||
}
|
||||
|
||||
pub fn names(&self) -> Vec<String> {
|
||||
let registry = self.registry.lock();
|
||||
registry.keys().cloned().collect()
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod compact;
|
||||
@ -36,6 +36,7 @@ pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod format;
|
||||
@ -75,6 +76,7 @@ pub(crate) mod mkdir;
|
||||
pub(crate) mod move_;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod nu;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod path;
|
||||
@ -114,7 +116,6 @@ pub(crate) mod to_tsv;
|
||||
pub(crate) mod to_url;
|
||||
pub(crate) mod to_xml;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod trim;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod url_;
|
||||
@ -154,9 +155,12 @@ pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use each::EachGroup;
|
||||
pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
@ -165,6 +169,7 @@ pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use enter::Enter;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
@ -218,7 +223,7 @@ pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice};
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
||||
pub(crate) use range::Range;
|
||||
pub(crate) use reduce::Reduce;
|
||||
pub(crate) use reject::Reject;
|
||||
@ -255,7 +260,6 @@ pub(crate) use to_url::ToURL;
|
||||
pub(crate) use to_xml::ToXML;
|
||||
pub(crate) use to_yaml::ToYAML;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use trim::Trim;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -37,7 +37,7 @@ pub trait ConfigExtensions: Debug + Send {
|
||||
}
|
||||
|
||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||
let vars = config.vars.lock();
|
||||
let vars = &config.vars;
|
||||
|
||||
if let Some(mode) = vars.get("pivot_mode") {
|
||||
let mode = match mode.as_string() {
|
||||
|
@ -1,18 +1,22 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
use heim::cpu::time;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
|
||||
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use chrono::prelude::*;
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct Benchmark;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BenchmarkArgs {
|
||||
block: Block,
|
||||
passthrough: Option<Block>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -22,15 +26,22 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("benchmark").required(
|
||||
Signature::build("benchmark")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and benchmark",
|
||||
)
|
||||
.named(
|
||||
"passthrough",
|
||||
SyntaxShape::Block,
|
||||
"Display the benchmark results and pass through the block's output",
|
||||
Some('p'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
|
||||
"Runs a block and returns the time it took to execute it"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -40,6 +51,21 @@ impl WholeStreamCommand for Benchmark {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Benchmarks a command within a block",
|
||||
example: "benchmark { sleep 500ms }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Benchmarks a command within a block and passes its output through",
|
||||
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
|
||||
result: Some(vec![UntaggedValue::int(45).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(
|
||||
@ -48,11 +74,15 @@ async fn benchmark(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let tag = raw_args.call_info.args.span;
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block }, input) = raw_args.process(®istry).await?;
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
let start_time: chrono::DateTime<_> = Utc::now();
|
||||
let start_time = Instant::now();
|
||||
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let start = time().await;
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
@ -63,16 +93,110 @@ async fn benchmark(
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
let output = result?.into_vec().await;
|
||||
|
||||
let _ = result?.drain_vec().await;
|
||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let end = time().await;
|
||||
|
||||
let end_time = Instant::now();
|
||||
context.clear_errors();
|
||||
|
||||
let output = Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
||||
tag: Tag::from(block.span),
|
||||
}));
|
||||
// return basic runtime
|
||||
#[cfg(not(feature = "rich-benchmark"))]
|
||||
{
|
||||
let mut indexmap = IndexMap::with_capacity(1);
|
||||
|
||||
Ok(OutputStream::from(vec![output]))
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
}
|
||||
// return advanced stats
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
if let (Ok(start), Ok(end)) = (start, end) {
|
||||
let mut indexmap = IndexMap::with_capacity(4);
|
||||
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
|
||||
let user_time = into_big_int(end.user() - start.user());
|
||||
indexmap.insert("user time".to_string(), user_time);
|
||||
|
||||
let system_time = into_big_int(end.system() - start.system());
|
||||
indexmap.insert("system time".to_string(), system_time);
|
||||
|
||||
let idle_time = into_big_int(end.idle() - start.idle());
|
||||
indexmap.insert("idle time".to_string(), idle_time);
|
||||
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
} else {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not retreive CPU time",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark_output<T, Output>(
|
||||
indexmap: IndexMap<String, BigInt>,
|
||||
block_output: Output,
|
||||
passthrough: Option<Block>,
|
||||
tag: T,
|
||||
context: &mut EvaluationContext,
|
||||
scope: &Scope,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
T: Into<Tag> + Copy,
|
||||
Output: Into<OutputStream>,
|
||||
{
|
||||
let value = UntaggedValue::Row(Dictionary::from(
|
||||
indexmap
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
|
||||
.collect::<IndexMap<String, Value>>(),
|
||||
))
|
||||
.into_value(tag);
|
||||
|
||||
if let Some(time_block) = passthrough {
|
||||
let benchmark_output = InputStream::one(value);
|
||||
|
||||
// add autoview for an empty block
|
||||
let time_block = add_implicit_autoview(time_block);
|
||||
|
||||
let _ = run_block(
|
||||
&time_block,
|
||||
context,
|
||||
benchmark_output,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?;
|
||||
context.clear_errors();
|
||||
|
||||
Ok(block_output.into())
|
||||
} else {
|
||||
let benchmark_output = OutputStream::one(value);
|
||||
Ok(benchmark_output)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_implicit_autoview(mut block: Block) -> Block {
|
||||
if block.block.is_empty() {
|
||||
block.push({
|
||||
let mut commands = Commands::new(block.span);
|
||||
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
|
||||
"autoview".to_string(),
|
||||
block.span,
|
||||
block.span,
|
||||
)));
|
||||
commands
|
||||
});
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
|
||||
time.try_into()
|
||||
.unwrap_or_else(|_| Duration::new(0, 0))
|
||||
.as_nanos()
|
||||
.into()
|
||||
}
|
||||
|
@ -94,6 +94,35 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||
|
||||
// Weather symbols
|
||||
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
|
||||
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
|
||||
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
|
||||
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
|
||||
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
|
||||
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
|
||||
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
|
||||
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
|
||||
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
|
||||
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
// Ansi Erase Sequences
|
||||
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
|
||||
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
|
||||
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
|
||||
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
|
||||
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
@ -11,7 +11,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
ctx: &mut EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
@ -64,7 +64,7 @@ pub(crate) async fn run_block(
|
||||
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
ctx: &mut EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
|
@ -10,7 +10,7 @@ use nu_protocol::Value;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
|
@ -19,7 +19,7 @@ use nu_source::Tag;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
external_redirection: ExternalRedirection,
|
||||
@ -39,7 +39,7 @@ pub(crate) async fn run_external_command(
|
||||
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
external_redirection: ExternalRedirection,
|
||||
@ -543,7 +543,7 @@ mod tests {
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, Context, InputStream};
|
||||
use super::{run_external_command, EvaluationContext, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use futures::executor::block_on;
|
||||
@ -573,7 +573,8 @@ mod tests {
|
||||
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.");
|
||||
let mut ctx =
|
||||
EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(run_external_command(
|
||||
cmd,
|
||||
@ -591,7 +592,7 @@ mod tests {
|
||||
// async fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
|
||||
// let mut ctx = EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// .await?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
@ -9,7 +9,7 @@ use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue,
|
||||
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut Context,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
@ -200,6 +200,28 @@ pub(crate) async fn run_internal_command(
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddPlugins(path) => {
|
||||
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||
Ok(plugins) => {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| {
|
||||
!context.is_command_registered(p.name())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
Err(reason) => {
|
||||
context.error(reason.clone());
|
||||
InputStream::one(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clears the terminal"
|
||||
"Clears the terminal"
|
||||
}
|
||||
|
||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::help::get_help;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
@ -303,6 +303,11 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
false
|
||||
}
|
||||
|
||||
// Commands that are not meant to be run by users
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
@ -367,6 +372,10 @@ impl Command {
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn is_internal(&self) -> bool {
|
||||
self.0.is_internal()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Work with dates."
|
||||
"Apply date function"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -65,7 +65,7 @@ async fn do_(
|
||||
let registry = registry.clone();
|
||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::stream::once;
|
||||
@ -84,7 +84,7 @@ pub async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<Context>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input_clone = input.clone();
|
||||
@ -120,7 +120,7 @@ async fn each(
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
|
133
crates/nu-cli/src/commands/each/group.rs
Normal file
133
crates/nu-cli/src/commands/each/group.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use crate::commands::each::process_row;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct EachGroup;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachGroupArgs {
|
||||
group_size: Tagged<usize>,
|
||||
block: Block,
|
||||
//numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachGroup {
|
||||
fn name(&self) -> &str {
|
||||
"each group"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each group")
|
||||
.required("group_size", SyntaxShape::Int, "the size of each group")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each group",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block on groups of `group_size` rows of a table at a time."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Echo the sum of each pair",
|
||||
example: "echo [1 2 3 4] | each group 2 { echo $it | math sum }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
|
||||
Ok(input
|
||||
.chunks(each_args.group_size.item)
|
||||
.then(move |input| {
|
||||
run_block_on_vec(
|
||||
input,
|
||||
block.clone(),
|
||||
scope.clone(),
|
||||
head.clone(),
|
||||
context.clone(),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
let value = Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag: Tag::unknown(),
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, value).await {
|
||||
Ok(s) => {
|
||||
// We need to handle this differently depending on whether process_row
|
||||
// returned just 1 value or if it returned multiple as a stream.
|
||||
let vec = s.collect::<Vec<_>>().await;
|
||||
|
||||
// If it returned just one value, just take that value
|
||||
if vec.len() == 1 {
|
||||
return OutputStream::one(vec.into_iter().next().expect(
|
||||
"This should be impossible, we just checked that vec.len() == 1.",
|
||||
));
|
||||
}
|
||||
|
||||
// If it returned multiple values, we need to put them into a table and
|
||||
// return that.
|
||||
let result = vec.into_iter().collect::<Result<Vec<ReturnSuccess>, _>>();
|
||||
let result_table = match result {
|
||||
Ok(t) => t,
|
||||
Err(e) => return OutputStream::one(Err(e)),
|
||||
};
|
||||
|
||||
let table = result_table
|
||||
.into_iter()
|
||||
.filter_map(|x| x.raw_value())
|
||||
.collect();
|
||||
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(UntaggedValue::Table(table).into())))
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachGroup;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachGroup {})
|
||||
}
|
||||
}
|
9
crates/nu-cli/src/commands/each/mod.rs
Normal file
9
crates/nu-cli/src/commands/each/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub mod command;
|
||||
pub mod group;
|
||||
pub mod window;
|
||||
|
||||
pub(crate) use command::make_indexed_item;
|
||||
pub use command::process_row;
|
||||
pub use command::Each;
|
||||
pub use group::EachGroup;
|
||||
pub use window::EachWindow;
|
113
crates/nu-cli/src/commands/each/window.rs
Normal file
113
crates/nu-cli/src/commands/each/window.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::commands::each::group::run_block_on_vec;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
//use itertools::Itertools;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Primitive, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct EachWindow;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachWindowArgs {
|
||||
window_size: Tagged<usize>,
|
||||
block: Block,
|
||||
stride: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachWindow {
|
||||
fn name(&self) -> &str {
|
||||
"each window"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each window")
|
||||
.required("window_size", SyntaxShape::Int, "the size of each window")
|
||||
.named(
|
||||
"stride",
|
||||
SyntaxShape::Int,
|
||||
"the number of rows to slide over between windows",
|
||||
Some('s'),
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each group",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block on sliding windows of `window_size` rows of a table at a time."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Echo the sum of each window",
|
||||
example: "echo [1 2 3 4] | each window 2 { echo $it | math sum }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
|
||||
let mut window: Vec<_> = input
|
||||
.by_ref()
|
||||
.take(*each_args.window_size - 1)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
// `window` must start with dummy values, which will be removed on the first iteration
|
||||
let stride = each_args.stride.map(|x| *x).unwrap_or(1);
|
||||
window.insert(0, UntaggedValue::Primitive(Primitive::Nothing).into());
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.then(move |(i, input)| {
|
||||
// This would probably be more efficient if `last` was a VecDeque
|
||||
// But we can't have that because it needs to be put into a Table
|
||||
window.remove(0);
|
||||
window.push(input);
|
||||
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
let local_window = window.clone();
|
||||
|
||||
async move {
|
||||
if i % stride == 0 {
|
||||
Some(run_block_on_vec(local_window, block, scope, head, context).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|x| async { x })
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachWindow;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachWindow {})
|
||||
}
|
||||
}
|
@ -81,17 +81,20 @@ struct RangeIterator {
|
||||
end: Primitive,
|
||||
tag: Tag,
|
||||
is_end_inclusive: bool,
|
||||
is_done: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||
let start = match range.from.0.item {
|
||||
Primitive::Nothing => Primitive::Int(0.into()),
|
||||
x => x,
|
||||
};
|
||||
|
||||
RangeIterator {
|
||||
curr: range.from.0.item,
|
||||
curr: start,
|
||||
end: range.to.0.item,
|
||||
tag,
|
||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||
is_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,14 +102,40 @@ impl RangeIterator {
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Result<ReturnSuccess, ShellError>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr != self.end {
|
||||
let ordering = if self.end == Primitive::Nothing {
|
||||
Ordering::Less
|
||||
} else {
|
||||
let result =
|
||||
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot create range",
|
||||
"unsupported range",
|
||||
self.tag.span,
|
||||
)
|
||||
});
|
||||
|
||||
if let Err(result) = result {
|
||||
return Some(Err(result));
|
||||
}
|
||||
|
||||
let result = result
|
||||
.expect("Internal error: the error case was already protected, but that failed");
|
||||
|
||||
result.compare()
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
if (ordering == Ordering::Less) || (self.is_end_inclusive && ordering == Ordering::Equal) {
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
|
||||
self.curr = match nu_data::value::compute_values(
|
||||
let next_value = nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
) {
|
||||
);
|
||||
|
||||
self.curr = match next_value {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
} else if self.is_end_inclusive && !self.is_done {
|
||||
self.is_done = true;
|
||||
Some(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
|
||||
))
|
||||
} else {
|
||||
// TODO: add inclusive/exclusive ranges
|
||||
None
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
87
crates/nu-cli/src/commands/exec.rs
Normal file
87
crates/nu-cli/src/commands/exec.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Exec;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ExecArgs {
|
||||
pub command: Tagged<PathBuf>,
|
||||
pub rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exec {
|
||||
fn name(&self) -> &str {
|
||||
"exec"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("exec")
|
||||
.required("command", SyntaxShape::Path, "the command to execute")
|
||||
.rest(SyntaxShape::Pattern, "any additional arguments for command")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Execute command"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exec(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Execute 'ps aux'",
|
||||
example: "exec ps aux",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Execute 'nautilus'",
|
||||
example: "exec nautilus",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _): (ExecArgs, _) = args.process(®istry).await?;
|
||||
|
||||
let mut command = Command::new(args.command.item);
|
||||
for tagged_arg in args.rest {
|
||||
command.arg(tagged_arg.item);
|
||||
}
|
||||
|
||||
let err = command.exec(); // this replaces our process, should not return
|
||||
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
format!("{}", err),
|
||||
&name,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
"exec is not supported on your platform",
|
||||
&args.call_info.name_tag,
|
||||
))
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::command::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
|
||||
.delimiter(separator as u8)
|
||||
.from_reader(s.as_bytes());
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
let headers = if headerless {
|
||||
(1..=reader.headers()?.len())
|
||||
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
|
||||
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))
|
||||
tagged_row.insert_value(
|
||||
header,
|
||||
UntaggedValue::decimal_from_float(f, span).into_value(&tag),
|
||||
)
|
||||
} else {
|
||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
||||
}
|
||||
|
@ -39,11 +39,12 @@ impl WholeStreamCommand for FromJSON {
|
||||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal(*n).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::String(s) => {
|
||||
|
@ -45,6 +45,7 @@ async fn from_ods(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
|
||||
let (
|
||||
@ -73,7 +74,7 @@ async fn from_ods(
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
|
@ -30,11 +30,12 @@ impl WholeStreamCommand for FromTOML {
|
||||
|
||||
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
|
||||
toml::Value::Float(n) => UntaggedValue::decimal(*n).into_value(tag),
|
||||
toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
|
||||
toml::Value::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ async fn from_xlsx(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromXLSXArgs {
|
||||
@ -73,7 +74,7 @@ async fn from_xlsx(
|
||||
let value = match cell {
|
||||
DataType::Empty => UntaggedValue::nothing(),
|
||||
DataType::String(s) => UntaggedValue::string(s),
|
||||
DataType::Float(f) => UntaggedValue::decimal(*f),
|
||||
DataType::Float(f) => UntaggedValue::decimal_from_float(*f, span),
|
||||
DataType::Int(i) => UntaggedValue::int(*i),
|
||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||
_ => UntaggedValue::nothing(),
|
||||
|
@ -58,6 +58,7 @@ fn convert_yaml_value_to_nu_value(
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
let err_not_compatible_number = ShellError::labeled_error(
|
||||
"Expected a compatible number",
|
||||
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||
span,
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
let result: Result<Vec<Value>, ShellError> = a
|
||||
|
@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
|
||||
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::HasFallibleSpan;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
@ -58,17 +58,95 @@ impl WholeStreamCommand for Get {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
let fields = path.clone();
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: column_paths }, mut input) = args.process(®istry).await?;
|
||||
if column_paths.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
get_data_by_column_path(
|
||||
obj,
|
||||
path,
|
||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
||||
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
trace!("get {:?}", column_paths);
|
||||
let output_stream = input
|
||||
.map(move |item| {
|
||||
let output = column_paths
|
||||
.iter()
|
||||
.map(move |path| get_output(&item, path))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream();
|
||||
Ok(output_stream)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_output(item: &Value, path: &ColumnPath) -> Vec<Result<ReturnSuccess, ShellError>> {
|
||||
match get_column_path(path, item) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
}) => vec![],
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
}) => rows.into_iter().map(ReturnSuccess::value).collect(),
|
||||
Ok(other) => vec![ReturnSuccess::value(other)],
|
||||
Err(reason) => vec![ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
get_data_by_column_path(obj, path, move |obj_source, column_path_tried, error| {
|
||||
let path_members_span = path.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
UntaggedValue::Table(rows) => {
|
||||
return get_column_path_from_table_error(
|
||||
rows,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
);
|
||||
}
|
||||
UntaggedValue::Row(columns) => {
|
||||
if let Some(error) = get_column_from_row_error(
|
||||
columns,
|
||||
column_path_tried,
|
||||
&path_members_span,
|
||||
obj_source,
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
} else {
|
||||
error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_column_path_from_table_error(
|
||||
rows: &[Value],
|
||||
column_path_tried: &PathMember,
|
||||
path_members_span: &Span,
|
||||
) -> ShellError {
|
||||
match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
@ -93,15 +171,15 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
"Appears to contain rows. Try indexing instead.",
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
@ -112,11 +190,11 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(",")
|
||||
names.join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
@ -130,16 +208,24 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
format!("There isn't a row indexed at {}", idx),
|
||||
column_path_tried.span,
|
||||
secondary_label,
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
UntaggedValue::Row(columns) => match column_path_tried {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_from_row_error(
|
||||
columns: &Dictionary,
|
||||
column_path_tried: &PathMember,
|
||||
path_members_span: &Span,
|
||||
obj_source: &Value,
|
||||
) -> Option<ShellError> {
|
||||
match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
@ -147,107 +233,35 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
Some(ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
&obj_source.data_descriptors().join(",")
|
||||
&obj_source.data_descriptors().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
} => Some(ShellError::labeled_error_with_secondary(
|
||||
"No rows available",
|
||||
format!("A row at '{}' can't be indexed.", &idx),
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Appears to contain columns. Columns available: {}",
|
||||
columns.keys().join(",")
|
||||
columns.keys().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
|
||||
error
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -28,7 +28,7 @@ impl WholeStreamCommand for GroupBy {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"create a new table grouped."
|
||||
"Create a new table grouped."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -96,7 +96,7 @@ pub async fn group_by(
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(args.call_info.args.head.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use indexmap::IndexMap;
|
||||
|
@ -63,54 +63,114 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
Ok(
|
||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
return None;
|
||||
}
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let (mut subcommand_names, command_names) = sorted_names
|
||||
.into_iter()
|
||||
// Internal only commands shouldn't be displayed
|
||||
.filter(|cmd_name| {
|
||||
registry
|
||||
.get_command(&cmd_name)
|
||||
.filter(|command| !command.is_internal())
|
||||
.is_some()
|
||||
})
|
||||
.partition::<Vec<_>, _>(|cmd_name| cmd_name.contains(' '));
|
||||
|
||||
fn process_name(
|
||||
dict: &mut TaggedDictBuilder,
|
||||
cmd_name: &str,
|
||||
registry: CommandRegistry,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<(), ShellError> {
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
registry.get_command(&cmd_name).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
format!("Could not load {}", cmd_name),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
})?,
|
||||
name,
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
dict.insert_untagged("name", cmd_name);
|
||||
dict.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
get_data_by_key(&value, "usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
.as_string()
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_subcommands_table(
|
||||
subcommand_names: &mut Vec<String>,
|
||||
cmd_name: &str,
|
||||
registry: CommandRegistry,
|
||||
rest: Vec<Tagged<String>>,
|
||||
name: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
let (matching, not_matching) =
|
||||
subcommand_names.drain(..).partition(|subcommand_name| {
|
||||
subcommand_name.starts_with(&format!("{} ", cmd_name))
|
||||
});
|
||||
*subcommand_names = not_matching;
|
||||
Ok(if !matching.is_empty() {
|
||||
UntaggedValue::table(
|
||||
&(matching
|
||||
.into_iter()
|
||||
.map(|cmd_name: String| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
Ok(short_desc.into_value())
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?[..]),
|
||||
)
|
||||
.into_value(name)
|
||||
} else {
|
||||
UntaggedValue::nothing().into_value(name)
|
||||
})
|
||||
}
|
||||
|
||||
let iterator =
|
||||
command_names
|
||||
.into_iter()
|
||||
.map(move |cmd_name| -> Result<_, ShellError> {
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
process_name(
|
||||
&mut short_desc,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?;
|
||||
short_desc.insert_value(
|
||||
"subcommands",
|
||||
make_subcommands_table(
|
||||
&mut subcommand_names,
|
||||
&cmd_name,
|
||||
registry.clone(),
|
||||
rest.clone(),
|
||||
name.clone(),
|
||||
)?,
|
||||
);
|
||||
ReturnSuccess::value(short_desc.into_value())
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(iterator).to_output_stream())
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||
®istry,
|
||||
|
@ -178,11 +178,7 @@ fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, Shell
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(
|
||||
value,
|
||||
&path,
|
||||
Box::new(move |(_, _, error)| error),
|
||||
);
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::NuConfig;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
@ -9,9 +9,7 @@ use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_LOCATION: &str = "history.txt";
|
||||
|
||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||
let default_path = nu_data::config::user_data()
|
||||
.map(|mut p| {
|
||||
p.push(DEFAULT_LOCATION);
|
||||
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||
|
||||
vars.get("history-path")
|
||||
config
|
||||
.var("history-path")
|
||||
.map_or(default_path.clone(), |custom_path| {
|
||||
match custom_path.as_string() {
|
||||
Ok(path) => PathBuf::from(path),
|
||||
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let config = NuConfig::new();
|
||||
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||
let tag = args.call_info.name_tag;
|
||||
let path = history_path(&config);
|
||||
let file = File::open(path);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -74,7 +74,7 @@ async fn if_command(
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
|
||||
let (
|
||||
IfArgs {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
@ -48,7 +48,7 @@ impl WholeStreamCommand for Insert {
|
||||
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<Context>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
mut value: Arc<Value>,
|
||||
field: Arc<ColumnPath>,
|
||||
@ -136,7 +136,7 @@ async fn insert(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
||||
let value = Arc::new(value);
|
||||
let column = Arc::new(column);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
@ -50,9 +50,16 @@ async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
if input.is_empty() {
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::boolean(true).into_value(name_tag),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
@ -72,7 +79,6 @@ async fn is_empty(
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
@ -53,7 +53,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Get the average of a list of numbers",
|
||||
example: "echo [-50 100.0 25] | math avg",
|
||||
result: Some(vec![UntaggedValue::decimal(25).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
25.0,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -137,6 +141,28 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
)),
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Duration(duration)),
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Duration(duration));
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Duration(result))) => {
|
||||
Ok(UntaggedValue::duration(result).into_value(name))
|
||||
}
|
||||
Ok(_) => Err(ShellError::labeled_error(
|
||||
"could not calculate average of non-integer or unrelated types",
|
||||
"source",
|
||||
name,
|
||||
)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(name.span),
|
||||
right_type.spanned(name.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(other),
|
||||
..
|
||||
|
@ -39,7 +39,7 @@ mod tests {
|
||||
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
||||
};
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::{decimal, int, table};
|
||||
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
|
||||
use nu_protocol::Value;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -98,17 +98,17 @@ mod tests {
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Values",
|
||||
values: vec![int(10), decimal(26.5), decimal(26.5)],
|
||||
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(21)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(table(&[decimal(26.5)])),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(table(&[decimal_from_float(26.5)])),
|
||||
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(63)),
|
||||
Ok(decimal(60.5)),
|
||||
Ok(decimal_from_float(60.5)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
@ -128,14 +128,14 @@ mod tests {
|
||||
},
|
||||
TestCase {
|
||||
description: "Mixed Negative Values",
|
||||
values: vec![decimal(-13.5), decimal(-11.5), int(10)],
|
||||
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(decimal(-13.5)),
|
||||
Ok(decimal_from_float(-13.5)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(-11.5)),
|
||||
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
|
||||
Ok(decimal_from_float(-11.5)),
|
||||
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
|
||||
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(-15)),
|
||||
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
||||
@ -151,10 +151,10 @@ mod tests {
|
||||
],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
|
||||
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
|
||||
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(2.5), "col2".to_owned() => decimal_from_float(6.5)]),
|
||||
Ok(row![
|
||||
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
||||
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
|
||||
@ -164,7 +164,7 @@ mod tests {
|
||||
"col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string"))
|
||||
]),
|
||||
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
|
||||
Ok(row!["col1".to_owned() => decimal(1.25), "col2".to_owned() => decimal(1.25)]),
|
||||
Ok(row!["col1".to_owned() => decimal_from_float(1.25), "col2".to_owned() => decimal_from_float(1.25)]),
|
||||
],
|
||||
},
|
||||
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved
|
||||
|
@ -41,7 +41,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Evalulate math in the pipeline",
|
||||
example: "echo '10 / 4' | math eval",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
2.5,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
vec![Example {
|
||||
description: "Get the median of a list of numbers",
|
||||
example: "echo [3 8 9 12 12 15] | math median",
|
||||
result: Some(vec![UntaggedValue::decimal(10.5).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
10.5,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
&name.span,
|
||||
)
|
||||
}),
|
||||
v if v.is_duration() => sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec()),
|
||||
// v is nothing primitive
|
||||
v if v.is_none() => sum(
|
||||
UntaggedValue::int(0).into_untagged_value(),
|
||||
|
@ -99,12 +99,20 @@ impl WholeStreamCommand for SubCommand {
|
||||
Example {
|
||||
description: "Get the variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance",
|
||||
result: Some(vec![UntaggedValue::decimal(2).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
2.0,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the sample variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance -s",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
result: Some(vec![UntaggedValue::decimal_from_float(
|
||||
2.5,
|
||||
Span::unknown(),
|
||||
)
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_data::value::merge_values;
|
||||
|
||||
@ -55,7 +55,7 @@ async fn merge(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let name_tag = raw_args.call_info.name_tag.clone();
|
||||
let (merge_args, input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = merge_args.block;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"moves across desired subcommand."
|
||||
"Moves across desired subcommand."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
|
3
crates/nu-cli/src/commands/nu/mod.rs
Normal file
3
crates/nu-cli/src/commands/nu/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod plugin;
|
||||
|
||||
pub use plugin::SubCommand as NuPlugin;
|
124
crates/nu-cli/src/commands/nu/plugin.rs
Normal file
124
crates/nu-cli/src/commands/nu/plugin.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
#[serde(rename = "load")]
|
||||
pub load_path: Option<Tagged<PathBuf>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"nu plugin"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu plugin").named(
|
||||
"load",
|
||||
SyntaxShape::Path,
|
||||
"a path to load the plugins from",
|
||||
Some('l'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Nu Plugin"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Load all plugins in the current directory",
|
||||
example: "nu plugin --load .",
|
||||
result: Some(vec![]),
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (Arguments { load_path }, _) = args.process(®istry).await?;
|
||||
|
||||
if let Some(Tagged {
|
||||
item: load_path,
|
||||
tag,
|
||||
}) = load_path
|
||||
{
|
||||
let path = canonicalize(shell_manager.path(), load_path).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot load plugins from directory",
|
||||
"directory not found",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Cannot load plugins from directory",
|
||||
"is not a directory",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let has_exec = path
|
||||
.metadata()
|
||||
.map(|m| umask::Mode::from(m.permissions().mode()).has(umask::USER_READ))
|
||||
.map_err(|e| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot load plugins from directory",
|
||||
format!("cannot stat ({})", e),
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
if !has_exec {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Cannot load plugins from directory",
|
||||
"permission denied",
|
||||
&tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(vec![ReturnSuccess::action(CommandAction::AddPlugins(
|
||||
path.to_string_lossy().to_string(),
|
||||
))]
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&SubCommand, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
112
crates/nu-cli/src/commands/random/integer.rs
Normal file
112
crates/nu-cli/src/commands/random/integer.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::deserializer::NumericRange;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use rand::prelude::{thread_rng, Rng};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IntegerArgs {
|
||||
range: Option<Tagged<NumericRange>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"random integer"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("random integer").optional("range", SyntaxShape::Range, "Range of values")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate a random integer [min..max]"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
integer(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Generate an unconstrained random integer",
|
||||
example: "random integer",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate a random integer less than or equal to 500",
|
||||
example: "random integer ..500",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate a random integer greater than or equal to 100000",
|
||||
example: "random integer 100000..",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Generate a random integer between 1 and 10",
|
||||
example: "random integer 1..10",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn integer(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (IntegerArgs { range }, _) = args.process(®istry).await?;
|
||||
|
||||
let (min, max) = if let Some(range) = &range {
|
||||
(range.item.min(), range.item.max())
|
||||
} else {
|
||||
(0, u64::MAX)
|
||||
};
|
||||
|
||||
match min.cmp(&max) {
|
||||
Ordering::Greater => Err(ShellError::labeled_error(
|
||||
format!("Invalid range {}..{}", min, max),
|
||||
"expected a valid range",
|
||||
range
|
||||
.expect("Unexpected ordering error in random integer")
|
||||
.span(),
|
||||
)),
|
||||
Ordering::Equal => {
|
||||
let untagged_result = UntaggedValue::int(min).into_value(Tag::unknown());
|
||||
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
|
||||
}
|
||||
_ => {
|
||||
let mut thread_rng = thread_rng();
|
||||
// add 1 to max, because gen_range is right-exclusive
|
||||
let max = max.saturating_add(1);
|
||||
let result: u64 = thread_rng.gen_range(min, max);
|
||||
|
||||
let untagged_result = UntaggedValue::int(result).into_value(Tag::unknown());
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ pub mod command;
|
||||
|
||||
pub mod bool;
|
||||
pub mod dice;
|
||||
pub mod integer;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub mod uuid;
|
||||
|
||||
@ -9,5 +10,6 @@ pub use command::Command as Random;
|
||||
|
||||
pub use self::bool::SubCommand as RandomBool;
|
||||
pub use dice::SubCommand as RandomDice;
|
||||
pub use integer::SubCommand as RandomInteger;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub use uuid::SubCommand as RandomUUID;
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::NumericRange;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_protocol::{RangeInclusion, ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -44,11 +44,23 @@ async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
let registry = registry.clone();
|
||||
let (RangeArgs { area }, input) = args.process(®istry).await?;
|
||||
let range = area.item;
|
||||
let (from, _) = range.from;
|
||||
let (to, _) = range.to;
|
||||
|
||||
let from = *from as usize;
|
||||
let to = *to as usize;
|
||||
let (from, left_inclusive) = range.from;
|
||||
let (to, right_inclusive) = range.to;
|
||||
let from = from.map(|from| *from as usize).unwrap_or(0).saturating_add(
|
||||
if left_inclusive == RangeInclusion::Inclusive {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
},
|
||||
);
|
||||
let to = to
|
||||
.map(|to| *to as usize)
|
||||
.unwrap_or(usize::MAX)
|
||||
.saturating_sub(if right_inclusive == RangeInclusion::Inclusive {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
});
|
||||
|
||||
Ok(input
|
||||
.skip(from)
|
||||
|
@ -81,7 +81,7 @@ impl WholeStreamCommand for Reduce {
|
||||
async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<Context>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
row: Value,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let row_clone = row.clone();
|
||||
@ -104,7 +104,7 @@ async fn reduce(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let base_scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(reduce_args.block);
|
||||
let (ioffset, start) = match reduce_args.fold {
|
||||
@ -138,7 +138,19 @@ async fn reduce(
|
||||
let row = each::make_indexed_item(input.0 + ioffset, input.1);
|
||||
|
||||
async {
|
||||
let f = acc?.into_vec().await[0].clone();
|
||||
let values = acc?.drain_vec().await;
|
||||
|
||||
let f = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
scope.vars.insert(String::from("$acc"), f);
|
||||
process_row(block, Arc::new(scope), context, row).await
|
||||
}
|
||||
@ -154,9 +166,20 @@ async fn reduce(
|
||||
let context = Arc::clone(&context);
|
||||
|
||||
async {
|
||||
scope
|
||||
.vars
|
||||
.insert(String::from("$acc"), acc?.into_vec().await[0].clone());
|
||||
let values = acc?.drain_vec().await;
|
||||
|
||||
let f = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
scope.vars.insert(String::from("$acc"), f);
|
||||
process_row(block, Arc::new(scope), context, row).await
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
@ -44,7 +44,7 @@ impl WholeStreamCommand for AliasCommand {
|
||||
block.set_redirect(call_info.args.external_redirection);
|
||||
|
||||
let alias_command = self.clone();
|
||||
let mut context = Context::from_args(&args, ®istry);
|
||||
let mut context = EvaluationContext::from_args(&args, ®istry);
|
||||
let input = args.input;
|
||||
|
||||
let mut scope = call_info.scope.clone();
|
||||
|
@ -48,7 +48,19 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
"Runs external command (not a nushell builtin)"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Run the external echo command",
|
||||
example: "run_external echo 'nushell'",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn is_internal(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -72,33 +84,16 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
.and_then(spanned_expression_to_string)?;
|
||||
|
||||
let mut external_context = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
EvaluationContext {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
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(),
|
||||
)),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let is_interactive = self.interactive;
|
||||
@ -148,7 +143,10 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
async fn maybe_autocd_dir<'a>(cmd: &ExternalCommand, ctx: &mut Context) -> Option<String> {
|
||||
async fn maybe_autocd_dir<'a>(
|
||||
cmd: &ExternalCommand,
|
||||
ctx: &mut EvaluationContext,
|
||||
) -> Option<String> {
|
||||
// We will "auto cd" if
|
||||
// - the command name ends in a path separator, or
|
||||
// - it's not a command on the path and no arguments were given.
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
@ -83,7 +83,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
|
||||
let fetcher = get_data_by_column_path(
|
||||
&value,
|
||||
&path,
|
||||
Box::new(move |(obj_source, path_member_tried, error)| {
|
||||
move |obj_source, path_member_tried, error| {
|
||||
if let PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
@ -98,7 +98,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
|
||||
}
|
||||
|
||||
error
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
let field = path.clone();
|
||||
|
@ -37,9 +37,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("active", "X".to_string());
|
||||
dict.insert_untagged("active", true);
|
||||
} else {
|
||||
dict.insert_untagged("active", " ".to_string());
|
||||
dict.insert_untagged("active", false);
|
||||
}
|
||||
dict.insert_untagged("name", shell.name());
|
||||
dict.insert_untagged("path", shell.path());
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Value};
|
||||
|
@ -1,8 +1,11 @@
|
||||
extern crate unicode_segmentation;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub struct Size;
|
||||
|
||||
@ -29,7 +32,8 @@ impl WholeStreamCommand for Size {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
vec![
|
||||
Example {
|
||||
description: "Count the number of words in a string",
|
||||
example: r#"echo "There are seven words in this sentence" | size"#,
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
@ -39,7 +43,19 @@ impl WholeStreamCommand for Size {
|
||||
"bytes".to_string() => UntaggedValue::int(38).into(),
|
||||
})
|
||||
.into()]),
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Counts unicode characters correctly in a string",
|
||||
example: r#"echo "Amélie Amelie" | size"#,
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"lines".to_string() => UntaggedValue::int(0).into(),
|
||||
"words".to_string() => UntaggedValue::int(2).into(),
|
||||
"chars".to_string() => UntaggedValue::int(13).into(),
|
||||
"bytes".to_string() => UntaggedValue::int(15).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,15 +88,15 @@ fn count(contents: &str, tag: impl Into<Tag>) -> Value {
|
||||
let bytes = contents.len() as i64;
|
||||
let mut end_of_word = true;
|
||||
|
||||
for c in contents.chars() {
|
||||
for c in UnicodeSegmentation::graphemes(contents, true) {
|
||||
chars += 1;
|
||||
|
||||
match c {
|
||||
'\n' => {
|
||||
"\n" => {
|
||||
lines += 1;
|
||||
end_of_word = true;
|
||||
}
|
||||
' ' => end_of_word = true,
|
||||
" " => end_of_word = true,
|
||||
_ => {
|
||||
if end_of_word {
|
||||
words += 1;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
@ -1,17 +1,26 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::atomic::Ordering,
|
||||
task::{Poll, Waker},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use std::{thread, time};
|
||||
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
pub struct Sleep;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SleepArgs {
|
||||
pub dur: Tagged<u64>,
|
||||
pub duration: Tagged<u64>,
|
||||
pub rest: Vec<Tagged<u64>>,
|
||||
}
|
||||
|
||||
@ -28,7 +37,7 @@ impl WholeStreamCommand for Sleep {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"delay for a specified amount of time"
|
||||
"Delay for a specified amount of time"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -36,7 +45,26 @@ impl WholeStreamCommand for Sleep {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sleep(args, registry).await
|
||||
let registry = registry.clone();
|
||||
let ctrl_c = args.ctrl_c().clone();
|
||||
|
||||
let (SleepArgs { duration, rest }, input) = args.process(®istry).await?;
|
||||
|
||||
let total_dur = Duration::from_nanos(duration.item)
|
||||
+ rest
|
||||
.iter()
|
||||
.map(|val| Duration::from_nanos(val.item))
|
||||
.sum::<Duration>();
|
||||
|
||||
SleepFuture::new(total_dur, ctrl_c).await;
|
||||
// this is necessary because the following 2 commands gave different results:
|
||||
// `echo | sleep 1sec` - nothing
|
||||
// `sleep 1sec` - table with 0 elements
|
||||
if input.is_empty() {
|
||||
Ok(OutputStream::empty())
|
||||
} else {
|
||||
Ok(input.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -51,20 +79,100 @@ impl WholeStreamCommand for Sleep {
|
||||
example: "sleep 1sec 1sec 1sec",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Delay the output of another command by 1sec",
|
||||
example: "echo [55 120] | sleep 1sec",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(55).into(),
|
||||
UntaggedValue::int(120).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn sleep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
struct SleepFuture {
|
||||
shared_state: Arc<Mutex<SharedState>>,
|
||||
}
|
||||
|
||||
let (SleepArgs { dur, rest }, ..) = args.process(®istry).await?;
|
||||
impl SleepFuture {
|
||||
/// Create a new `SleepFuture` which will complete after the provided
|
||||
/// timeout and check for Ctrl+C periodically.
|
||||
pub fn new(duration: Duration, ctrl_c: Arc<AtomicBool>) -> Self {
|
||||
let shared_state = Arc::new(Mutex::new(SharedState {
|
||||
done: false,
|
||||
waker: None,
|
||||
}));
|
||||
|
||||
let total_dur = dur.item + rest.iter().map(|val| val.item).sum::<u64>();
|
||||
let total_dur = time::Duration::from_nanos(total_dur);
|
||||
thread::sleep(total_dur);
|
||||
// Spawn the main sleep thread
|
||||
let thread_shared_state = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
thread::sleep(duration);
|
||||
let mut shared_state = thread_shared_state.lock();
|
||||
// Signal that the timer has completed and wake up the last
|
||||
// task on which the future was polled, if one exists.
|
||||
if !shared_state.done {
|
||||
shared_state.done = true;
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
// Spawn the Ctrl+C-watching polling thread
|
||||
let thread_shared_state = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
{
|
||||
let mut shared_state = thread_shared_state.lock();
|
||||
// exit if the main thread is done
|
||||
if shared_state.done {
|
||||
return;
|
||||
}
|
||||
// finish the future prematurely if Ctrl+C has been pressed
|
||||
if ctrl_c.load(Ordering::SeqCst) {
|
||||
shared_state.done = true;
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake()
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// sleep for a short time
|
||||
thread::sleep(CTRL_C_CHECK_INTERVAL);
|
||||
}
|
||||
});
|
||||
|
||||
SleepFuture { shared_state }
|
||||
}
|
||||
}
|
||||
|
||||
struct SharedState {
|
||||
done: bool,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl Future for SleepFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
// Look at the shared state to see if the timer has already completed.
|
||||
let mut shared_state = self.shared_state.lock();
|
||||
if shared_state.done {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
// Set the waker if necessary
|
||||
if shared_state
|
||||
.waker
|
||||
.as_ref()
|
||||
.map(|waker| !waker.will_wake(&cx.waker()))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
shared_state.waker = Some(cx.waker().clone());
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -73,7 +181,6 @@ mod tests {
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
@ -81,6 +188,8 @@ mod tests {
|
||||
test_examples(Sleep {});
|
||||
let elapsed = start.elapsed();
|
||||
println!("{:?}", elapsed);
|
||||
assert!(elapsed >= std::time::Duration::from_secs(4));
|
||||
// only examples with actual output are run
|
||||
assert!(elapsed >= std::time::Duration::from_secs(1));
|
||||
assert!(elapsed < std::time::Duration::from_secs(2));
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ fn action(
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
@ -145,8 +145,7 @@ mod tests {
|
||||
let word = string("Cargo.tomL");
|
||||
let pattern = ".tomL";
|
||||
let insensitive = false;
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
@ -157,8 +156,7 @@ mod tests {
|
||||
let word = string("Cargo.tomL");
|
||||
let pattern = "Lomt.";
|
||||
let insensitive = false;
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
@ -169,8 +167,7 @@ mod tests {
|
||||
let word = string("Cargo.ToMl");
|
||||
let pattern = ".TOML";
|
||||
let insensitive = true;
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
@ -181,8 +178,7 @@ mod tests {
|
||||
let word = string("Cargo.tOml");
|
||||
let pattern = "lomt.";
|
||||
let insensitive = true;
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -104,7 +104,7 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
@ -118,8 +118,7 @@ mod tests {
|
||||
fn str_ends_with_pattern() {
|
||||
let word = string("Cargo.toml");
|
||||
let pattern = ".toml";
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
@ -129,8 +128,7 @@ mod tests {
|
||||
fn str_does_not_end_with_pattern() {
|
||||
let word = string("Cargo.toml");
|
||||
let pattern = "Car";
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -7,7 +7,6 @@ use nu_protocol::{
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -15,6 +14,7 @@ struct Arguments {
|
||||
find: Tagged<String>,
|
||||
replace: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
@ -33,6 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally find and replace text by column paths",
|
||||
)
|
||||
.switch("all", "replace all occurences of find string", Some('a'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -48,11 +49,18 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
vec![
|
||||
Example {
|
||||
description: "Find and replace contents with capture group",
|
||||
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'",
|
||||
result: Some(vec![Value::from("my_library.nu")]),
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find string",
|
||||
example: "echo 'abc abc abc' | str find-replace -a 'b' 'z'",
|
||||
result: Some(vec![Value::from("azc azc azc")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,17 +78,17 @@ async fn operate(
|
||||
find,
|
||||
replace,
|
||||
rest,
|
||||
all,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let options = FindReplace(find.item, replace.item);
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &options, v.tag())?)
|
||||
ReturnSuccess::value(action(&v, &options, v.tag(), all)?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
@ -89,7 +97,7 @@ async fn operate(
|
||||
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &options, old.tag())),
|
||||
Box::new(move |old| action(old, &options, old.tag(), all)),
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -99,7 +107,12 @@ async fn operate(
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, options: &FindReplace, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
fn action(
|
||||
input: &Value,
|
||||
options: &FindReplace,
|
||||
tag: impl Into<Tag>,
|
||||
all: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
@ -109,7 +122,13 @@ fn action(input: &Value, options: &FindReplace, tag: impl Into<Tag>) -> Result<V
|
||||
let regex = Regex::new(find.as_str());
|
||||
|
||||
let out = match regex {
|
||||
Ok(re) => UntaggedValue::string(re.replace(s, replacement.as_str()).to_owned()),
|
||||
Ok(re) => {
|
||||
if all {
|
||||
UntaggedValue::string(re.replace_all(s, replacement.as_str()).to_owned())
|
||||
} else {
|
||||
UntaggedValue::string(re.replace(s, replacement.as_str()).to_owned())
|
||||
}
|
||||
}
|
||||
Err(_) => UntaggedValue::string(s),
|
||||
};
|
||||
|
||||
@ -143,10 +162,10 @@ mod tests {
|
||||
fn can_have_capture_groups() {
|
||||
let word = string("Cargo.toml");
|
||||
let expected = string("Carga.toml");
|
||||
|
||||
let all = false;
|
||||
let find_replace_options = FindReplace("Cargo.(.+)".to_string(), "Carga.$1".to_string());
|
||||
|
||||
let actual = action(&word, &find_replace_options, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &find_replace_options, Tag::unknown(), all).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use nu_source::Tagged;
|
||||
use num_bigint::{BigInt, BigUint, ToBigInt};
|
||||
|
||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
use num_format::Locale;
|
||||
use num_traits::{Pow, Signed};
|
||||
use std::iter;
|
||||
|
||||
@ -40,12 +40,15 @@ impl WholeStreamCommand for SubCommand {
|
||||
"decimal digits to which to round",
|
||||
Some('d'),
|
||||
)
|
||||
/*
|
||||
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
|
||||
.switch(
|
||||
"group-digits",
|
||||
// TODO according to system localization
|
||||
"group digits, currently by thousand with commas",
|
||||
Some('g'),
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -67,6 +70,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
example: "= 1.7 | str from -d 0",
|
||||
result: Some(vec![UntaggedValue::string("2").into_untagged_value()]),
|
||||
},
|
||||
/*
|
||||
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
|
||||
Example {
|
||||
description: "format large number with localized digit grouping",
|
||||
example: "= 1000000.2 | str from -g",
|
||||
@ -74,6 +79,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
UntaggedValue::string("1,000,000.2").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
*/
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -147,7 +153,7 @@ pub fn action(
|
||||
}
|
||||
|
||||
fn format_bigint(int: &BigInt) -> String {
|
||||
int.to_formatted_string(&Locale::en)
|
||||
format!("{}", int)
|
||||
|
||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
||||
// #[cfg(windows)]
|
||||
@ -200,10 +206,8 @@ fn format_decimal(mut decimal: BigDecimal, digits: Option<u64>, group_digits: bo
|
||||
|
||||
let format_default_loc = |int_part: BigInt| {
|
||||
let loc = Locale::en;
|
||||
let (int_str, sep) = (
|
||||
int_part.to_formatted_string(&loc),
|
||||
String::from(loc.decimal()),
|
||||
);
|
||||
//TODO: when num_format is available for recent bigint, replace this with the locale-based format
|
||||
let (int_str, sep) = (format!("{}", int_part), String::from(loc.decimal()));
|
||||
|
||||
format!("{}{}{}", int_str, sep, dec_str)
|
||||
};
|
||||
|
@ -104,7 +104,7 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
@ -118,8 +118,7 @@ mod tests {
|
||||
fn str_starts_with_pattern() {
|
||||
let word = string("Cargo.toml");
|
||||
let pattern = "Car";
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
@ -129,8 +128,7 @@ mod tests {
|
||||
fn str_does_not_start_with_pattern() {
|
||||
let word = string("Cargo.toml");
|
||||
let pattern = ".toml";
|
||||
let expected =
|
||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
||||
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -8,7 +8,6 @@ use nu_protocol::{
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::{as_string, ValueExt};
|
||||
|
||||
use std::cmp;
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::TryInto;
|
||||
|
||||
@ -64,9 +63,9 @@ impl WholeStreamCommand for SubCommand {
|
||||
result: Some(vec![Value::from("nushell")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the last characters from the string",
|
||||
description: "Drop the last `n` characters from the string",
|
||||
example: "echo 'good nushell' | str substring ',-5'",
|
||||
result: Some(vec![Value::from("shell")]),
|
||||
result: Some(vec![Value::from("good nu")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the remaining characters from a starting index",
|
||||
@ -141,8 +140,16 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
|
||||
)
|
||||
})?;
|
||||
|
||||
let start: isize = options.0;
|
||||
let end: isize = options.1;
|
||||
let start: isize = if options.0 < 0 {
|
||||
options.0 + len
|
||||
} else {
|
||||
options.0
|
||||
};
|
||||
let end: isize = if options.1 < 0 {
|
||||
std::cmp::max(len + options.1, 0)
|
||||
} else {
|
||||
options.1
|
||||
};
|
||||
|
||||
if start < len && end >= 0 {
|
||||
match start.cmp(&end) {
|
||||
@ -152,42 +159,14 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
|
||||
"End must be greater than or equal to Start",
|
||||
tag.span,
|
||||
)),
|
||||
Ordering::Less => {
|
||||
let end: isize = cmp::min(options.1, len);
|
||||
|
||||
Ok(UntaggedValue::string(
|
||||
Ordering::Less => Ok(UntaggedValue::string(
|
||||
s.chars()
|
||||
.skip(start as usize)
|
||||
.take((end - start) as usize)
|
||||
.collect::<String>(),
|
||||
)
|
||||
.into_value(tag))
|
||||
.into_value(tag)),
|
||||
}
|
||||
}
|
||||
} else if start >= 0 && end <= 0 {
|
||||
let end = options.1.abs();
|
||||
let reversed = s
|
||||
.chars()
|
||||
.skip(start as usize)
|
||||
.take((len - start) as usize)
|
||||
.collect::<String>();
|
||||
|
||||
let reversed = if start == 0 {
|
||||
reversed
|
||||
} else {
|
||||
s.chars().take(start as usize).collect::<String>()
|
||||
};
|
||||
|
||||
let reversed = reversed
|
||||
.chars()
|
||||
.rev()
|
||||
.take(end as usize)
|
||||
.collect::<String>();
|
||||
|
||||
Ok(
|
||||
UntaggedValue::string(reversed.chars().rev().collect::<String>())
|
||||
.into_value(tag),
|
||||
)
|
||||
} else {
|
||||
Ok(UntaggedValue::string("").into_value(tag))
|
||||
}
|
||||
@ -344,19 +323,20 @@ mod tests {
|
||||
expectation("andr", (0, 4)),
|
||||
expectation("andre", (0, 5)),
|
||||
expectation("andres", (0, 6)),
|
||||
expectation("andres", (0, -6)),
|
||||
expectation("ndres", (0, -5)),
|
||||
expectation("dres", (0, -4)),
|
||||
expectation("res", (0, -3)),
|
||||
expectation("es", (0, -2)),
|
||||
expectation("s", (0, -1)),
|
||||
expectation("", (0, -6)),
|
||||
expectation("a", (0, -5)),
|
||||
expectation("an", (0, -4)),
|
||||
expectation("and", (0, -3)),
|
||||
expectation("andr", (0, -2)),
|
||||
expectation("andre", (0, -1)),
|
||||
expectation("", (0, -110)),
|
||||
expectation("", (6, 0)),
|
||||
expectation("s", (6, -1)),
|
||||
expectation("es", (6, -2)),
|
||||
expectation("res", (6, -3)),
|
||||
expectation("dres", (6, -4)),
|
||||
expectation("ndres", (6, -5)),
|
||||
expectation("andres", (6, -6)),
|
||||
expectation("", (6, -1)),
|
||||
expectation("", (6, -2)),
|
||||
expectation("", (6, -3)),
|
||||
expectation("", (6, -4)),
|
||||
expectation("", (6, -5)),
|
||||
expectation("", (6, -6)),
|
||||
];
|
||||
|
||||
for expectation in cases.iter() {
|
||||
|
@ -113,7 +113,7 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::{decimal, string};
|
||||
use nu_plugin::test_helpers::value::{decimal_from_float, string};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
@ -127,7 +127,7 @@ mod tests {
|
||||
#[allow(clippy::approx_constant)]
|
||||
fn turns_to_integer() {
|
||||
let word = string("3.1415");
|
||||
let expected = decimal(3.1415);
|
||||
let expected = decimal_from_float(3.1415);
|
||||
|
||||
let actual = action(&word, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -8,6 +8,7 @@ use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
pub use trim_both_ends::SubCommand as Trim;
|
||||
pub use trim_left::SubCommand as TrimLeft;
|
||||
@ -38,14 +39,22 @@ where
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag(), to_trim, &trim_operation)?)
|
||||
ReturnSuccess::value(action(
|
||||
&v,
|
||||
v.tag(),
|
||||
to_trim,
|
||||
&trim_operation,
|
||||
ActionMode::Global,
|
||||
)?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag(), to_trim, &trim_operation)),
|
||||
Box::new(move |old| {
|
||||
action(old, old.tag(), to_trim, &trim_operation, ActionMode::Local)
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -55,27 +64,63 @@ where
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ActionMode {
|
||||
Local,
|
||||
Global,
|
||||
}
|
||||
|
||||
pub fn action<F>(
|
||||
input: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
char_: Option<char>,
|
||||
trim_operation: &F,
|
||||
mode: ActionMode,
|
||||
) -> Result<Value, ShellError>
|
||||
where
|
||||
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
|
||||
{
|
||||
let tag = tag.into();
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag))
|
||||
}
|
||||
other => {
|
||||
other => match mode {
|
||||
ActionMode::Global => match other {
|
||||
UntaggedValue::Row(dictionary) => {
|
||||
let results: Result<Vec<(String, Value)>, ShellError> = dictionary
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|(k, v)| -> Result<_, ShellError> {
|
||||
Ok((
|
||||
k.clone(),
|
||||
action(&v, tag.clone(), char_, trim_operation, mode)?,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let indexmap = IndexMap::from_iter(results?);
|
||||
Ok(UntaggedValue::Row(indexmap.into()).into_value(tag))
|
||||
}
|
||||
UntaggedValue::Table(values) => {
|
||||
let values: Result<Vec<Value>, ShellError> = values
|
||||
.iter()
|
||||
.map(|v| -> Result<_, ShellError> {
|
||||
Ok(action(v, tag.clone(), char_, trim_operation, mode)?)
|
||||
})
|
||||
.collect();
|
||||
Ok(UntaggedValue::Table(values?).into_value(tag))
|
||||
}
|
||||
_ => Ok(input.clone()),
|
||||
},
|
||||
ActionMode::Local => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
tag.span,
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,11 @@ fn trim(s: &str, char_: Option<char>) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{trim, SubCommand};
|
||||
use crate::commands::str_::trim::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use crate::commands::str_::trim::{action, ActionMode};
|
||||
use nu_plugin::{
|
||||
row,
|
||||
test_helpers::value::{int, string, table},
|
||||
};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
@ -79,7 +82,43 @@ mod tests {
|
||||
let word = string("andres ");
|
||||
let expected = string("andres");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), None, &trim).unwrap();
|
||||
let actual = action(&word, Tag::unknown(), None, &trim, ActionMode::Local).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims_global() {
|
||||
let word = string(" global ");
|
||||
let expected = string("global");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_trim_ignores_numbers() {
|
||||
let number = int(2020);
|
||||
let expected = int(2020);
|
||||
|
||||
let actual = action(&number, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_trim_row() {
|
||||
let row = row!["a".to_string() => string(" c "), " b ".to_string() => string(" d ")];
|
||||
let expected = row!["a".to_string() => string("c"), " b ".to_string() => string("d")];
|
||||
|
||||
let actual = action(&row, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_trim_table() {
|
||||
let row = table(&[string(" a "), int(65), string(" d")]);
|
||||
let expected = table(&[string("a"), int(65), string("d")]);
|
||||
|
||||
let actual = action(&row, Tag::unknown(), None, &trim, ActionMode::Global).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -88,7 +127,7 @@ mod tests {
|
||||
let word = string("!#andres#!");
|
||||
let expected = string("#andres#");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), Some('!'), &trim).unwrap();
|
||||
let actual = action(&word, Tag::unknown(), Some('!'), &trim, ActionMode::Local).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user