mirror of
https://github.com/nushell/nushell.git
synced 2025-07-07 18:07:02 +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
|
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||||
condition: eq(variables['style'], 'canary')
|
condition: eq(variables['style'], 'canary')
|
||||||
displayName: Check clippy lints
|
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')
|
condition: eq(variables['style'], 'minimal')
|
||||||
displayName: Run tests
|
displayName: Run tests
|
||||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
- 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"
|
name = "nu"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*/"]
|
members = ["crates/*/"]
|
||||||
@ -18,48 +18,48 @@ members = ["crates/*/"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = {version = "0.19.0", path = "./crates/nu-cli"}
|
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
|
||||||
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
|
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
|
||||||
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
|
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
|
||||||
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
|
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
|
||||||
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
|
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
|
||||||
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
|
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
|
||||||
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
|
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
|
||||||
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
|
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_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||||
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true}
|
nu_plugin_fetch = {version = "0.20.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_bson = {version = "0.20.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_from_sqlite = {version = "0.20.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_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||||
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
|
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
|
||||||
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
|
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
|
||||||
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
|
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||||
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
|
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||||
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
|
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
|
||||||
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
|
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||||
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true}
|
nu_plugin_textview = {version = "0.20.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_bson = {version = "0.20.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_to_sqlite = {version = "0.20.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_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}
|
semver = {version = "0.10.0", optional = true}
|
||||||
url = {version = "2.1.1", optional = true}
|
url = {version = "2.1.1", optional = true}
|
||||||
|
|
||||||
clap = "2.33.1"
|
clap = "2.33.3"
|
||||||
ctrlc = "3.1.4"
|
ctrlc = "3.1.6"
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
log = "0.4.8"
|
log = "0.4.11"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
quick-xml = "0.18.1"
|
quick-xml = "0.18.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[build-dependencies]
|
||||||
serde = {version = "1.0.110", features = ["derive"]}
|
serde = {version = "1.0.115", features = ["derive"]}
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -75,9 +75,11 @@ default = [
|
|||||||
"ptree-support",
|
"ptree-support",
|
||||||
"term-support",
|
"term-support",
|
||||||
"uuid-support",
|
"uuid-support",
|
||||||
|
"rustyline-support",
|
||||||
"match",
|
"match",
|
||||||
"post",
|
"post",
|
||||||
"fetch",
|
"fetch",
|
||||||
|
"rich-benchmark",
|
||||||
]
|
]
|
||||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
||||||
stable = ["default"]
|
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"]
|
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||||
git-support = ["nu-cli/git2"]
|
git-support = ["nu-cli/git2"]
|
||||||
ptree-support = ["nu-cli/ptree"]
|
ptree-support = ["nu-cli/ptree"]
|
||||||
|
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||||
|
rustyline-support = ["nu-cli/rustyline-support"]
|
||||||
term-support = ["nu-cli/term"]
|
term-support = ["nu-cli/term"]
|
||||||
trash-support = ["nu-cli/trash-support"]
|
trash-support = ["nu-cli/trash-support"]
|
||||||
uuid-support = ["nu-cli/uuid_crate"]
|
uuid-support = ["nu-cli/uuid_crate"]
|
||||||
|
@ -4,103 +4,102 @@ description = "CLI for nushell"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-data = {version = "0.19.0", path = "../nu-data"}
|
nu-data = {version = "0.20.0", path = "../nu-data"}
|
||||||
nu-errors = {version = "0.19.0", path = "../nu-errors"}
|
nu-errors = {version = "0.20.0", path = "../nu-errors"}
|
||||||
nu-parser = {version = "0.19.0", path = "../nu-parser"}
|
nu-parser = {version = "0.20.0", path = "../nu-parser"}
|
||||||
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
|
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
|
||||||
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
|
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
|
||||||
nu-source = {version = "0.19.0", path = "../nu-source"}
|
nu-source = {version = "0.20.0", path = "../nu-source"}
|
||||||
nu-table = {version = "0.19.0", path = "../nu-table"}
|
nu-table = {version = "0.20.0", path = "../nu-table"}
|
||||||
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
|
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
|
||||||
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
|
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
|
||||||
|
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
app_dirs = {version = "2", package = "app_dirs2"}
|
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
async-trait = "0.1.36"
|
async-trait = "0.1.40"
|
||||||
base64 = "0.12.3"
|
base64 = "0.12.3"
|
||||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
byte-unit = "3.1.3"
|
byte-unit = "4.0.9"
|
||||||
bytes = "0.5.5"
|
bytes = "0.5.6"
|
||||||
calamine = "0.16"
|
calamine = "0.16.1"
|
||||||
chrono = {version = "0.4.11", features = ["serde"]}
|
chrono = {version = "0.4.15", features = ["serde"]}
|
||||||
clap = "2.33.1"
|
clap = "2.33.3"
|
||||||
codespan-reporting = "0.9.5"
|
codespan-reporting = "0.9.5"
|
||||||
csv = "1.1"
|
csv = "1.1.3"
|
||||||
ctrlc = {version = "3.1.4", optional = true}
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
derive-new = "0.5.8"
|
derive-new = "0.5.8"
|
||||||
directories = {version = "2.0.2", optional = true}
|
directories = {version = "3.0.1", optional = true}
|
||||||
dirs = {version = "2.0.2", optional = true}
|
dirs = {version = "3.0.1", optional = true}
|
||||||
dtparse = "1.1.0"
|
dtparse = "1.1.0"
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
filesize = "0.2.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-util = "0.3.5"
|
||||||
futures_codec = "0.4"
|
futures_codec = "0.4.1"
|
||||||
getset = "0.1.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"
|
glob = "0.3.0"
|
||||||
hex = "0.4"
|
heim = {version = "0.1.0-beta.3", optional = true}
|
||||||
htmlescape = "0.3.1"
|
htmlescape = "0.3.1"
|
||||||
ical = "0.6.*"
|
ical = "0.6.0"
|
||||||
ichwh = {version = "0.3.4", optional = true}
|
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"
|
itertools = "0.9.0"
|
||||||
log = "0.4.8"
|
log = "0.4.11"
|
||||||
meval = "0.2"
|
meval = "0.2.0"
|
||||||
natural = "0.5.0"
|
natural = "0.5.0"
|
||||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||||
num-traits = "0.2.11"
|
num-traits = "0.2.12"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
pin-utils = "0.1.0"
|
pin-utils = "0.1.0"
|
||||||
pretty-hex = "0.1.1"
|
pretty-hex = "0.2.0"
|
||||||
pretty_env_logger = "0.4.0"
|
ptree = {version = "0.3.0", optional = true}
|
||||||
ptree = {version = "0.2", optional = true}
|
|
||||||
query_interface = "0.3.5"
|
query_interface = "0.3.5"
|
||||||
rand = "0.7"
|
rand = "0.7.3"
|
||||||
regex = "1"
|
regex = "1.3.9"
|
||||||
roxmltree = "0.13.0"
|
roxmltree = "0.13.0"
|
||||||
rust-embed = "5.6.0"
|
rust-embed = "5.6.0"
|
||||||
rustyline = "6.2.0"
|
rustyline = {version = "6.3.0", optional = true}
|
||||||
serde = {version = "1.0.114", features = ["derive"]}
|
serde = {version = "1.0.115", features = ["derive"]}
|
||||||
serde-hjson = "0.9.1"
|
serde-hjson = "0.9.1"
|
||||||
serde_bytes = "0.11.5"
|
serde_bytes = "0.11.5"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.55"
|
serde_json = "1.0.57"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8.13"
|
||||||
sha2 = "0.9.1"
|
sha2 = "0.9.1"
|
||||||
shellexpand = "2.0.0"
|
shellexpand = "2.0.0"
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
tempfile = "3.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"
|
term_size = "0.3.2"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
typetag = "0.1.5"
|
unicode-segmentation = "1.6.0"
|
||||||
umask = "1.0.0"
|
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||||
unicode-xid = "0.2.1"
|
|
||||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||||
which = {version = "4.0.2", 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"
|
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]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
umask = "1.0.0"
|
||||||
users = "0.10.0"
|
users = "0.10.0"
|
||||||
|
|
||||||
# TODO this will be possible with new dependency resolver
|
# TODO this will be possible with new dependency resolver
|
||||||
@ -112,17 +111,18 @@ users = "0.10.0"
|
|||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
features = ["bundled", "blob"]
|
features = ["bundled", "blob"]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.23.1"
|
version = "0.24.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
git2 = {version = "0.13", optional = true}
|
git2 = {version = "0.13.11", optional = true}
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9.2"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
clipboard-cli = ["clipboard"]
|
clipboard-cli = ["clipboard"]
|
||||||
|
rich-benchmark = ["heim"]
|
||||||
|
rustyline-support = ["rustyline"]
|
||||||
stable = []
|
stable = []
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
@ -1,38 +1,30 @@
|
|||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||||
use crate::context::Context;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::git::current_branch;
|
|
||||||
use crate::path::canonicalize;
|
use crate::path::canonicalize;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
#[cfg(feature = "rustyline-support")]
|
||||||
use crate::shell::Helper;
|
use crate::shell::Helper;
|
||||||
use crate::EnvironmentSyncer;
|
use crate::EnvironmentSyncer;
|
||||||
use futures_codec::FramedRead;
|
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::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
#[cfg(feature = "rustyline-support")]
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::{
|
||||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
self,
|
||||||
|
config::Configurer,
|
||||||
|
config::{ColorMode, CompletionType, Config},
|
||||||
|
error::ReadlineError,
|
||||||
|
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||||
|
};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::Ordering;
|
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> {
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@ -64,21 +56,16 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
|||||||
search_paths
|
search_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_default_context(
|
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||||
syncer: &mut crate::EnvironmentSyncer,
|
let mut context = EvaluationContext::basic()?;
|
||||||
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);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
use crate::commands::*;
|
use crate::commands::*;
|
||||||
|
|
||||||
context.add_commands(vec![
|
context.add_commands(vec![
|
||||||
|
whole_stream_command(NuPlugin),
|
||||||
// System/file operations
|
// System/file operations
|
||||||
|
whole_stream_command(Exec),
|
||||||
whole_stream_command(Pwd),
|
whole_stream_command(Pwd),
|
||||||
whole_stream_command(Ls),
|
whole_stream_command(Ls),
|
||||||
whole_stream_command(Du),
|
whole_stream_command(Du),
|
||||||
@ -136,7 +123,6 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(SplitRow),
|
whole_stream_command(SplitRow),
|
||||||
whole_stream_command(SplitChars),
|
whole_stream_command(SplitChars),
|
||||||
whole_stream_command(Lines),
|
whole_stream_command(Lines),
|
||||||
whole_stream_command(Trim),
|
|
||||||
whole_stream_command(Echo),
|
whole_stream_command(Echo),
|
||||||
whole_stream_command(Parse),
|
whole_stream_command(Parse),
|
||||||
whole_stream_command(Str),
|
whole_stream_command(Str),
|
||||||
@ -204,6 +190,8 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Rename),
|
whole_stream_command(Rename),
|
||||||
whole_stream_command(Uniq),
|
whole_stream_command(Uniq),
|
||||||
whole_stream_command(Each),
|
whole_stream_command(Each),
|
||||||
|
whole_stream_command(EachGroup),
|
||||||
|
whole_stream_command(EachWindow),
|
||||||
whole_stream_command(IsEmpty),
|
whole_stream_command(IsEmpty),
|
||||||
// Table manipulation
|
// Table manipulation
|
||||||
whole_stream_command(Move),
|
whole_stream_command(Move),
|
||||||
@ -265,6 +253,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(RandomDice),
|
whole_stream_command(RandomDice),
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
whole_stream_command(RandomUUID),
|
whole_stream_command(RandomUUID),
|
||||||
|
whole_stream_command(RandomInteger),
|
||||||
// Path
|
// Path
|
||||||
whole_stream_command(PathBasename),
|
whole_stream_command(PathBasename),
|
||||||
whole_stream_command(PathCommand),
|
whole_stream_command(PathCommand),
|
||||||
@ -282,7 +271,7 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(UrlQuery),
|
whole_stream_command(UrlQuery),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard-cli")]
|
||||||
{
|
{
|
||||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
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>,
|
pipelines: Vec<String>,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut syncer = crate::EnvironmentSyncer::new();
|
let mut syncer = EnvironmentSyncer::new();
|
||||||
let mut context = create_default_context(&mut syncer, false)?;
|
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")]
|
if let Err(reason) = syncer.autoenv(ctx) {
|
||||||
{
|
print_err(reason, &Text::from(""));
|
||||||
let cc = context.ctrl_c.clone();
|
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
cc.store(true, Ordering::SeqCst);
|
|
||||||
})
|
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
|
|
||||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
|
||||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// before we start up, let's run our startup commands
|
let _ = register_plugins(ctx);
|
||||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
let _ = configure_ctrl_c(ctx);
|
||||||
if let Some(commands) = config.get("startup") {
|
});
|
||||||
match commands {
|
|
||||||
Value {
|
let _ = run_startup_commands(&mut context, &config).await;
|
||||||
value: UntaggedValue::Table(pipelines),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
for pipeline in pipelines {
|
|
||||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
|
||||||
let _ = run_pipeline_standalone(
|
|
||||||
pipeline_string,
|
|
||||||
false,
|
|
||||||
&mut context,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("warning: expected a table of pipeline strings as startup commands");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for pipeline in pipelines {
|
for pipeline in pipelines {
|
||||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
#[cfg(feature = "rustyline-support")]
|
||||||
#[cfg(windows)]
|
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
match input {
|
||||||
#[cfg(not(windows))]
|
Ok(s) => LineResult::Success(s),
|
||||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||||
|
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
Err(err) => {
|
||||||
let mut rl: Editor<_> = Editor::with_config(config);
|
outln!("Error: {:?}", err);
|
||||||
|
LineResult::Break
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"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.
|
/// 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(
|
#[cfg(feature = "rustyline-support")]
|
||||||
mut syncer: EnvironmentSyncer,
|
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||||
mut context: Context,
|
let mut syncer = EnvironmentSyncer::new();
|
||||||
) -> Result<(), Box<dyn Error>> {
|
let configuration = syncer.get_config();
|
||||||
let configuration = nu_data::config::NuConfig::new();
|
|
||||||
|
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 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 _ = rl.load_history(&history_path);
|
||||||
|
|
||||||
let skip_welcome_message = config
|
let skip_welcome_message = configuration
|
||||||
.get("skip_welcome_message")
|
.var("skip_welcome_message")
|
||||||
.map(|x| x.is_true())
|
.map(|x| x.is_true())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !skip_welcome_message {
|
if !skip_welcome_message {
|
||||||
@ -587,44 +368,8 @@ pub async fn cli(
|
|||||||
let _ = ansi_term::enable_ansi_support();
|
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;
|
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 {
|
loop {
|
||||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||||
@ -633,12 +378,8 @@ pub async fn cli(
|
|||||||
|
|
||||||
let cwd = context.shell_manager.path();
|
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 = {
|
let colored_prompt = {
|
||||||
if let Some(prompt) = config.get("prompt") {
|
if let Some(prompt) = configuration.var("prompt") {
|
||||||
let prompt_line = prompt.as_string()?;
|
let prompt_line = prompt.as_string()?;
|
||||||
|
|
||||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||||
@ -695,6 +436,7 @@ pub async fn cli(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
use crate::git::current_branch;
|
||||||
format!(
|
format!(
|
||||||
"\x1b[32m{}{}\x1b[m> ",
|
"\x1b[32m{}{}\x1b[m> ",
|
||||||
cwd,
|
cwd,
|
||||||
@ -722,14 +464,28 @@ pub async fn cli(
|
|||||||
initial_command = None;
|
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
|
// 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
|
// 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
|
// 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.reload();
|
||||||
syncer.sync_env_vars(&mut context);
|
syncer.sync_env_vars(ctx);
|
||||||
syncer.sync_path_vars(&mut context);
|
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 {
|
match line {
|
||||||
LineResult::Success(line) => {
|
LineResult::Success(line) => {
|
||||||
@ -752,7 +508,7 @@ pub async fn cli(
|
|||||||
LineResult::CtrlC => {
|
LineResult::CtrlC => {
|
||||||
let config_ctrlc_exit = config::config(Tag::unknown())?
|
let config_ctrlc_exit = config::config(Tag::unknown())?
|
||||||
.get("ctrlc_exit")
|
.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
|
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||||
|
|
||||||
if !config_ctrlc_exit {
|
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 => {
|
LineResult::Break => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -782,15 +545,283 @@ pub async fn cli(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||||
// Show hints unless explicitly disabled in config
|
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
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() {
|
for (idx, value) in line_editor_vars.row_entries() {
|
||||||
if idx == "show_hints" && value.expect_string() == "false" {
|
if idx == "show_hints" && value.expect_string() == "false" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(rustyline::hint::HistoryHinter {})
|
Some(rustyline::hint::HistoryHinter {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,11 +837,12 @@ fn chomp_newline(s: &str) -> &str {
|
|||||||
pub enum LineResult {
|
pub enum LineResult {
|
||||||
Success(String),
|
Success(String),
|
||||||
Error(String, ShellError),
|
Error(String, ShellError),
|
||||||
CtrlC,
|
|
||||||
Break,
|
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') {
|
let line = if line.ends_with('\n') {
|
||||||
&line[..line.len() - 1]
|
&line[..line.len() - 1]
|
||||||
} else {
|
} 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
|
/// 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(
|
pub async fn process_line(
|
||||||
readline: Result<String, ReadlineError>,
|
line: &str,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
cli_mode: bool,
|
cli_mode: bool,
|
||||||
) -> LineResult {
|
) -> LineResult {
|
||||||
match &readline {
|
if line.trim() == "" {
|
||||||
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
|
LineResult::Success(line.to_string())
|
||||||
|
} else {
|
||||||
Ok(line) => {
|
|
||||||
let line = chomp_newline(line);
|
let line = chomp_newline(line);
|
||||||
ctx.raw_input = line.to_string();
|
ctx.raw_input = line.to_string();
|
||||||
|
|
||||||
@ -1038,13 +1069,6 @@ pub async fn process_line(
|
|||||||
Err(err) => LineResult::Error(line.to_string(), err),
|
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) {
|
pub fn print_err(err: ShellError, source: &Text) {
|
||||||
@ -1069,7 +1093,7 @@ mod tests {
|
|||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn quickcheck_parse(data: String) -> bool {
|
fn quickcheck_parse(data: String) -> bool {
|
||||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
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());
|
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||||
}
|
}
|
||||||
true
|
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 cd;
|
||||||
pub(crate) mod char_;
|
pub(crate) mod char_;
|
||||||
pub(crate) mod classified;
|
pub(crate) mod classified;
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard-cli")]
|
||||||
pub(crate) mod clip;
|
pub(crate) mod clip;
|
||||||
pub(crate) mod command;
|
pub(crate) mod command;
|
||||||
pub(crate) mod compact;
|
pub(crate) mod compact;
|
||||||
@ -36,6 +36,7 @@ pub(crate) mod each;
|
|||||||
pub(crate) mod echo;
|
pub(crate) mod echo;
|
||||||
pub(crate) mod enter;
|
pub(crate) mod enter;
|
||||||
pub(crate) mod every;
|
pub(crate) mod every;
|
||||||
|
pub(crate) mod exec;
|
||||||
pub(crate) mod exit;
|
pub(crate) mod exit;
|
||||||
pub(crate) mod first;
|
pub(crate) mod first;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
@ -75,6 +76,7 @@ pub(crate) mod mkdir;
|
|||||||
pub(crate) mod move_;
|
pub(crate) mod move_;
|
||||||
pub(crate) mod next;
|
pub(crate) mod next;
|
||||||
pub(crate) mod nth;
|
pub(crate) mod nth;
|
||||||
|
pub(crate) mod nu;
|
||||||
pub(crate) mod open;
|
pub(crate) mod open;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
@ -114,7 +116,6 @@ pub(crate) mod to_tsv;
|
|||||||
pub(crate) mod to_url;
|
pub(crate) mod to_url;
|
||||||
pub(crate) mod to_xml;
|
pub(crate) mod to_xml;
|
||||||
pub(crate) mod to_yaml;
|
pub(crate) mod to_yaml;
|
||||||
pub(crate) mod trim;
|
|
||||||
pub(crate) mod uniq;
|
pub(crate) mod uniq;
|
||||||
pub(crate) mod update;
|
pub(crate) mod update;
|
||||||
pub(crate) mod url_;
|
pub(crate) mod url_;
|
||||||
@ -154,9 +155,12 @@ pub(crate) use do_::Do;
|
|||||||
pub(crate) use drop::Drop;
|
pub(crate) use drop::Drop;
|
||||||
pub(crate) use du::Du;
|
pub(crate) use du::Du;
|
||||||
pub(crate) use each::Each;
|
pub(crate) use each::Each;
|
||||||
|
pub(crate) use each::EachGroup;
|
||||||
|
pub(crate) use each::EachWindow;
|
||||||
pub(crate) use echo::Echo;
|
pub(crate) use echo::Echo;
|
||||||
pub(crate) use if_::If;
|
pub(crate) use if_::If;
|
||||||
pub(crate) use is_empty::IsEmpty;
|
pub(crate) use is_empty::IsEmpty;
|
||||||
|
pub(crate) use nu::NuPlugin;
|
||||||
pub(crate) use update::Update;
|
pub(crate) use update::Update;
|
||||||
pub(crate) mod kill;
|
pub(crate) mod kill;
|
||||||
pub(crate) use kill::Kill;
|
pub(crate) use kill::Kill;
|
||||||
@ -165,6 +169,7 @@ pub(crate) use clear::Clear;
|
|||||||
pub(crate) mod touch;
|
pub(crate) mod touch;
|
||||||
pub(crate) use enter::Enter;
|
pub(crate) use enter::Enter;
|
||||||
pub(crate) use every::Every;
|
pub(crate) use every::Every;
|
||||||
|
pub(crate) use exec::Exec;
|
||||||
pub(crate) use exit::Exit;
|
pub(crate) use exit::Exit;
|
||||||
pub(crate) use first::First;
|
pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use format::Format;
|
||||||
@ -218,7 +223,7 @@ pub(crate) use prev::Previous;
|
|||||||
pub(crate) use pwd::Pwd;
|
pub(crate) use pwd::Pwd;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
pub(crate) use random::RandomUUID;
|
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 range::Range;
|
||||||
pub(crate) use reduce::Reduce;
|
pub(crate) use reduce::Reduce;
|
||||||
pub(crate) use reject::Reject;
|
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_xml::ToXML;
|
||||||
pub(crate) use to_yaml::ToYAML;
|
pub(crate) use to_yaml::ToYAML;
|
||||||
pub(crate) use touch::Touch;
|
pub(crate) use touch::Touch;
|
||||||
pub(crate) use trim::Trim;
|
|
||||||
pub(crate) use uniq::Uniq;
|
pub(crate) use uniq::Uniq;
|
||||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||||
pub(crate) use version::Version;
|
pub(crate) use version::Version;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::config;
|
use nu_data::config;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -37,7 +37,7 @@ pub trait ConfigExtensions: Debug + Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
if let Some(mode) = vars.get("pivot_mode") {
|
if let Some(mode) = vars.get("pivot_mode") {
|
||||||
let mode = match mode.as_string() {
|
let mode = match mode.as_string() {
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
#[cfg(feature = "rich-benchmark")]
|
||||||
|
use heim::cpu::time;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
|
||||||
|
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
|
use std::convert::TryInto;
|
||||||
use chrono::prelude::*;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub struct Benchmark;
|
pub struct Benchmark;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct BenchmarkArgs {
|
struct BenchmarkArgs {
|
||||||
block: Block,
|
block: Block,
|
||||||
|
passthrough: Option<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -22,15 +26,22 @@ impl WholeStreamCommand for Benchmark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("benchmark").required(
|
Signature::build("benchmark")
|
||||||
|
.required(
|
||||||
"block",
|
"block",
|
||||||
SyntaxShape::Block,
|
SyntaxShape::Block,
|
||||||
"the block to run and benchmark",
|
"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 {
|
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(
|
async fn run(
|
||||||
@ -40,6 +51,21 @@ impl WholeStreamCommand for Benchmark {
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
benchmark(args, registry).await
|
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(
|
async fn benchmark(
|
||||||
@ -48,11 +74,15 @@ async fn benchmark(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
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 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(
|
let result = run_block(
|
||||||
&block,
|
&block,
|
||||||
@ -63,16 +93,110 @@ async fn benchmark(
|
|||||||
&scope.env,
|
&scope.env,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
let output = result?.into_vec().await;
|
||||||
|
|
||||||
let _ = result?.drain_vec().await;
|
#[cfg(feature = "rich-benchmark")]
|
||||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
let end = time().await;
|
||||||
|
|
||||||
|
let end_time = Instant::now();
|
||||||
context.clear_errors();
|
context.clear_errors();
|
||||||
|
|
||||||
let output = Ok(ReturnSuccess::Value(Value {
|
// return basic runtime
|
||||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
#[cfg(not(feature = "rich-benchmark"))]
|
||||||
tag: Tag::from(block.span),
|
{
|
||||||
}));
|
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()), // ⚡
|
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => 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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::commands::classified::expr::run_expression_block;
|
use crate::commands::classified::expr::run_expression_block;
|
||||||
use crate::commands::classified::internal::run_internal_command;
|
use crate::commands::classified::internal::run_internal_command;
|
||||||
use crate::context::Context;
|
use crate::evaluation_context::EvaluationContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::stream::InputStream;
|
use crate::stream::InputStream;
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
@ -11,7 +11,7 @@ use std::sync::atomic::Ordering;
|
|||||||
|
|
||||||
pub(crate) async fn run_block(
|
pub(crate) async fn run_block(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
it: &Value,
|
||||||
vars: &IndexMap<String, Value>,
|
vars: &IndexMap<String, Value>,
|
||||||
@ -64,7 +64,7 @@ pub(crate) async fn run_block(
|
|||||||
|
|
||||||
async fn run_pipeline(
|
async fn run_pipeline(
|
||||||
commands: &Commands,
|
commands: &Commands,
|
||||||
ctx: &mut Context,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
it: &Value,
|
it: &Value,
|
||||||
vars: &IndexMap<String, Value>,
|
vars: &IndexMap<String, Value>,
|
||||||
|
@ -10,7 +10,7 @@ use nu_protocol::Value;
|
|||||||
|
|
||||||
pub(crate) async fn run_expression_block(
|
pub(crate) async fn run_expression_block(
|
||||||
expr: SpannedExpression,
|
expr: SpannedExpression,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
it: &Value,
|
it: &Value,
|
||||||
vars: &IndexMap<String, Value>,
|
vars: &IndexMap<String, Value>,
|
||||||
env: &IndexMap<String, String>,
|
env: &IndexMap<String, String>,
|
||||||
|
@ -19,7 +19,7 @@ use nu_source::Tag;
|
|||||||
|
|
||||||
pub(crate) async fn run_external_command(
|
pub(crate) async fn run_external_command(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
external_redirection: ExternalRedirection,
|
external_redirection: ExternalRedirection,
|
||||||
@ -39,7 +39,7 @@ pub(crate) async fn run_external_command(
|
|||||||
|
|
||||||
async fn run_with_stdin(
|
async fn run_with_stdin(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
external_redirection: ExternalRedirection,
|
external_redirection: ExternalRedirection,
|
||||||
@ -543,7 +543,7 @@ mod tests {
|
|||||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use super::{run_external_command, Context, InputStream};
|
use super::{run_external_command, EvaluationContext, InputStream};
|
||||||
|
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
@ -573,7 +573,8 @@ mod tests {
|
|||||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||||
|
|
||||||
let input = InputStream::empty();
|
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(
|
assert!(run_external_command(
|
||||||
cmd,
|
cmd,
|
||||||
@ -591,7 +592,7 @@ mod tests {
|
|||||||
// async fn failure_run() -> Result<(), ShellError> {
|
// async fn failure_run() -> Result<(), ShellError> {
|
||||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
// 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)
|
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||||
// .await?
|
// .await?
|
||||||
// .expect("There was a problem running the external command.");
|
// .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(
|
pub(crate) async fn run_internal_command(
|
||||||
command: InternalCommand,
|
command: InternalCommand,
|
||||||
context: &mut Context,
|
context: &mut EvaluationContext,
|
||||||
input: InputStream,
|
input: InputStream,
|
||||||
it: &Value,
|
it: &Value,
|
||||||
vars: &IndexMap<String, Value>,
|
vars: &IndexMap<String, Value>,
|
||||||
@ -200,6 +200,28 @@ pub(crate) async fn run_internal_command(
|
|||||||
)]);
|
)]);
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
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 => {
|
CommandAction::PreviousShell => {
|
||||||
context.shell_manager.prev();
|
context.shell_manager.prev();
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"clears the terminal"
|
"Clears the terminal"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::help::get_help;
|
use crate::commands::help::get_help;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::deserializer::ConfigDeserializer;
|
use crate::deserializer::ConfigDeserializer;
|
||||||
use crate::evaluate::evaluate_args::evaluate_args;
|
use crate::evaluate::evaluate_args::evaluate_args;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -303,6 +303,11 @@ pub trait WholeStreamCommand: Send + Sync {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands that are not meant to be run by users
|
||||||
|
fn is_internal(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@ -367,6 +372,10 @@ impl Command {
|
|||||||
self.0.is_binary()
|
self.0.is_binary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_internal(&self) -> bool {
|
||||||
|
self.0.is_internal()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||||
&*self.0
|
&*self.0
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Work with dates."
|
"Apply date function"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -65,7 +65,7 @@ async fn do_(
|
|||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
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 scope = raw_args.call_info.scope.clone();
|
||||||
let (
|
let (
|
||||||
DoArgs {
|
DoArgs {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
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::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
@ -84,7 +84,7 @@ pub async fn process_row(
|
|||||||
block: Arc<Block>,
|
block: Arc<Block>,
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
head: Arc<Box<SpannedExpression>>,
|
head: Arc<Box<SpannedExpression>>,
|
||||||
mut context: Arc<Context>,
|
mut context: Arc<EvaluationContext>,
|
||||||
input: Value,
|
input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let input_clone = input.clone();
|
let input_clone = input.clone();
|
||||||
@ -120,7 +120,7 @@ async fn each(
|
|||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||||
let scope = Arc::new(raw_args.call_info.scope.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 (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||||
let block = Arc::new(each_args.block);
|
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,
|
end: Primitive,
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
is_end_inclusive: bool,
|
is_end_inclusive: bool,
|
||||||
is_done: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RangeIterator {
|
impl RangeIterator {
|
||||||
pub fn new(range: Range, tag: Tag) -> 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 {
|
RangeIterator {
|
||||||
curr: range.from.0.item,
|
curr: start,
|
||||||
end: range.to.0.item,
|
end: range.to.0.item,
|
||||||
tag,
|
tag,
|
||||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||||
is_done: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,14 +102,40 @@ impl RangeIterator {
|
|||||||
impl Iterator for RangeIterator {
|
impl Iterator for RangeIterator {
|
||||||
type Item = Result<ReturnSuccess, ShellError>;
|
type Item = Result<ReturnSuccess, ShellError>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
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());
|
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,
|
Operator::Plus,
|
||||||
&UntaggedValue::Primitive(self.curr.clone()),
|
&UntaggedValue::Primitive(self.curr.clone()),
|
||||||
&UntaggedValue::int(1),
|
&UntaggedValue::int(1),
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
self.curr = match next_value {
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
UntaggedValue::Primitive(p) => p,
|
UntaggedValue::Primitive(p) => p,
|
||||||
_ => {
|
_ => {
|
||||||
@ -123,11 +152,6 @@ impl Iterator for RangeIterator {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(ReturnSuccess::value(output))
|
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 {
|
} else {
|
||||||
// TODO: add inclusive/exclusive ranges
|
// TODO: add inclusive/exclusive ranges
|
||||||
None
|
None
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::UnevaluatedCallInfo;
|
use crate::commands::UnevaluatedCallInfo;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::ExternalRedirection;
|
use nu_protocol::hir::ExternalRedirection;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
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::commands::command::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
@ -14,6 +14,7 @@ fn from_delimited_string_to_value(
|
|||||||
.delimiter(separator as u8)
|
.delimiter(separator as u8)
|
||||||
.from_reader(s.as_bytes());
|
.from_reader(s.as_bytes());
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
let headers = if headerless {
|
let headers = if headerless {
|
||||||
(1..=reader.headers()?.len())
|
(1..=reader.headers()?.len())
|
||||||
@ -30,7 +31,10 @@ fn from_delimited_string_to_value(
|
|||||||
if let Ok(i) = value.parse::<i64>() {
|
if let Ok(i) = value.parse::<i64>() {
|
||||||
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
tagged_row.insert_value(header, UntaggedValue::int(i).into_value(&tag))
|
||||||
} else if let Ok(f) = value.parse::<f64>() {
|
} 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 {
|
} else {
|
||||||
tagged_row.insert_value(header, UntaggedValue::string(value).into_value(&tag))
|
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 {
|
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
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::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::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::String(s) => {
|
serde_hjson::Value::String(s) => {
|
||||||
|
@ -45,6 +45,7 @@ async fn from_ods(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let span = tag.span;
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
@ -73,7 +74,7 @@ async fn from_ods(
|
|||||||
let value = match cell {
|
let value = match cell {
|
||||||
DataType::Empty => UntaggedValue::nothing(),
|
DataType::Empty => UntaggedValue::nothing(),
|
||||||
DataType::String(s) => UntaggedValue::string(s),
|
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::Int(i) => UntaggedValue::int(*i),
|
||||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||||
_ => UntaggedValue::nothing(),
|
_ => 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 {
|
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||||
toml::Value::Integer(n) => UntaggedValue::int(*n).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) => {
|
toml::Value::String(s) => {
|
||||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ async fn from_xlsx(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let span = tag.span;
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (
|
let (
|
||||||
FromXLSXArgs {
|
FromXLSXArgs {
|
||||||
@ -73,7 +74,7 @@ async fn from_xlsx(
|
|||||||
let value = match cell {
|
let value = match cell {
|
||||||
DataType::Empty => UntaggedValue::nothing(),
|
DataType::Empty => UntaggedValue::nothing(),
|
||||||
DataType::String(s) => UntaggedValue::string(s),
|
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::Int(i) => UntaggedValue::int(*i),
|
||||||
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
DataType::Bool(b) => UntaggedValue::boolean(*b),
|
||||||
_ => UntaggedValue::nothing(),
|
_ => UntaggedValue::nothing(),
|
||||||
|
@ -58,6 +58,7 @@ fn convert_yaml_value_to_nu_value(
|
|||||||
tag: impl Into<Tag>,
|
tag: impl Into<Tag>,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
let span = tag.span;
|
||||||
|
|
||||||
let err_not_compatible_number = ShellError::labeled_error(
|
let err_not_compatible_number = ShellError::labeled_error(
|
||||||
"Expected a compatible number",
|
"Expected a compatible number",
|
||||||
@ -69,10 +70,11 @@ fn convert_yaml_value_to_nu_value(
|
|||||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||||
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
|
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||||
.into_value(tag)
|
span,
|
||||||
}
|
)
|
||||||
|
.into_value(tag),
|
||||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||||
serde_yaml::Value::Sequence(a) => {
|
serde_yaml::Value::Sequence(a) => {
|
||||||
let result: Result<Vec<Value>, ShellError> = a
|
let result: Result<Vec<Value>, ShellError> = a
|
||||||
|
@ -4,8 +4,8 @@ use indexmap::set::IndexSet;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
did_you_mean, ColumnPath, Dictionary, PathMember, Primitive, ReturnSuccess, Signature,
|
||||||
UnspannedPathMember, UntaggedValue, Value,
|
SyntaxShape, UnspannedPathMember, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::HasFallibleSpan;
|
use nu_source::HasFallibleSpan;
|
||||||
use nu_value_ext::get_data_by_column_path;
|
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> {
|
pub async fn get(
|
||||||
let fields = path.clone();
|
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(
|
let descs = nu_protocol::merge_descriptors(&vec);
|
||||||
obj,
|
|
||||||
path,
|
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
} else {
|
||||||
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
|
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 {
|
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 {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::String(column),
|
unspanned: UnspannedPathMember::String(column),
|
||||||
..
|
..
|
||||||
@ -93,15 +171,15 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
return ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
"Appears to contain rows. Try indexing instead.",
|
"Appears to contain rows. Try indexing instead.",
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
return ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
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())
|
.map(|x| x.to_owned())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(","),
|
.join(","),
|
||||||
names.join(",")
|
names.join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
PathMember {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::Int(idx),
|
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)
|
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",
|
"Row not found",
|
||||||
format!("There isn't a row indexed at {}", idx),
|
format!("There isn't a row indexed at {}", idx),
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
secondary_label,
|
secondary_label,
|
||||||
column_path_tried.span.since(path_members_span),
|
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 {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::String(column),
|
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);
|
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||||
|
|
||||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
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",
|
"Unknown column",
|
||||||
primary_label,
|
primary_label,
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
format!(
|
format!(
|
||||||
"Perhaps you meant '{}'? Columns available: {}",
|
"Perhaps you meant '{}'? Columns available: {}",
|
||||||
suggestions[0].1,
|
suggestions[0].1,
|
||||||
&obj_source.data_descriptors().join(",")
|
&obj_source.data_descriptors().join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
column_path_tried.span.since(path_members_span),
|
||||||
);
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PathMember {
|
PathMember {
|
||||||
unspanned: UnspannedPathMember::Int(idx),
|
unspanned: UnspannedPathMember::Int(idx),
|
||||||
..
|
..
|
||||||
} => {
|
} => Some(ShellError::labeled_error_with_secondary(
|
||||||
return ShellError::labeled_error_with_secondary(
|
|
||||||
"No rows available",
|
"No rows available",
|
||||||
format!("A row at '{}' can't be indexed.", &idx),
|
format!("A row at '{}' can't be indexed.", &idx),
|
||||||
column_path_tried.span,
|
column_path_tried.span,
|
||||||
format!(
|
format!(
|
||||||
"Appears to contain columns. Columns available: {}",
|
"Appears to contain columns. Columns available: {}",
|
||||||
columns.keys().join(",")
|
columns.keys().join(", ")
|
||||||
),
|
),
|
||||||
column_path_tried.span.since(path_members_span),
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -28,7 +28,7 @@ impl WholeStreamCommand for GroupBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"create a new table grouped."
|
"Create a new table grouped."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -96,7 +96,7 @@ pub async fn group_by(
|
|||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(args.call_info.args.head.clone());
|
let head = Arc::new(args.call_info.args.head.clone());
|
||||||
let scope = Arc::new(args.call_info.scope.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 (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
let values: Vec<Value> = input.collect().await;
|
let values: Vec<Value> = input.collect().await;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -63,54 +63,114 @@ async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
let mut sorted_names = registry.names();
|
let mut sorted_names = registry.names();
|
||||||
sorted_names.sort();
|
sorted_names.sort();
|
||||||
|
|
||||||
Ok(
|
let (mut subcommand_names, command_names) = sorted_names
|
||||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
.into_iter()
|
||||||
// If it's a subcommand, don't list it during the commands list
|
// Internal only commands shouldn't be displayed
|
||||||
if cmd.contains(' ') {
|
.filter(|cmd_name| {
|
||||||
return None;
|
registry
|
||||||
}
|
.get_command(&cmd_name)
|
||||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
.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 document_tag = rest[0].tag.clone();
|
||||||
let value = command_dict(
|
let value = command_dict(
|
||||||
match registry.get_command(&cmd).ok_or_else(|| {
|
registry.get_command(&cmd_name).ok_or_else(|| {
|
||||||
ShellError::labeled_error(
|
ShellError::labeled_error(
|
||||||
format!("Could not load {}", cmd),
|
format!("Could not load {}", cmd_name),
|
||||||
"could not load command",
|
"could not load command",
|
||||||
document_tag,
|
document_tag,
|
||||||
)
|
)
|
||||||
}) {
|
})?,
|
||||||
Ok(ok) => ok,
|
name,
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
},
|
|
||||||
name.clone(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
short_desc.insert_untagged("name", cmd);
|
dict.insert_untagged("name", cmd_name);
|
||||||
short_desc.insert_untagged(
|
dict.insert_untagged(
|
||||||
"description",
|
"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(
|
ShellError::labeled_error(
|
||||||
"Expected a usage key",
|
"Expected a usage key",
|
||||||
"expected a 'usage' key",
|
"expected a 'usage' key",
|
||||||
&value.tag,
|
&value.tag,
|
||||||
)
|
)
|
||||||
},
|
})?
|
||||||
) {
|
.as_string()?,
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
}
|
|
||||||
.as_string()
|
|
||||||
{
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
Ok(())
|
||||||
}))
|
}
|
||||||
.to_output_stream(),
|
|
||||||
|
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" {
|
} else if rest[0].item == "generate_docs" {
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||||
®istry,
|
®istry,
|
||||||
|
@ -178,11 +178,7 @@ fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, Shell
|
|||||||
Box::new(move |_: usize, value: &Value| {
|
Box::new(move |_: usize, value: &Value| {
|
||||||
let path = by.clone();
|
let path = by.clone();
|
||||||
|
|
||||||
let eval = nu_value_ext::get_data_by_column_path(
|
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||||
value,
|
|
||||||
&path,
|
|
||||||
Box::new(move |(_, _, error)| error),
|
|
||||||
);
|
|
||||||
|
|
||||||
match eval {
|
match eval {
|
||||||
Ok(with_value) => Ok(with_value),
|
Ok(with_value) => Ok(with_value),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::config::NuConfig;
|
use nu_data::config::{Conf, NuConfig};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -9,9 +9,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
const DEFAULT_LOCATION: &str = "history.txt";
|
const DEFAULT_LOCATION: &str = "history.txt";
|
||||||
|
|
||||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||||
let vars = config.vars.lock();
|
|
||||||
|
|
||||||
let default_path = nu_data::config::user_data()
|
let default_path = nu_data::config::user_data()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
p.push(DEFAULT_LOCATION);
|
p.push(DEFAULT_LOCATION);
|
||||||
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||||
|
|
||||||
vars.get("history-path")
|
config
|
||||||
|
.var("history-path")
|
||||||
.map_or(default_path.clone(), |custom_path| {
|
.map_or(default_path.clone(), |custom_path| {
|
||||||
match custom_path.as_string() {
|
match custom_path.as_string() {
|
||||||
Ok(path) => PathBuf::from(path),
|
Ok(path) => PathBuf::from(path),
|
||||||
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
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 tag = args.call_info.name_tag;
|
||||||
let path = history_path(&config);
|
let path = history_path(&config);
|
||||||
let file = File::open(path);
|
let file = File::open(path);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::evaluate::evaluate_baseline_expr;
|
use crate::evaluate::evaluate_baseline_expr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
@ -74,7 +74,7 @@ async fn if_command(
|
|||||||
let registry = Arc::new(registry.clone());
|
let registry = Arc::new(registry.clone());
|
||||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||||
let tag = raw_args.call_info.name_tag.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 (
|
let (
|
||||||
IfArgs {
|
IfArgs {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -48,7 +48,7 @@ impl WholeStreamCommand for Insert {
|
|||||||
|
|
||||||
async fn process_row(
|
async fn process_row(
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
mut context: Arc<Context>,
|
mut context: Arc<EvaluationContext>,
|
||||||
input: Value,
|
input: Value,
|
||||||
mut value: Arc<Value>,
|
mut value: Arc<Value>,
|
||||||
field: Arc<ColumnPath>,
|
field: Arc<ColumnPath>,
|
||||||
@ -136,7 +136,7 @@ async fn insert(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Arc::new(raw_args.call_info.scope.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 (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
||||||
let value = Arc::new(value);
|
let value = Arc::new(value);
|
||||||
let column = Arc::new(column);
|
let column = Arc::new(column);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
@ -50,9 +50,16 @@ async fn is_empty(
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name_tag = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
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
|
Ok(input
|
||||||
.map(move |value| {
|
.map(move |value| {
|
||||||
let value_tag = value.tag();
|
let value_tag = value.tag();
|
||||||
@ -72,7 +79,6 @@ async fn is_empty(
|
|||||||
(_, _) => IsEmptyFor::Value,
|
(_, _) => IsEmptyFor::Value,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// let no_args = vec![];
|
|
||||||
let mut arguments = rest.iter().rev();
|
let mut arguments = rest.iter().rev();
|
||||||
let replacement_if_true = match arguments.next() {
|
let replacement_if_true = match arguments.next() {
|
||||||
Some(arg) => arg.clone(),
|
Some(arg) => arg.clone(),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
@ -53,7 +53,11 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Get the average of a list of numbers",
|
description: "Get the average of a list of numbers",
|
||||||
example: "echo [-50 100.0 25] | math avg",
|
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 {
|
||||||
value: UntaggedValue::Primitive(other),
|
value: UntaggedValue::Primitive(other),
|
||||||
..
|
..
|
||||||
|
@ -39,7 +39,7 @@ mod tests {
|
|||||||
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
||||||
};
|
};
|
||||||
use nu_plugin::row;
|
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 nu_protocol::Value;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -98,17 +98,17 @@ mod tests {
|
|||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
description: "Mixed Values",
|
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_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(21)),
|
Ok(decimal(21)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(decimal(26.5)),
|
Ok(decimal_from_float(26.5)),
|
||||||
Ok(decimal(26.5)),
|
Ok(decimal_from_float(26.5)),
|
||||||
Ok(table(&[decimal(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(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
||||||
Ok(decimal(63)),
|
Ok(decimal(63)),
|
||||||
Ok(decimal(60.5)),
|
Ok(decimal_from_float(60.5)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
@ -128,14 +128,14 @@ mod tests {
|
|||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
description: "Mixed Negative Values",
|
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_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(-5)),
|
Ok(decimal(-5)),
|
||||||
Ok(decimal(-13.5)),
|
Ok(decimal_from_float(-13.5)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(decimal(-11.5)),
|
Ok(decimal_from_float(-11.5)),
|
||||||
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
|
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(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
||||||
Ok(decimal(-15)),
|
Ok(decimal(-15)),
|
||||||
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
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_err: None,
|
||||||
expected_res: vec![
|
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(1), "col2".to_owned() => int(5)]),
|
||||||
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
|
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![
|
Ok(row![
|
||||||
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
||||||
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
|
"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"))
|
"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() => 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
|
// TODO-Uncomment once Issue: https://github.com/nushell/nushell/issues/1883 is resolved
|
||||||
|
@ -41,7 +41,11 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Evalulate math in the pipeline",
|
description: "Evalulate math in the pipeline",
|
||||||
example: "echo '10 / 4' | math eval",
|
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 {
|
vec![Example {
|
||||||
description: "Get the median of a list of numbers",
|
description: "Get the median of a list of numbers",
|
||||||
example: "echo [3 8 9 12 12 15] | math median",
|
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,
|
&name.span,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
v if v.is_duration() => sum(UntaggedValue::int(0).into_untagged_value(), values.to_vec()),
|
||||||
// v is nothing primitive
|
// v is nothing primitive
|
||||||
v if v.is_none() => sum(
|
v if v.is_none() => sum(
|
||||||
UntaggedValue::int(0).into_untagged_value(),
|
UntaggedValue::int(0).into_untagged_value(),
|
||||||
|
@ -99,12 +99,20 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Get the variance of a list of numbers",
|
description: "Get the variance of a list of numbers",
|
||||||
example: "echo [1 2 3 4 5] | math variance",
|
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 {
|
Example {
|
||||||
description: "Get the sample variance of a list of numbers",
|
description: "Get the sample variance of a list of numbers",
|
||||||
example: "echo [1 2 3 4 5] | math variance -s",
|
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::classified::block::run_block;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::value::merge_values;
|
use nu_data::value::merge_values;
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ async fn merge(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = raw_args.call_info.scope.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 name_tag = raw_args.call_info.name_tag.clone();
|
||||||
let (merge_args, input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
let (merge_args, input): (MergeArgs, _) = raw_args.process(®istry).await?;
|
||||||
let block = merge_args.block;
|
let block = merge_args.block;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::base::select_fields;
|
use nu_data::base::select_fields;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"moves across desired subcommand."
|
"Moves across desired subcommand."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
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 bool;
|
||||||
pub mod dice;
|
pub mod dice;
|
||||||
|
pub mod integer;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
pub mod uuid;
|
pub mod uuid;
|
||||||
|
|
||||||
@ -9,5 +10,6 @@ pub use command::Command as Random;
|
|||||||
|
|
||||||
pub use self::bool::SubCommand as RandomBool;
|
pub use self::bool::SubCommand as RandomBool;
|
||||||
pub use dice::SubCommand as RandomDice;
|
pub use dice::SubCommand as RandomDice;
|
||||||
|
pub use integer::SubCommand as RandomInteger;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
pub use uuid::SubCommand as RandomUUID;
|
pub use uuid::SubCommand as RandomUUID;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::deserializer::NumericRange;
|
use crate::deserializer::NumericRange;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
use nu_protocol::{RangeInclusion, ReturnSuccess, Signature, SyntaxShape};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -44,11 +44,23 @@ async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
|||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (RangeArgs { area }, input) = args.process(®istry).await?;
|
let (RangeArgs { area }, input) = args.process(®istry).await?;
|
||||||
let range = area.item;
|
let range = area.item;
|
||||||
let (from, _) = range.from;
|
let (from, left_inclusive) = range.from;
|
||||||
let (to, _) = range.to;
|
let (to, right_inclusive) = range.to;
|
||||||
|
let from = from.map(|from| *from as usize).unwrap_or(0).saturating_add(
|
||||||
let from = *from as usize;
|
if left_inclusive == RangeInclusion::Inclusive {
|
||||||
let to = *to as usize;
|
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
|
Ok(input
|
||||||
.skip(from)
|
.skip(from)
|
||||||
|
@ -81,7 +81,7 @@ impl WholeStreamCommand for Reduce {
|
|||||||
async fn process_row(
|
async fn process_row(
|
||||||
block: Arc<Block>,
|
block: Arc<Block>,
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
mut context: Arc<Context>,
|
mut context: Arc<EvaluationContext>,
|
||||||
row: Value,
|
row: Value,
|
||||||
) -> Result<InputStream, ShellError> {
|
) -> Result<InputStream, ShellError> {
|
||||||
let row_clone = row.clone();
|
let row_clone = row.clone();
|
||||||
@ -104,7 +104,7 @@ async fn reduce(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let base_scope = raw_args.call_info.scope.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 (reduce_args, mut input): (ReduceArgs, _) = raw_args.process(®istry).await?;
|
||||||
let block = Arc::new(reduce_args.block);
|
let block = Arc::new(reduce_args.block);
|
||||||
let (ioffset, start) = match reduce_args.fold {
|
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);
|
let row = each::make_indexed_item(input.0 + ioffset, input.1);
|
||||||
|
|
||||||
async {
|
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);
|
scope.vars.insert(String::from("$acc"), f);
|
||||||
process_row(block, Arc::new(scope), context, row).await
|
process_row(block, Arc::new(scope), context, row).await
|
||||||
}
|
}
|
||||||
@ -154,9 +166,20 @@ async fn reduce(
|
|||||||
let context = Arc::clone(&context);
|
let context = Arc::clone(&context);
|
||||||
|
|
||||||
async {
|
async {
|
||||||
scope
|
let values = acc?.drain_vec().await;
|
||||||
.vars
|
|
||||||
.insert(String::from("$acc"), acc?.into_vec().await[0].clone());
|
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
|
process_row(block, Arc::new(scope), context, row).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
|
@ -44,7 +44,7 @@ impl WholeStreamCommand for AliasCommand {
|
|||||||
block.set_redirect(call_info.args.external_redirection);
|
block.set_redirect(call_info.args.external_redirection);
|
||||||
|
|
||||||
let alias_command = self.clone();
|
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 input = args.input;
|
||||||
|
|
||||||
let mut scope = call_info.scope.clone();
|
let mut scope = call_info.scope.clone();
|
||||||
|
@ -48,7 +48,19 @@ impl WholeStreamCommand for RunExternalCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
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(
|
async fn run(
|
||||||
@ -72,33 +84,16 @@ impl WholeStreamCommand for RunExternalCommand {
|
|||||||
.and_then(spanned_expression_to_string)?;
|
.and_then(spanned_expression_to_string)?;
|
||||||
|
|
||||||
let mut external_context = {
|
let mut external_context = {
|
||||||
#[cfg(windows)]
|
EvaluationContext {
|
||||||
{
|
|
||||||
Context {
|
|
||||||
registry: registry.clone(),
|
registry: registry.clone(),
|
||||||
host: args.host.clone(),
|
host: args.host.clone(),
|
||||||
user_recently_used_autoenv_untrust: false,
|
user_recently_used_autoenv_untrust: false,
|
||||||
shell_manager: args.shell_manager.clone(),
|
shell_manager: args.shell_manager.clone(),
|
||||||
ctrl_c: args.ctrl_c.clone(),
|
ctrl_c: args.ctrl_c.clone(),
|
||||||
current_errors: Arc::new(Mutex::new(vec![])),
|
current_errors: Arc::new(Mutex::new(vec![])),
|
||||||
windows_drives_previous_cwd: Arc::new(Mutex::new(
|
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||||
std::collections::HashMap::new(),
|
|
||||||
)),
|
|
||||||
raw_input: String::default(),
|
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;
|
let is_interactive = self.interactive;
|
||||||
@ -148,7 +143,10 @@ impl WholeStreamCommand for RunExternalCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[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
|
// We will "auto cd" if
|
||||||
// - the command name ends in a path separator, or
|
// - the command name ends in a path separator, or
|
||||||
// - it's not a command on the path and no arguments were given.
|
// - 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::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -83,7 +83,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
|
|||||||
let fetcher = get_data_by_column_path(
|
let fetcher = get_data_by_column_path(
|
||||||
&value,
|
&value,
|
||||||
&path,
|
&path,
|
||||||
Box::new(move |(obj_source, path_member_tried, error)| {
|
move |obj_source, path_member_tried, error| {
|
||||||
if let PathMember {
|
if let PathMember {
|
||||||
unspanned: UnspannedPathMember::String(column),
|
unspanned: UnspannedPathMember::String(column),
|
||||||
..
|
..
|
||||||
@ -98,7 +98,7 @@ async fn select(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputS
|
|||||||
}
|
}
|
||||||
|
|
||||||
error
|
error
|
||||||
}),
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let field = path.clone();
|
let field = path.clone();
|
||||||
|
@ -37,9 +37,9 @@ fn shells(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream
|
|||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
|
||||||
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
|
if index == (*args.shell_manager.current_shell).load(Ordering::SeqCst) {
|
||||||
dict.insert_untagged("active", "X".to_string());
|
dict.insert_untagged("active", true);
|
||||||
} else {
|
} else {
|
||||||
dict.insert_untagged("active", " ".to_string());
|
dict.insert_untagged("active", false);
|
||||||
}
|
}
|
||||||
dict.insert_untagged("name", shell.name());
|
dict.insert_untagged("name", shell.name());
|
||||||
dict.insert_untagged("path", shell.path());
|
dict.insert_untagged("path", shell.path());
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Value};
|
use nu_protocol::{ReturnSuccess, Value};
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
extern crate unicode_segmentation;
|
||||||
|
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use indexmap::indexmap;
|
use indexmap::indexmap;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
pub struct Size;
|
pub struct Size;
|
||||||
|
|
||||||
@ -29,7 +32,8 @@ impl WholeStreamCommand for Size {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Count the number of words in a string",
|
description: "Count the number of words in a string",
|
||||||
example: r#"echo "There are seven words in this sentence" | size"#,
|
example: r#"echo "There are seven words in this sentence" | size"#,
|
||||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||||
@ -39,7 +43,19 @@ impl WholeStreamCommand for Size {
|
|||||||
"bytes".to_string() => UntaggedValue::int(38).into(),
|
"bytes".to_string() => UntaggedValue::int(38).into(),
|
||||||
})
|
})
|
||||||
.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 bytes = contents.len() as i64;
|
||||||
let mut end_of_word = true;
|
let mut end_of_word = true;
|
||||||
|
|
||||||
for c in contents.chars() {
|
for c in UnicodeSegmentation::graphemes(contents, true) {
|
||||||
chars += 1;
|
chars += 1;
|
||||||
|
|
||||||
match c {
|
match c {
|
||||||
'\n' => {
|
"\n" => {
|
||||||
lines += 1;
|
lines += 1;
|
||||||
end_of_word = true;
|
end_of_word = true;
|
||||||
}
|
}
|
||||||
' ' => end_of_word = true,
|
" " => end_of_word = true,
|
||||||
_ => {
|
_ => {
|
||||||
if end_of_word {
|
if end_of_word {
|
||||||
words += 1;
|
words += 1;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::context::CommandRegistry;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||||
use nu_source::Tagged;
|
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;
|
pub struct Sleep;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SleepArgs {
|
pub struct SleepArgs {
|
||||||
pub dur: Tagged<u64>,
|
pub duration: Tagged<u64>,
|
||||||
pub rest: Vec<Tagged<u64>>,
|
pub rest: Vec<Tagged<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +37,7 @@ impl WholeStreamCommand for Sleep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"delay for a specified amount of time"
|
"Delay for a specified amount of time"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -36,7 +45,26 @@ impl WholeStreamCommand for Sleep {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> 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> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -51,20 +79,100 @@ impl WholeStreamCommand for Sleep {
|
|||||||
example: "sleep 1sec 1sec 1sec",
|
example: "sleep 1sec 1sec 1sec",
|
||||||
result: None,
|
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> {
|
struct SleepFuture {
|
||||||
let registry = registry.clone();
|
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>();
|
// Spawn the main sleep thread
|
||||||
let total_dur = time::Duration::from_nanos(total_dur);
|
let thread_shared_state = shared_state.clone();
|
||||||
thread::sleep(total_dur);
|
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)]
|
#[cfg(test)]
|
||||||
@ -73,7 +181,6 @@ mod tests {
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn examples_work_as_expected() {
|
fn examples_work_as_expected() {
|
||||||
use crate::examples::test as test_examples;
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
@ -81,6 +188,8 @@ mod tests {
|
|||||||
test_examples(Sleep {});
|
test_examples(Sleep {});
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
println!("{:?}", 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 {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_plugin::test_helpers::value::string;
|
||||||
use nu_protocol::{Primitive, UntaggedValue};
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -145,8 +145,7 @@ mod tests {
|
|||||||
let word = string("Cargo.tomL");
|
let word = string("Cargo.tomL");
|
||||||
let pattern = ".tomL";
|
let pattern = ".tomL";
|
||||||
let insensitive = false;
|
let insensitive = false;
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@ -157,8 +156,7 @@ mod tests {
|
|||||||
let word = string("Cargo.tomL");
|
let word = string("Cargo.tomL");
|
||||||
let pattern = "Lomt.";
|
let pattern = "Lomt.";
|
||||||
let insensitive = false;
|
let insensitive = false;
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@ -169,8 +167,7 @@ mod tests {
|
|||||||
let word = string("Cargo.ToMl");
|
let word = string("Cargo.ToMl");
|
||||||
let pattern = ".TOML";
|
let pattern = ".TOML";
|
||||||
let insensitive = true;
|
let insensitive = true;
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@ -181,8 +178,7 @@ mod tests {
|
|||||||
let word = string("Cargo.tOml");
|
let word = string("Cargo.tOml");
|
||||||
let pattern = "lomt.";
|
let pattern = "lomt.";
|
||||||
let insensitive = true;
|
let insensitive = true;
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
|
@ -104,7 +104,7 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_plugin::test_helpers::value::string;
|
||||||
use nu_protocol::{Primitive, UntaggedValue};
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -118,8 +118,7 @@ mod tests {
|
|||||||
fn str_ends_with_pattern() {
|
fn str_ends_with_pattern() {
|
||||||
let word = string("Cargo.toml");
|
let word = string("Cargo.toml");
|
||||||
let pattern = ".toml";
|
let pattern = ".toml";
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@ -129,8 +128,7 @@ mod tests {
|
|||||||
fn str_does_not_end_with_pattern() {
|
fn str_does_not_end_with_pattern() {
|
||||||
let word = string("Cargo.toml");
|
let word = string("Cargo.toml");
|
||||||
let pattern = "Car";
|
let pattern = "Car";
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
|
@ -7,7 +7,6 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use nu_source::{Tag, Tagged};
|
use nu_source::{Tag, Tagged};
|
||||||
use nu_value_ext::ValueExt;
|
use nu_value_ext::ValueExt;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -15,6 +14,7 @@ struct Arguments {
|
|||||||
find: Tagged<String>,
|
find: Tagged<String>,
|
||||||
replace: Tagged<String>,
|
replace: Tagged<String>,
|
||||||
rest: Vec<ColumnPath>,
|
rest: Vec<ColumnPath>,
|
||||||
|
all: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
@ -33,6 +33,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
SyntaxShape::ColumnPath,
|
SyntaxShape::ColumnPath,
|
||||||
"optionally find and replace text by column paths",
|
"optionally find and replace text by column paths",
|
||||||
)
|
)
|
||||||
|
.switch("all", "replace all occurences of find string", Some('a'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -48,11 +49,18 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Find and replace contents with capture group",
|
description: "Find and replace contents with capture group",
|
||||||
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'",
|
example: "echo 'my_library.rb' | str find-replace '(.+).rb' '$1.nu'",
|
||||||
result: Some(vec![Value::from("my_library.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,
|
find,
|
||||||
replace,
|
replace,
|
||||||
rest,
|
rest,
|
||||||
|
all,
|
||||||
},
|
},
|
||||||
input,
|
input,
|
||||||
) = args.process(®istry).await?;
|
) = args.process(®istry).await?;
|
||||||
let options = FindReplace(find.item, replace.item);
|
let options = FindReplace(find.item, replace.item);
|
||||||
|
|
||||||
let column_paths: Vec<_> = rest;
|
let column_paths: Vec<_> = rest;
|
||||||
|
|
||||||
Ok(input
|
Ok(input
|
||||||
.map(move |v| {
|
.map(move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
ReturnSuccess::value(action(&v, &options, v.tag())?)
|
ReturnSuccess::value(action(&v, &options, v.tag(), all)?)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
|
|
||||||
@ -89,7 +97,7 @@ async fn operate(
|
|||||||
|
|
||||||
ret = ret.swap_data_by_column_path(
|
ret = ret.swap_data_by_column_path(
|
||||||
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())
|
.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 {
|
match &input.value {
|
||||||
UntaggedValue::Primitive(Primitive::Line(s))
|
UntaggedValue::Primitive(Primitive::Line(s))
|
||||||
| UntaggedValue::Primitive(Primitive::String(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 regex = Regex::new(find.as_str());
|
||||||
|
|
||||||
let out = match regex {
|
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),
|
Err(_) => UntaggedValue::string(s),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,10 +162,10 @@ mod tests {
|
|||||||
fn can_have_capture_groups() {
|
fn can_have_capture_groups() {
|
||||||
let word = string("Cargo.toml");
|
let word = string("Cargo.toml");
|
||||||
let expected = string("Carga.toml");
|
let expected = string("Carga.toml");
|
||||||
|
let all = false;
|
||||||
let find_replace_options = FindReplace("Cargo.(.+)".to_string(), "Carga.$1".to_string());
|
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);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use nu_source::Tagged;
|
|||||||
use num_bigint::{BigInt, BigUint, ToBigInt};
|
use num_bigint::{BigInt, BigUint, ToBigInt};
|
||||||
|
|
||||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
// 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 num_traits::{Pow, Signed};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
@ -40,12 +40,15 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
"decimal digits to which to round",
|
"decimal digits to which to round",
|
||||||
Some('d'),
|
Some('d'),
|
||||||
)
|
)
|
||||||
|
/*
|
||||||
|
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
|
||||||
.switch(
|
.switch(
|
||||||
"group-digits",
|
"group-digits",
|
||||||
// TODO according to system localization
|
// TODO according to system localization
|
||||||
"group digits, currently by thousand with commas",
|
"group digits, currently by thousand with commas",
|
||||||
Some('g'),
|
Some('g'),
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -67,6 +70,8 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
example: "= 1.7 | str from -d 0",
|
example: "= 1.7 | str from -d 0",
|
||||||
result: Some(vec![UntaggedValue::string("2").into_untagged_value()]),
|
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 {
|
Example {
|
||||||
description: "format large number with localized digit grouping",
|
description: "format large number with localized digit grouping",
|
||||||
example: "= 1000000.2 | str from -g",
|
example: "= 1000000.2 | str from -g",
|
||||||
@ -74,6 +79,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
UntaggedValue::string("1,000,000.2").into_untagged_value()
|
UntaggedValue::string("1,000,000.2").into_untagged_value()
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,7 +153,7 @@ pub fn action(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn format_bigint(int: &BigInt) -> String {
|
fn format_bigint(int: &BigInt) -> String {
|
||||||
int.to_formatted_string(&Locale::en)
|
format!("{}", int)
|
||||||
|
|
||||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
||||||
// #[cfg(windows)]
|
// #[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 format_default_loc = |int_part: BigInt| {
|
||||||
let loc = Locale::en;
|
let loc = Locale::en;
|
||||||
let (int_str, sep) = (
|
//TODO: when num_format is available for recent bigint, replace this with the locale-based format
|
||||||
int_part.to_formatted_string(&loc),
|
let (int_str, sep) = (format!("{}", int_part), String::from(loc.decimal()));
|
||||||
String::from(loc.decimal()),
|
|
||||||
);
|
|
||||||
|
|
||||||
format!("{}{}{}", int_str, sep, dec_str)
|
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 {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_plugin::test_helpers::value::string;
|
||||||
use nu_protocol::{Primitive, UntaggedValue};
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -118,8 +118,7 @@ mod tests {
|
|||||||
fn str_starts_with_pattern() {
|
fn str_starts_with_pattern() {
|
||||||
let word = string("Cargo.toml");
|
let word = string("Cargo.toml");
|
||||||
let pattern = "Car";
|
let pattern = "Car";
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(true).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(true.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@ -129,8 +128,7 @@ mod tests {
|
|||||||
fn str_does_not_start_with_pattern() {
|
fn str_does_not_start_with_pattern() {
|
||||||
let word = string("Cargo.toml");
|
let word = string("Cargo.toml");
|
||||||
let pattern = ".toml";
|
let pattern = ".toml";
|
||||||
let expected =
|
let expected = UntaggedValue::boolean(false).into_untagged_value();
|
||||||
UntaggedValue::Primitive(Primitive::Boolean(false.into())).into_untagged_value();
|
|
||||||
|
|
||||||
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
let actual = action(&word, &pattern, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
|
@ -8,7 +8,6 @@ use nu_protocol::{
|
|||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
use nu_value_ext::{as_string, ValueExt};
|
use nu_value_ext::{as_string, ValueExt};
|
||||||
|
|
||||||
use std::cmp;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
@ -64,9 +63,9 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
result: Some(vec![Value::from("nushell")]),
|
result: Some(vec![Value::from("nushell")]),
|
||||||
},
|
},
|
||||||
Example {
|
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'",
|
example: "echo 'good nushell' | str substring ',-5'",
|
||||||
result: Some(vec![Value::from("shell")]),
|
result: Some(vec![Value::from("good nu")]),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get the remaining characters from a starting index",
|
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 start: isize = if options.0 < 0 {
|
||||||
let end: isize = options.1;
|
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 {
|
if start < len && end >= 0 {
|
||||||
match start.cmp(&end) {
|
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",
|
"End must be greater than or equal to Start",
|
||||||
tag.span,
|
tag.span,
|
||||||
)),
|
)),
|
||||||
Ordering::Less => {
|
Ordering::Less => Ok(UntaggedValue::string(
|
||||||
let end: isize = cmp::min(options.1, len);
|
|
||||||
|
|
||||||
Ok(UntaggedValue::string(
|
|
||||||
s.chars()
|
s.chars()
|
||||||
.skip(start as usize)
|
.skip(start as usize)
|
||||||
.take((end - start) as usize)
|
.take((end - start) as usize)
|
||||||
.collect::<String>(),
|
.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 {
|
} else {
|
||||||
Ok(UntaggedValue::string("").into_value(tag))
|
Ok(UntaggedValue::string("").into_value(tag))
|
||||||
}
|
}
|
||||||
@ -344,19 +323,20 @@ mod tests {
|
|||||||
expectation("andr", (0, 4)),
|
expectation("andr", (0, 4)),
|
||||||
expectation("andre", (0, 5)),
|
expectation("andre", (0, 5)),
|
||||||
expectation("andres", (0, 6)),
|
expectation("andres", (0, 6)),
|
||||||
expectation("andres", (0, -6)),
|
expectation("", (0, -6)),
|
||||||
expectation("ndres", (0, -5)),
|
expectation("a", (0, -5)),
|
||||||
expectation("dres", (0, -4)),
|
expectation("an", (0, -4)),
|
||||||
expectation("res", (0, -3)),
|
expectation("and", (0, -3)),
|
||||||
expectation("es", (0, -2)),
|
expectation("andr", (0, -2)),
|
||||||
expectation("s", (0, -1)),
|
expectation("andre", (0, -1)),
|
||||||
|
expectation("", (0, -110)),
|
||||||
expectation("", (6, 0)),
|
expectation("", (6, 0)),
|
||||||
expectation("s", (6, -1)),
|
expectation("", (6, -1)),
|
||||||
expectation("es", (6, -2)),
|
expectation("", (6, -2)),
|
||||||
expectation("res", (6, -3)),
|
expectation("", (6, -3)),
|
||||||
expectation("dres", (6, -4)),
|
expectation("", (6, -4)),
|
||||||
expectation("ndres", (6, -5)),
|
expectation("", (6, -5)),
|
||||||
expectation("andres", (6, -6)),
|
expectation("", (6, -6)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for expectation in cases.iter() {
|
for expectation in cases.iter() {
|
||||||
|
@ -113,7 +113,7 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
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;
|
use nu_source::Tag;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -127,7 +127,7 @@ mod tests {
|
|||||||
#[allow(clippy::approx_constant)]
|
#[allow(clippy::approx_constant)]
|
||||||
fn turns_to_integer() {
|
fn turns_to_integer() {
|
||||||
let word = string("3.1415");
|
let word = string("3.1415");
|
||||||
let expected = decimal(3.1415);
|
let expected = decimal_from_float(3.1415);
|
||||||
|
|
||||||
let actual = action(&word, Tag::unknown()).unwrap();
|
let actual = action(&word, Tag::unknown()).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
|
@ -8,6 +8,7 @@ use nu_protocol::ShellTypeName;
|
|||||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||||
use nu_source::{Tag, Tagged};
|
use nu_source::{Tag, Tagged};
|
||||||
use nu_value_ext::ValueExt;
|
use nu_value_ext::ValueExt;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
pub use trim_both_ends::SubCommand as Trim;
|
pub use trim_both_ends::SubCommand as Trim;
|
||||||
pub use trim_left::SubCommand as TrimLeft;
|
pub use trim_left::SubCommand as TrimLeft;
|
||||||
@ -38,14 +39,22 @@ where
|
|||||||
Ok(input
|
Ok(input
|
||||||
.map(move |v| {
|
.map(move |v| {
|
||||||
if column_paths.is_empty() {
|
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 {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
|
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
ret = ret.swap_data_by_column_path(
|
ret = ret.swap_data_by_column_path(
|
||||||
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())
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum ActionMode {
|
||||||
|
Local,
|
||||||
|
Global,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn action<F>(
|
pub fn action<F>(
|
||||||
input: &Value,
|
input: &Value,
|
||||||
tag: impl Into<Tag>,
|
tag: impl Into<Tag>,
|
||||||
char_: Option<char>,
|
char_: Option<char>,
|
||||||
trim_operation: &F,
|
trim_operation: &F,
|
||||||
|
mode: ActionMode,
|
||||||
) -> Result<Value, ShellError>
|
) -> Result<Value, ShellError>
|
||||||
where
|
where
|
||||||
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
|
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
let tag = tag.into();
|
||||||
match &input.value {
|
match &input.value {
|
||||||
UntaggedValue::Primitive(Primitive::Line(s))
|
UntaggedValue::Primitive(Primitive::Line(s))
|
||||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||||
Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag))
|
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());
|
let got = format!("got {}", other.type_name());
|
||||||
Err(ShellError::labeled_error(
|
Err(ShellError::labeled_error(
|
||||||
"value is not string",
|
"value is not string",
|
||||||
got,
|
got,
|
||||||
tag.into().span,
|
tag.span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,11 @@ fn trim(s: &str, char_: Option<char>) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{trim, SubCommand};
|
use super::{trim, SubCommand};
|
||||||
use crate::commands::str_::trim::action;
|
use crate::commands::str_::trim::{action, ActionMode};
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_plugin::{
|
||||||
|
row,
|
||||||
|
test_helpers::value::{int, string, table},
|
||||||
|
};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -79,7 +82,43 @@ mod tests {
|
|||||||
let word = string("andres ");
|
let word = string("andres ");
|
||||||
let expected = 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);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +127,7 @@ mod tests {
|
|||||||
let word = string("!#andres#!");
|
let word = string("!#andres#!");
|
||||||
let expected = 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);
|
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