mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
b674cee9d2 | |||
cb8491cfee | |||
8196b031f8 | |||
50dd56d3c4 | |||
0f7e1d4d01 | |||
ec77c572b9 | |||
f97561c416 | |||
5faa82e323 | |||
4e17292a12 | |||
666fbbb0d1 | |||
c6fe58467b | |||
46d1938f5c | |||
8229af7591 | |||
ee76523507 | |||
c283db373b | |||
1b0ed30516 | |||
a6fdee4a51 | |||
6951fb440c | |||
502c9ea706 | |||
22f67be461 | |||
77ffd06715 | |||
1d833ef972 | |||
0d8064ed2d | |||
cc06ea4d87 | |||
3cf7652e86 | |||
1eb28c6cb6 | |||
db590369a8 | |||
f4d654d2a2 | |||
5725e55abb | |||
b6d19cc9fa | |||
bc6c884a14 | |||
cb78bf8fd6 | |||
400bc97e35 | |||
2fd464bf7b | |||
e626522b3a | |||
791e07650d | |||
bf2363947b | |||
a2cc2259e7 | |||
808fe496a6 | |||
2fb48bd6ac |
66
Cargo.lock
generated
66
Cargo.lock
generated
@ -2858,12 +2858,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"dunce",
|
||||
"futures 0.3.5",
|
||||
"itertools",
|
||||
"log 0.4.11",
|
||||
"nu-cli",
|
||||
"nu-data",
|
||||
@ -2896,7 +2897,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"ansi_term 0.12.1",
|
||||
@ -2934,6 +2935,7 @@ dependencies = [
|
||||
"ichwh",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"lazy_static 1.4.0",
|
||||
"log 0.4.11",
|
||||
"meval",
|
||||
"nu-data",
|
||||
@ -2993,7 +2995,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-data"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"bigdecimal",
|
||||
@ -3012,6 +3014,7 @@ dependencies = [
|
||||
"nu-test-support",
|
||||
"nu-value-ext",
|
||||
"num-bigint 0.3.0",
|
||||
"num-format",
|
||||
"num-traits 0.2.12",
|
||||
"parking_lot 0.11.0",
|
||||
"query_interface",
|
||||
@ -3022,7 +3025,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-errors"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"bigdecimal",
|
||||
@ -3041,7 +3044,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"codespan-reporting",
|
||||
@ -3059,13 +3062,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"indexmap",
|
||||
"nu-errors",
|
||||
"nu-protocol",
|
||||
"nu-source",
|
||||
"nu-test-support",
|
||||
"nu-value-ext",
|
||||
"num-bigint 0.3.0",
|
||||
"serde 1.0.115",
|
||||
@ -3074,7 +3078,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"byte-unit",
|
||||
@ -3097,7 +3101,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-source"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"derive-new",
|
||||
"getset",
|
||||
@ -3108,7 +3112,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"unicode-width",
|
||||
@ -3116,8 +3120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"dunce",
|
||||
"getset",
|
||||
@ -3133,7 +3138,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-value-ext"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itertools",
|
||||
@ -3145,7 +3150,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_binaryview"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"crossterm 0.18.0",
|
||||
@ -3161,7 +3166,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_chart"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"crossterm 0.18.0",
|
||||
"nu-cli",
|
||||
@ -3176,7 +3181,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_fetch"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"futures 0.3.5",
|
||||
@ -3190,7 +3195,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_from_bson"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bson",
|
||||
@ -3204,7 +3209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_from_sqlite"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"nu-errors",
|
||||
@ -3219,19 +3224,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"nu-errors",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"nu-source",
|
||||
"nu-test-support",
|
||||
"nu-value-ext",
|
||||
"semver 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_match"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"nu-errors",
|
||||
"nu-plugin",
|
||||
@ -3242,7 +3248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_post"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"futures 0.3.5",
|
||||
@ -3258,7 +3264,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_ps"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"futures 0.3.5",
|
||||
"futures-timer",
|
||||
@ -3272,7 +3278,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_s3"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"futures 0.3.5",
|
||||
"nu-errors",
|
||||
@ -3284,7 +3290,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_start"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"nu-errors",
|
||||
@ -3297,7 +3303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_sys"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"battery",
|
||||
"futures 0.3.5",
|
||||
@ -3312,7 +3318,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_textview"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"bat",
|
||||
@ -3327,7 +3333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_to_bson"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bson",
|
||||
"nu-errors",
|
||||
@ -3340,7 +3346,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_to_sqlite"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"hex 0.4.2",
|
||||
"nu-errors",
|
||||
@ -3355,7 +3361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_tree"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"derive-new",
|
||||
"nu-errors",
|
||||
@ -3367,7 +3373,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_xpath"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"indexmap",
|
||||
@ -5533,9 +5539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "trash"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "329be7bb48445d16bf4c241ba9514af46f2189c715bf5fd854e38f7c95f60194"
|
||||
checksum = "bbf511f5673142be74cd6a46cfb7e3da8d2b95bc08d2d46c072798e6ce8b5b9f"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
75
Cargo.toml
75
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,32 +18,32 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.21.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.21.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"}
|
||||
nu-cli = {version = "0.22.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.22.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.22.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.22.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.22.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.22.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.22.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.22.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_plugin_binaryview = {version = "0.21.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.21.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.21.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.21.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.21.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.22.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.22.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.22.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.22.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.22.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.22.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.22.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.22.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.22.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.22.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.22.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.22.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.22.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.22.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.22.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.22.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
@ -51,10 +51,11 @@ ctrlc = {version = "3.1.6", optional = true}
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
itertools = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = {version = "0.21.0", path = "./crates/nu-test-support"}
|
||||
nu-test-support = {version = "0.22.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@ -190,6 +191,26 @@ name = "nu_plugin_extra_xpath"
|
||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||
required-features = ["xpath"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_bson"
|
||||
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_bson"
|
||||
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
@ -46,7 +46,7 @@ Try it in Gitpod.
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
|
||||
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.15.1
|
||||
version │ 0.21.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version | echo $it
|
||||
0.15.1
|
||||
> open Cargo.toml | get package.version
|
||||
0.21.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
|
@ -4,21 +4,21 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = {version = "0.21.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.21.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.21.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.21.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.21.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.21.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.21.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.21.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"}
|
||||
nu-data = {version = "0.22.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.22.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.22.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.22.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.22.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.22.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.22.0", path = "../nu-value-ext"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
async-recursion = "0.3.1"
|
||||
@ -91,12 +91,13 @@ uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.2", optional = true}
|
||||
zip = {version = "0.5.7", optional = true}
|
||||
lazy_static = "1.*"
|
||||
|
||||
Inflector = "0.11"
|
||||
clipboard = {version = "0.5.0", optional = true}
|
||||
encoding_rs = "0.8.24"
|
||||
rayon = "1.4.0"
|
||||
trash = {version = "1.1.1", optional = true}
|
||||
trash = {version = "1.2.0", optional = true}
|
||||
url = "2.1.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
@ -5,7 +5,7 @@ fn main() -> Result<(), io::Error> {
|
||||
let out_dir = env::var_os("OUT_DIR").expect(
|
||||
"\
|
||||
OUT_DIR environment variable not found. \
|
||||
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
|
||||
OUT_DIR is guaranteed to exist in a build script by cargo - see \
|
||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
||||
");
|
||||
|
||||
|
@ -159,7 +159,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(MoveColumn),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
@ -180,6 +180,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(Nth),
|
||||
whole_stream_command(Drop),
|
||||
whole_stream_command(Format),
|
||||
whole_stream_command(FileSize),
|
||||
whole_stream_command(Where),
|
||||
whole_stream_command(If),
|
||||
whole_stream_command(Compact),
|
||||
@ -198,6 +199,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(EachWindow),
|
||||
whole_stream_command(Empty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Flatten),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Merge),
|
||||
whole_stream_command(Shuffle),
|
||||
@ -221,6 +223,9 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(MathSummation),
|
||||
whole_stream_command(MathVariance),
|
||||
whole_stream_command(MathProduct),
|
||||
whole_stream_command(MathRound),
|
||||
whole_stream_command(MathFloor),
|
||||
whole_stream_command(MathCeil),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
@ -273,6 +278,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(UrlPath),
|
||||
whole_stream_command(UrlHost),
|
||||
whole_stream_command(UrlQuery),
|
||||
whole_stream_command(Seq),
|
||||
]);
|
||||
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
@ -388,13 +394,10 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let mut prompt_block =
|
||||
nu_parser::classify_block(&result, context.registry());
|
||||
let prompt_block = nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
prompt_block.block.expand_it_usage();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
@ -857,8 +860,7 @@ pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<S
|
||||
let lite_result = nu_parser::lite_parse(&line, 0)?;
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
classified_block.block.expand_it_usage();
|
||||
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
@ -899,7 +901,7 @@ pub async fn process_line(
|
||||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
let classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
|
||||
debug!("{:#?}", classified_block);
|
||||
//println!("{:#?}", pipeline);
|
||||
@ -1016,8 +1018,6 @@ pub async fn process_line(
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
classified_block.block.expand_it_usage();
|
||||
|
||||
trace!("{:#?}", classified_block);
|
||||
let env = ctx.get_env();
|
||||
match run_block(
|
||||
|
@ -42,6 +42,7 @@ pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod flatten;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod from;
|
||||
pub(crate) mod from_csv;
|
||||
@ -97,6 +98,7 @@ pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod seq;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
@ -175,7 +177,8 @@ pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
pub(crate) use flatten::Command as Flatten;
|
||||
pub(crate) use format::{FileSize, Format};
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_csv::FromCSV;
|
||||
pub(crate) use from_eml::FromEML;
|
||||
@ -206,12 +209,12 @@ pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||
MathStddev, MathSummation, MathVariance,
|
||||
Math, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian, MathMinimum,
|
||||
MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use move_::{Move, MoveColumn, Mv};
|
||||
pub(crate) use move_::{Move, Mv};
|
||||
pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
@ -236,6 +239,7 @@ pub(crate) use rm::Remove;
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Select;
|
||||
pub(crate) use seq::Seq;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
@ -278,19 +282,26 @@ mod tests {
|
||||
use crate::examples::{test_anchors, test_examples};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
fn commands() -> Vec<Command> {
|
||||
fn full_tests() -> Vec<Command> {
|
||||
vec![
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Empty),
|
||||
]
|
||||
}
|
||||
|
||||
fn only_examples() -> Vec<Command> {
|
||||
let mut commands = full_tests();
|
||||
commands.extend(vec![whole_stream_command(Flatten)]);
|
||||
commands
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
for cmd in commands() {
|
||||
for cmd in only_examples() {
|
||||
test_examples(cmd)?;
|
||||
}
|
||||
|
||||
@ -299,7 +310,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tracks_metadata() -> Result<(), ShellError> {
|
||||
for cmd in commands() {
|
||||
for cmd in full_tests() {
|
||||
test_anchors(cmd)?;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
|
||||
use deduction_to_signature::DeductionToSignature;
|
||||
use log::trace;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
@ -86,7 +86,6 @@ pub async fn alias(
|
||||
},
|
||||
_ctx,
|
||||
) = args.process(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||
@ -110,7 +109,7 @@ pub async fn alias(
|
||||
let alias: Value = raw_input.trim().to_string().into();
|
||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||
|
||||
// add to startup if alias doesn't exist and replce if it does
|
||||
// add to startup if alias doesn't exist and replace if it does
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
@ -132,209 +131,41 @@ pub async fn alias(
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
let mut processed_args: Vec<VarDeclaration> = vec![];
|
||||
for (_, item) in list.iter().enumerate() {
|
||||
match item.as_string() {
|
||||
Ok(var_name) => {
|
||||
let dollar_var_name = format!("${}", var_name);
|
||||
processed_args.push(VarDeclaration {
|
||||
name: dollar_var_name,
|
||||
// type_decl: None,
|
||||
span: item.tag.span,
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Found vars: {:?}", processed_args);
|
||||
|
||||
let inferred_shapes = {
|
||||
if let Some(true) = infer {
|
||||
VarSyntaxShapeDeductor::infer_vars(&processed_args, &block, ®istry)?
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
processed_args.into_iter().map(|arg| (arg, None)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(true) = infer {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
||||
block,
|
||||
),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
processed_args
|
||||
.into_iter()
|
||||
.map(|arg| (arg, SyntaxShape::Any))
|
||||
.collect(),
|
||||
block,
|
||||
),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_arg_shapes(
|
||||
args: Vec<String>,
|
||||
block: &Block,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
|
||||
match find_block_shapes(block, registry) {
|
||||
Ok(found) => Ok(args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
(
|
||||
arg.clone(),
|
||||
match found.get(arg) {
|
||||
None | Some((_, None)) => SyntaxShape::Any,
|
||||
Some((_, Some(shape))) => *shape,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
|
||||
|
||||
fn check_insert(
|
||||
existing: &mut ShapeMap,
|
||||
to_add: (String, (Span, Option<SyntaxShape>)),
|
||||
) -> Result<(), ShellError> {
|
||||
match (to_add.1).1 {
|
||||
None => match existing.get(&to_add.0) {
|
||||
None => {
|
||||
existing.insert(to_add.0, to_add.1);
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Ok(()),
|
||||
},
|
||||
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
|
||||
None => Ok(()),
|
||||
Some(exist) => match exist.1 {
|
||||
None => Ok(()),
|
||||
Some(shape) => match shape {
|
||||
SyntaxShape::Any => Ok(()),
|
||||
shape if shape == new => Ok(()),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Type conflict in alias variable use",
|
||||
format!("{:?}", new),
|
||||
(to_add.1).0,
|
||||
format!("{:?}", shape),
|
||||
exist.0,
|
||||
)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
|
||||
for (k, v) in new.iter() {
|
||||
check_insert(existing, (k.clone(), *v))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_expr_shapes(
|
||||
spanned_expr: &SpannedExpression,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<ShapeMap, ShellError> {
|
||||
match &spanned_expr.expr {
|
||||
// TODO range will need similar if/when invocations can be parsed within range expression
|
||||
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
|
||||
find_expr_shapes(&bin.right, registry)
|
||||
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
|
||||
}),
|
||||
Expression::Block(b) => find_block_shapes(&b, registry),
|
||||
Expression::Path(path) => match &path.head.expr {
|
||||
Expression::Invocation(b) => find_block_shapes(&b, registry),
|
||||
Expression::Variable(Variable::Other(var, _)) => {
|
||||
let mut result = HashMap::new();
|
||||
result.insert(var.to_string(), (spanned_expr.span, None));
|
||||
Ok(result)
|
||||
}
|
||||
_ => Ok(HashMap::new()),
|
||||
},
|
||||
_ => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
|
||||
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
|
||||
found
|
||||
.iter()
|
||||
.map(|(v, sh)| match sh.1 {
|
||||
None => (v.clone(), (sh.0, Some(sig_shape))),
|
||||
Some(shape) => (v.clone(), (sh.0, Some(shape))),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let signature = DeductionToSignature::get(&name.item, &inferred_shapes);
|
||||
trace!("Inferred signature: {:?}", signature);
|
||||
|
||||
let mut arg_shapes = HashMap::new();
|
||||
for pipeline in &block.block {
|
||||
for classified in &pipeline.list {
|
||||
match classified {
|
||||
ClassifiedCommand::Expr(spanned_expr) => {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
check_merge(&mut arg_shapes, &found)?
|
||||
}
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
if let Some(signature) = registry.get(&internal.name) {
|
||||
if let Some(positional) = &internal.args.positional {
|
||||
for (i, spanned_expr) in positional.iter().enumerate() {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
if i >= signature.positional.len() {
|
||||
if let Some((sig_shape, _)) = &signature.rest_positional {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
} else {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
} else {
|
||||
let (pos_type, _) = &signature.positional[i];
|
||||
match pos_type {
|
||||
// TODO pass on mandatory/optional?
|
||||
PositionalType::Mandatory(_, sig_shape)
|
||||
| PositionalType::Optional(_, sig_shape) => {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, val) in named.iter() {
|
||||
if let NamedValue::Value(_, spanned_expr) = val {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
match signature.named.get(name) {
|
||||
None => {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
Some((named_type, _)) => {
|
||||
if let NamedType::Mandatory(_, sig_shape)
|
||||
| NamedType::Optional(_, sig_shape) = named_type
|
||||
{
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!("registry has lost name it provided");
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arg_shapes)
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(Box::new(signature), block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -349,3 +180,42 @@ mod tests {
|
||||
Ok(test_examples(Alias {})?)
|
||||
}
|
||||
}
|
||||
|
||||
mod deduction_to_signature {
|
||||
//For now this logic is relativly simple.
|
||||
//For each var, one mandatory positional is added.
|
||||
//As soon as more support for optional positional arguments is arrived,
|
||||
//this logic might be a little bit more tricky.
|
||||
use crate::types::deduction::{Deduction, VarDeclaration};
|
||||
use nu_protocol::{PositionalType, Signature, SyntaxShape};
|
||||
|
||||
pub struct DeductionToSignature {}
|
||||
impl DeductionToSignature {
|
||||
pub fn get(
|
||||
cmd_name: &str,
|
||||
deductions: &[(VarDeclaration, Option<Deduction>)],
|
||||
) -> Signature {
|
||||
let mut signature = Signature::build(cmd_name);
|
||||
for (decl, deduction) in deductions {
|
||||
match deduction {
|
||||
None => signature.positional.push((
|
||||
PositionalType::optional(&decl.name, SyntaxShape::Any),
|
||||
decl.name.clone(),
|
||||
)),
|
||||
Some(deduction) => match deduction {
|
||||
Deduction::VarShapeDeduction(normal_var_deduction) => {
|
||||
signature.positional.push((
|
||||
PositionalType::optional(
|
||||
&decl.name,
|
||||
normal_var_deduction[0].deduction,
|
||||
),
|
||||
decl.name.clone(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,15 @@ use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
escape: Option<Tagged<String>>,
|
||||
osc: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
Signature::build("ansi")
|
||||
.optional(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
.named(
|
||||
"escape", // \x1b
|
||||
SyntaxShape::Any,
|
||||
"escape sequence without the escape character(s)",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"osc",
|
||||
SyntaxShape::Any,
|
||||
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
r#"Output ANSI codes to change color
|
||||
|
||||
For escape sequences:
|
||||
Escape: '\x1b[' is not required for --escape parameter
|
||||
Format: #(;#)m
|
||||
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
|
||||
There can be multiple text formatting sequence numbers
|
||||
separated by a ; and ending with an m where the # is of the
|
||||
following values:
|
||||
attributes
|
||||
0 reset / normal display
|
||||
1 bold or increased intensity
|
||||
2 faint or decreased intensity
|
||||
3 italic on (non-mono font)
|
||||
4 underline on
|
||||
5 slow blink on
|
||||
6 fast blink on
|
||||
7 reverse video on
|
||||
8 nondisplayed (invisible) on
|
||||
9 strike-through on
|
||||
|
||||
foreground/bright colors background/bright colors
|
||||
30/90 black 40/100 black
|
||||
31/91 red 41/101 red
|
||||
32/92 green 42/102 green
|
||||
33/93 yellow 43/103 yellow
|
||||
34/94 blue 44/104 blue
|
||||
35/95 magenta 45/105 magenta
|
||||
36/96 cyan 46/106 cyan
|
||||
37/97 white 47/107 white
|
||||
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
OSC: '\x1b]' is not required for --osc parameter
|
||||
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
|
||||
Format: #
|
||||
0 Set window title and icon name
|
||||
1 Set icon name
|
||||
2 Set window title
|
||||
4 Set/read color palette
|
||||
9 iTerm2 Grown notifications
|
||||
10 Set foreground color (x11 color spec)
|
||||
11 Set background color (x11 color spec)
|
||||
... others"#
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -49,6 +107,14 @@ impl WholeStreamCommand for Ansi {
|
||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -57,10 +123,41 @@ impl WholeStreamCommand for Ansi {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
let (AnsiArgs { color, escape, osc }, _) = args.process(®istry).await?;
|
||||
|
||||
if let Some(e) = escape {
|
||||
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||
if esc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
e.tag(),
|
||||
));
|
||||
}
|
||||
let output = format!("\x1b[{}", e.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(e.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(o) = osc {
|
||||
let osc_vec: Vec<char> = o.item.chars().collect();
|
||||
if osc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
o.tag(),
|
||||
));
|
||||
}
|
||||
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
|
||||
// OCS's need to end with a bell '\x07' char
|
||||
let output = format!("\x1b]{};", o.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(o.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
row: Value,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
pub struct Command;
|
||||
@ -26,7 +26,7 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append the given row to the table"
|
||||
"Append a row to the table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -34,30 +34,57 @@ impl WholeStreamCommand for Command {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { mut row }, input) = args.process(registry).await?;
|
||||
let (Arguments { mut value }, input) = args.process(registry).await?;
|
||||
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
if let Some(first) = input.get(0) {
|
||||
row.tag = first.tag();
|
||||
value.tag = first.tag();
|
||||
}
|
||||
|
||||
Ok(
|
||||
futures::stream::iter(input.into_iter().chain(vec![row]).map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
// Checks if we are trying to append a row literal
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(values),
|
||||
tag,
|
||||
} = &value
|
||||
{
|
||||
if values.len() == 1 && values[0].is_row() {
|
||||
value = values[0].value.clone().into_value(tag);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
input
|
||||
.into_iter()
|
||||
.chain(vec![value])
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Add something to the end of a list or table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
use nu_protocol::row;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Add values to the end of the table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Add row value to the end of the table",
|
||||
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||
result: Some(vec![
|
||||
row! { "country".into() => Value::from("Ecuador")},
|
||||
row! { "country".into() => Value::from("New Zealand")},
|
||||
row! { "country".into() => Value::from("USA")},
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ pub struct Char;
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
unicode: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
Signature::build("ansi")
|
||||
.required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
|
||||
UntaggedValue::string("\u{2261}").into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Output unicode character",
|
||||
example: r#"char -u 1f378"#,
|
||||
result: Some(vec![Value::from("\u{1f378}")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -53,24 +61,44 @@ impl WholeStreamCommand for Char {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||
let (CharArgs { name, unicode }, _) = args.process(®istry).await?;
|
||||
|
||||
let special_character = str_to_character(&name.item);
|
||||
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
if unicode {
|
||||
let decoded_char = string_to_unicode_char(&name.item);
|
||||
if let Some(output) = decoded_char {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding unicode character",
|
||||
"error decoding unicode character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown character",
|
||||
"unknown character",
|
||||
name.tag(),
|
||||
))
|
||||
let special_character = str_to_character(&name.item);
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error finding named character",
|
||||
"error finding named character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||
u32::from_str_radix(s, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32)
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"sp" | "space" => Some(" ".into()),
|
||||
// Unicode names came from https://www.compart.com/en/unicode
|
||||
// Private Use Area (U+E000-U+F8FF)
|
||||
// Unicode can't be mixed with Ansi or it will break width calculation
|
||||
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||
|
||||
@ -96,15 +125,14 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"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 ⚡
|
||||
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||
"moon" => Some("🌛".to_string()),
|
||||
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||
"snowy" | "snow" => Some("❄️".to_string()),
|
||||
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
@ -123,6 +151,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"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,
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::futures::ThreadedReceiver;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
@ -13,6 +14,7 @@ use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
@ -50,6 +52,7 @@ async fn run_with_stdin(
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
@ -65,8 +68,10 @@ async fn run_with_stdin(
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args
|
||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
||||
command_args.push((
|
||||
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||
is_literal,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
@ -80,14 +85,14 @@ async fn run_with_stdin(
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
command_args.push(trimmed_value_string);
|
||||
command_args.push((trimmed_value_string, is_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
.map(|(arg, _is_literal)| {
|
||||
let home_dir;
|
||||
|
||||
#[cfg(feature = "dirs")]
|
||||
@ -103,8 +108,9 @@ async fn run_with_stdin(
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
||||
add_quotes(&arg)
|
||||
if !_is_literal {
|
||||
let escaped = escape_double_quotes(&arg);
|
||||
add_double_quotes(&escaped)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
@ -219,36 +225,19 @@ fn spawn(
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
if stdin_write.write(s.as_bytes()).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if let Err(e) = stdin_write.write(b) {
|
||||
let message = format!("Unable to write to stdin (error = {})", e);
|
||||
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
message,
|
||||
"application may have closed before completing pipeline",
|
||||
&stdin_name_tag,
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
if stdin_write.write(b).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
println!("Unsupported: {:?}", unsupported);
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
@ -494,11 +483,6 @@ where
|
||||
shellexpand::tilde_with_context(input, home_dir)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
||||
argument.chars().any(|c| c.is_whitespace())
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
@ -509,10 +493,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_quotes(argument: &str) -> String {
|
||||
fn add_double_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||
// allocate new string only if required
|
||||
if argument.contains('"') {
|
||||
Cow::Owned(argument.replace('"', r#"\""#))
|
||||
} else {
|
||||
Cow::Borrowed(argument)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
@ -538,7 +532,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
||||
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
||||
};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, EvaluationContext, InputStream};
|
||||
@ -618,10 +612,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
||||
fn checks_escape_double_quotes() {
|
||||
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -649,9 +643,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
||||
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -185,9 +185,9 @@ pub(crate) async fn run_internal_command(
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
CommandAction::AddAlias(sig, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
AliasCommand::new(*sig, block),
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use crate::prelude::*;
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
||||
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
@ -73,34 +72,35 @@ impl WholeStreamCommand for Each {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
||||
matches!(&*head, SpannedExpression {
|
||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
||||
..
|
||||
} if s == "expanded-each")
|
||||
}
|
||||
|
||||
pub async fn process_row(
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
mut context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let input_clone = input.clone();
|
||||
let input_stream = if is_expanded_it_usage(&head) {
|
||||
// When we process a row, we need to know whether the block wants to have the contents of the row as
|
||||
// a parameter to the block (so it gets assigned to a variable that can be used inside the block) or
|
||||
// if it wants the contents as as an input stream
|
||||
|
||||
let input_stream = if !block.params.is_empty() {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
};
|
||||
Ok(run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
Scope::append_it(scope, input),
|
||||
|
||||
let scope = if !block.params.is_empty() {
|
||||
// FIXME: add check for more than parameter, once that's supported
|
||||
Scope::append_var(scope, block.params[0].clone(), input)
|
||||
} else {
|
||||
scope
|
||||
};
|
||||
|
||||
Ok(
|
||||
run_block(&block, Arc::make_mut(&mut context), input_stream, scope)
|
||||
.await?
|
||||
.to_output_stream(),
|
||||
)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
@ -116,7 +116,6 @@ async fn each(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
@ -128,12 +127,11 @@ async fn each(
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
let row = make_indexed_item(input.0, input.1);
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, row).await {
|
||||
match process_row(block, scope, context, row).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
@ -146,11 +144,10 @@ async fn each(
|
||||
.then(move |input| {
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, input).await {
|
||||
match process_row(block, scope, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ 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_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
@ -52,7 +49,6 @@ impl WholeStreamCommand for EachGroup {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = 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?;
|
||||
@ -61,13 +57,7 @@ impl WholeStreamCommand for EachGroup {
|
||||
Ok(input
|
||||
.chunks(each_args.group_size.item)
|
||||
.then(move |input| {
|
||||
run_block_on_vec(
|
||||
input,
|
||||
block.clone(),
|
||||
scope.clone(),
|
||||
head.clone(),
|
||||
context.clone(),
|
||||
)
|
||||
run_block_on_vec(input, block.clone(), scope.clone(), context.clone())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
@ -78,7 +68,6 @@ 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 {
|
||||
@ -87,7 +76,7 @@ pub(crate) fn run_block_on_vec(
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, value).await {
|
||||
match process_row(block, scope, 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.
|
||||
|
@ -56,7 +56,6 @@ impl WholeStreamCommand for EachWindow {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = 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?;
|
||||
@ -82,13 +81,12 @@ impl WholeStreamCommand for EachWindow {
|
||||
|
||||
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)
|
||||
Some(run_block_on_vec(local_window, block, scope, context).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ async fn process_row(
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let scope = Scope::append_it(scope, input.clone());
|
||||
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||
|
||||
let mut stream = run_block(
|
||||
&default_block,
|
||||
|
192
crates/nu-cli/src/commands/flatten.rs
Normal file
192
crates/nu-cli/src/commands/flatten.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"flatten"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Flatten the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
flatten(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "flatten a table",
|
||||
example: "echo [[N, u, s, h, e, l, l]] | flatten | first",
|
||||
result: Some(vec![Value::from("N")]),
|
||||
},
|
||||
Example {
|
||||
description: "flatten a column having a nested table",
|
||||
example: "echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal",
|
||||
result: Some(vec![Value::from("arepa")]),
|
||||
},
|
||||
Example {
|
||||
description: "restrict the flattening by passing column names",
|
||||
example: "echo [[origin, crate, versions]; [World, $(echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions",
|
||||
result: Some(vec![Value::from("0.22")]),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn flatten(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (Arguments { rest: columns }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
futures::stream::iter(flat_value(&columns, &item, &tag).into_iter().flatten())
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
enum TableInside<'a> {
|
||||
Entries(&'a str, &'a Tag, Vec<&'a Value>),
|
||||
}
|
||||
|
||||
fn flat_value(
|
||||
columns: &[Tagged<String>],
|
||||
item: &Value,
|
||||
name_tag: impl Into<Tag>,
|
||||
) -> Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError> {
|
||||
let tag = item.tag.clone();
|
||||
let name_tag = name_tag.into();
|
||||
|
||||
let res = {
|
||||
if item.is_row() {
|
||||
let mut out = TaggedDictBuilder::new(tag);
|
||||
let mut a_table = None;
|
||||
let mut tables_explicitly_flattened = 0;
|
||||
|
||||
for (column, value) in item.row_entries() {
|
||||
let column_requested = columns.iter().find(|c| c.item == *column);
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Row(Dictionary { entries: mapa }),
|
||||
..
|
||||
} = value
|
||||
{
|
||||
if column_requested.is_none() && !columns.is_empty() {
|
||||
if out.contains_key(&column) {
|
||||
out.insert_value(format!("{}_{}", column, column), value.clone());
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (k, v) in mapa.into_iter() {
|
||||
if out.contains_key(k) {
|
||||
out.insert_value(format!("{}_{}", column, k), v.clone());
|
||||
} else {
|
||||
out.insert_value(k, v.clone());
|
||||
}
|
||||
}
|
||||
} else if value.is_table() {
|
||||
if tables_explicitly_flattened >= 1 && column_requested.is_some() {
|
||||
let attempted = if let Some(name) = column_requested {
|
||||
name.span()
|
||||
} else {
|
||||
name_tag.span
|
||||
};
|
||||
|
||||
let already_flattened =
|
||||
if let Some(TableInside::Entries(_, column_tag, _)) = a_table {
|
||||
column_tag.span
|
||||
} else {
|
||||
name_tag.span
|
||||
};
|
||||
|
||||
return Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Error(ShellError::labeled_error_with_secondary(
|
||||
"can only flatten one inner table at the same time",
|
||||
"tried flattening more than one column with inner tables",
|
||||
attempted,
|
||||
"...but is flattened already",
|
||||
already_flattened,
|
||||
))
|
||||
.into_value(name_tag),
|
||||
)]);
|
||||
}
|
||||
|
||||
if !columns.is_empty() {
|
||||
if let Some(requested) = column_requested {
|
||||
a_table = Some(TableInside::Entries(
|
||||
&requested.item,
|
||||
&requested.tag,
|
||||
value.table_entries().collect(),
|
||||
));
|
||||
|
||||
tables_explicitly_flattened += 1;
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
} else if a_table.is_none() {
|
||||
a_table = Some(TableInside::Entries(
|
||||
&column,
|
||||
&value.tag,
|
||||
value.table_entries().collect(),
|
||||
))
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
} else {
|
||||
out.insert_value(column, value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut expanded = vec![];
|
||||
|
||||
if let Some(TableInside::Entries(column, _, entries)) = a_table {
|
||||
for entry in entries.into_iter() {
|
||||
let mut base = out.clone();
|
||||
base.insert_value(column, entry.clone());
|
||||
expanded.push(base.into_value());
|
||||
}
|
||||
} else {
|
||||
expanded.push(out.into_value());
|
||||
}
|
||||
|
||||
expanded
|
||||
} else if item.is_table() {
|
||||
item.table_entries().map(Clone::clone).collect()
|
||||
} else {
|
||||
vec![item.clone()]
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res.into_iter().map(ReturnSuccess::value).collect())
|
||||
}
|
@ -83,7 +83,7 @@ async fn format_command(
|
||||
let result = evaluate_baseline_expr(
|
||||
&full_column_path.0,
|
||||
®istry,
|
||||
Scope::append_it(scope.clone(), value.clone()),
|
||||
Scope::append_var(scope.clone(), "$it", value.clone()),
|
||||
)
|
||||
.await;
|
||||
|
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
|
||||
UntaggedValue::Primitive, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
|
||||
pub struct FileSize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
field: ColumnPath,
|
||||
format: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FileSize {
|
||||
fn name(&self) -> &str {
|
||||
"format filesize"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format filesize")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"format value",
|
||||
SyntaxShape::String,
|
||||
"the format into which convert the filesizes",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts a column of filesizes to some specified format"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
filesize(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the size row to KB",
|
||||
example: "ls | format filesize size KB",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert the apparent row to B",
|
||||
example: "du | format filesize apparent B",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
input: Value,
|
||||
format: Tagged<String>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok({
|
||||
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||
match replace_for {
|
||||
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
|
||||
Ok(b) => OutputStream::one(ReturnSuccess::value(
|
||||
input.replace_data_at_column_path(&field, b).expect("Given that the existance check was already done, this souldn't trigger never"),
|
||||
)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn filesize(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (Arguments { field, format }, input) = raw_args.process(®istry).await?;
|
||||
let field = Arc::new(field);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let format = format.clone();
|
||||
let field = field.clone();
|
||||
|
||||
async {
|
||||
match process_row(input, format, field).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn convert_bytes_to_string_using_format(
|
||||
bytes: Value,
|
||||
format: Tagged<String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match bytes.value {
|
||||
Primitive(Filesize(b)) => {
|
||||
let byte = byte_unit::Byte::from_bytes(b as u128);
|
||||
let value = match format.item().to_lowercase().as_str() {
|
||||
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
|
||||
"kb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
|
||||
)),
|
||||
"kib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
|
||||
)),
|
||||
"mb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
|
||||
)),
|
||||
"mib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
|
||||
)),
|
||||
"gb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
|
||||
)),
|
||||
"gib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
|
||||
)),
|
||||
"tb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
|
||||
)),
|
||||
"tib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
|
||||
)),
|
||||
"pb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
|
||||
)),
|
||||
"pib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
|
||||
)),
|
||||
"eb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
|
||||
)),
|
||||
"eib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
|
||||
)),
|
||||
"zb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
|
||||
)),
|
||||
"zib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
|
||||
)),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
format!("Invalid format code: {:}", format.item()),
|
||||
"invalid format",
|
||||
format.tag(),
|
||||
)),
|
||||
};
|
||||
match value {
|
||||
Ok(b) => Ok(Value { value: b, ..bytes }),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"the data in this row is not of the type filesize",
|
||||
"invalid row type",
|
||||
bytes.tag(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FileSize;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(FileSize {})?)
|
||||
}
|
||||
}
|
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod command;
|
||||
pub mod format_filesize;
|
||||
|
||||
pub use command::Format;
|
||||
pub use format_filesize::FileSize;
|
@ -175,8 +175,8 @@ async fn from_yaml(
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::*;
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::row;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -51,30 +51,30 @@ impl WholeStreamCommand for Command {
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"File".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||
"type".to_string() => UntaggedValue::string("File").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(10).into(),
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||
"type".to_string() => UntaggedValue::string("File").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(20).into(),
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
]).into(),
|
||||
"Dir".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
"name".to_string() => UntaggedValue::string("Jonathan").into(),
|
||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(5).into(),
|
||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
"name".to_string() => UntaggedValue::string("Yehuda").into(),
|
||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||
"chickens".to_string() => UntaggedValue::int(4).into(),
|
||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||
}).into(),
|
||||
]).into(),
|
||||
})
|
||||
@ -139,7 +139,6 @@ pub async fn group_by(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(args.call_info.args.head.clone());
|
||||
let scope = args.call_info.scope.clone();
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let (Arguments { grouper }, input) = args.process(®istry).await?;
|
||||
@ -159,12 +158,9 @@ pub async fn group_by(
|
||||
for value in values.iter() {
|
||||
let run = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
|
||||
match crate::commands::each::process_row(run, scope, head, context, value.clone())
|
||||
.await
|
||||
{
|
||||
match crate::commands::each::process_row(run, scope, context, value.clone()).await {
|
||||
Ok(mut s) => {
|
||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
s.drain_vec().await;
|
||||
@ -278,9 +274,10 @@ pub fn group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::group;
|
||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::committers;
|
||||
use nu_errors::ShellError;
|
||||
use nu_source::*;
|
||||
use nu_test_support::value::{date, int, row, string, table};
|
||||
|
||||
#[test]
|
||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||
|
@ -121,7 +121,7 @@ async fn if_command(
|
||||
let then_case = then_case.clone();
|
||||
let else_case = else_case.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), input);
|
||||
let scope = Scope::append_var(scope.clone(), "$it", input);
|
||||
let mut context = context.clone();
|
||||
|
||||
async move {
|
||||
|
@ -87,7 +87,7 @@ async fn process_row(
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let scope = Scope::append_it(scope, input.clone());
|
||||
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||
|
||||
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
||||
|
||||
@ -140,7 +140,7 @@ async fn process_row(
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match scope
|
||||
.it()
|
||||
.var("$it")
|
||||
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
|
||||
.insert_data_at_column_path(&field, value.clone())
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ impl WholeStreamCommand for IntoInt {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert filesize to integer",
|
||||
example: "echo 1kb | into-int $it | = $it / 1024",
|
||||
example: "into-int 1kb | each { = $it / 1024 }",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
}]
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
|
@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
|
91
crates/nu-cli/src/commands/math/ceil.rs
Normal file
91
crates/nu-cli/src/commands/math/ceil.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::One;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math ceil"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math celi")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Applies the ceil function to a list of numbers"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_numerical_functions_on_stream(
|
||||
RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
},
|
||||
ceil_big_int,
|
||||
ceil_big_decimal,
|
||||
ceil_default,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Apply the ceil function to a list of numbers",
|
||||
example: "echo [1.5 2.3 -3.1] | math ceil",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(-3).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn ceil_big_int(val: BigInt) -> Value {
|
||||
UntaggedValue::int(val).into()
|
||||
}
|
||||
|
||||
fn ceil_big_decimal(val: BigDecimal) -> Value {
|
||||
let mut maybe_ceiled = val.round(0);
|
||||
if maybe_ceiled < val {
|
||||
maybe_ceiled += BigDecimal::one();
|
||||
}
|
||||
let (ceiled, _) = maybe_ceiled.into_bigint_and_exponent();
|
||||
UntaggedValue::int(ceiled).into()
|
||||
}
|
||||
|
||||
fn ceil_default(_: UntaggedValue) -> Value {
|
||||
UntaggedValue::Error(ShellError::unexpected(
|
||||
"Only numerical values are supported",
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -38,9 +38,8 @@ mod tests {
|
||||
avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
|
||||
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
||||
};
|
||||
use nu_plugin::row;
|
||||
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
|
||||
use nu_protocol::Value;
|
||||
use nu_protocol::{row, Value};
|
||||
use nu_test_support::value::{decimal, decimal_from_float, int, table};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
@ -71,14 +70,14 @@ mod tests {
|
||||
values: vec![int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(10)),
|
||||
Ok(decimal_from_float(10.0)),
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(table(&[int(10)])),
|
||||
Ok(decimal(0)),
|
||||
Ok(decimal_from_float(0.0)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(0)),
|
||||
Ok(decimal_from_float(0.0)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
@ -86,7 +85,7 @@ mod tests {
|
||||
values: vec![int(10), int(20), int(30)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(20)),
|
||||
Ok(decimal_from_float(20.0)),
|
||||
Ok(int(10)),
|
||||
Ok(int(30)),
|
||||
Ok(int(20)),
|
||||
@ -101,13 +100,13 @@ mod tests {
|
||||
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(21)),
|
||||
Ok(decimal_from_float(21.0)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(decimal_from_float(26.5)),
|
||||
Ok(table(&[decimal_from_float(26.5)])),
|
||||
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(63)),
|
||||
Ok(decimal_from_float(63.0)),
|
||||
Ok(decimal_from_float(60.5)),
|
||||
],
|
||||
},
|
||||
@ -116,14 +115,14 @@ mod tests {
|
||||
values: vec![int(-14), int(-11), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(decimal_from_float(-5.0)),
|
||||
Ok(int(-14)),
|
||||
Ok(int(10)),
|
||||
Ok(int(-11)),
|
||||
Ok(table(&[int(-14), int(-11), int(10)])),
|
||||
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
|
||||
Ok(int(-15)),
|
||||
Ok(decimal(114)),
|
||||
Ok(decimal_from_float(114.0)),
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
@ -131,13 +130,13 @@ mod tests {
|
||||
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
|
||||
expected_err: None,
|
||||
expected_res: vec![
|
||||
Ok(decimal(-5)),
|
||||
Ok(decimal_from_float(-5.0)),
|
||||
Ok(decimal_from_float(-13.5)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal_from_float(-11.5)),
|
||||
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
|
||||
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
||||
Ok(decimal(-15)),
|
||||
Ok(decimal_from_float(-15.0)),
|
||||
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
||||
],
|
||||
},
|
||||
|
@ -91,7 +91,9 @@ pub async fn eval(
|
||||
}
|
||||
|
||||
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
|
||||
match meval::eval_str(math_expression) {
|
||||
let mut ctx = meval::Context::new();
|
||||
ctx.var("tau", std::f64::consts::TAU);
|
||||
match meval::eval_str_with_context(math_expression, &ctx) {
|
||||
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
||||
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
|
||||
Err(error) => Err(error.to_string().to_lowercase()),
|
||||
|
91
crates/nu-cli/src/commands/math/floor.rs
Normal file
91
crates/nu-cli/src/commands/math/floor.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::One;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math floor"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math floor")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Applies the floor function to a list of numbers"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_numerical_functions_on_stream(
|
||||
RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
},
|
||||
floor_big_int,
|
||||
floor_big_decimal,
|
||||
floor_default,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Apply the floor function to a list of numbers",
|
||||
example: "echo [1.5 2.3 -3.1] | math floor",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(-4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn floor_big_int(val: BigInt) -> Value {
|
||||
UntaggedValue::int(val).into()
|
||||
}
|
||||
|
||||
fn floor_big_decimal(val: BigDecimal) -> Value {
|
||||
let mut maybe_floored = val.round(0);
|
||||
if maybe_floored > val {
|
||||
maybe_floored -= BigDecimal::one();
|
||||
}
|
||||
let (floored, _) = maybe_floored.into_bigint_and_exponent();
|
||||
UntaggedValue::int(floored).into()
|
||||
}
|
||||
|
||||
fn floor_default(_: UntaggedValue) -> Value {
|
||||
UntaggedValue::Error(ShellError::unexpected(
|
||||
"Only numerical values are supported",
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
pub mod avg;
|
||||
pub mod ceil;
|
||||
pub mod command;
|
||||
pub mod eval;
|
||||
pub mod floor;
|
||||
pub mod max;
|
||||
pub mod median;
|
||||
pub mod min;
|
||||
pub mod mode;
|
||||
pub mod product;
|
||||
pub mod round;
|
||||
pub mod stddev;
|
||||
pub mod sum;
|
||||
pub mod variance;
|
||||
@ -14,13 +17,16 @@ mod reducers;
|
||||
mod utils;
|
||||
|
||||
pub use avg::SubCommand as MathAverage;
|
||||
pub use ceil::SubCommand as MathCeil;
|
||||
pub use command::Command as Math;
|
||||
pub use eval::SubCommand as MathEval;
|
||||
pub use floor::SubCommand as MathFloor;
|
||||
pub use max::SubCommand as MathMaximum;
|
||||
pub use median::SubCommand as MathMedian;
|
||||
pub use min::SubCommand as MathMinimum;
|
||||
pub use mode::SubCommand as MathMode;
|
||||
pub use product::SubCommand as MathProduct;
|
||||
pub use round::SubCommand as MathRound;
|
||||
pub use stddev::SubCommand as MathStddev;
|
||||
pub use sum::SubCommand as MathSummation;
|
||||
pub use variance::SubCommand as MathVariance;
|
||||
|
111
crates/nu-cli/src/commands/math/round.rs
Normal file
111
crates/nu-cli/src/commands/math/round.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
precision: Option<Tagged<i64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math round"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math round").named(
|
||||
"precision",
|
||||
SyntaxShape::Number,
|
||||
"digits of precision",
|
||||
Some('p'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Applies the round function to a list of numbers"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Apply the round function to a list of numbers",
|
||||
example: "echo [1.5 2.3 -3.1] | math round",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(-3).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Apply the round function with precision specified",
|
||||
example: "echo [1.555 2.333 -3.111] | math round -p 2",
|
||||
result: Some(vec![
|
||||
UntaggedValue::decimal_from_float(1.56, Span::default()).into(),
|
||||
UntaggedValue::decimal_from_float(2.33, Span::default()).into(),
|
||||
UntaggedValue::decimal_from_float(-3.11, Span::default()).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn operate(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { precision }, input) = args.process(®istry).await?;
|
||||
let precision = precision.map(|p| p.item).unwrap_or(0);
|
||||
|
||||
let mapped = input.map(move |val| match val.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(val)) => round_big_int(val),
|
||||
UntaggedValue::Primitive(Primitive::Decimal(val)) => round_big_decimal(val, precision),
|
||||
other => round_default(other),
|
||||
});
|
||||
Ok(OutputStream::from_input(mapped))
|
||||
}
|
||||
|
||||
fn round_big_int(val: BigInt) -> Value {
|
||||
UntaggedValue::int(val).into()
|
||||
}
|
||||
|
||||
fn round_big_decimal(val: BigDecimal, precision: i64) -> Value {
|
||||
if precision > 0 {
|
||||
UntaggedValue::decimal(val.round(precision)).into()
|
||||
} else {
|
||||
let (rounded, _) = val.round(precision).as_bigint_and_exponent();
|
||||
UntaggedValue::int(rounded).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn round_default(_: UntaggedValue) -> Value {
|
||||
UntaggedValue::Error(ShellError::unexpected(
|
||||
"Only numerical values are supported",
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
@ -31,6 +31,26 @@ pub async fn run_with_function(
|
||||
}
|
||||
}
|
||||
|
||||
pub type IntFunction = fn(val: BigInt) -> Value;
|
||||
|
||||
pub type DecimalFunction = fn(val: BigDecimal) -> Value;
|
||||
|
||||
pub type DefaultFunction = fn(val: UntaggedValue) -> Value;
|
||||
|
||||
pub async fn run_with_numerical_functions_on_stream(
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
int_function: IntFunction,
|
||||
decimal_function: DecimalFunction,
|
||||
default_function: DefaultFunction,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mapped = input.map(move |val| match val.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(val)) => int_function(val),
|
||||
UntaggedValue::Primitive(Primitive::Decimal(val)) => decimal_function(val),
|
||||
other => default_function(other),
|
||||
});
|
||||
Ok(OutputStream::from_input(mapped))
|
||||
}
|
||||
|
||||
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
|
||||
if values.iter().all(|v| v.is_primitive()) {
|
||||
mf(&values, &name)
|
||||
|
@ -1,328 +0,0 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
after: Option<ColumnPath>,
|
||||
before: Option<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"move column"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("move column")
|
||||
.rest(SyntaxShape::ColumnPath, "the columns to move")
|
||||
.named(
|
||||
"after",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column that will precede the columns moved",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"before",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column that will be next the columns moved",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Move columns."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn operate(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = raw_args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
Arguments {
|
||||
rest: mut columns,
|
||||
before,
|
||||
after,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
|
||||
if columns.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected columns",
|
||||
"expected columns",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
if columns.iter().any(|c| c.members().len() > 1) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected columns",
|
||||
"expected columns",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
if vec![&after, &before]
|
||||
.iter()
|
||||
.map(|o| if o.is_some() { 1 } else { 0 })
|
||||
.sum::<usize>()
|
||||
> 1
|
||||
{
|
||||
return Err(ShellError::labeled_error(
|
||||
"can't move column(s)",
|
||||
"pick exactly one (before, after)",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
let member = columns.remove(0);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
let column_paths = vec![&member, &columns]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if after.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
.iter()
|
||||
.filter_map(|c| c.last())
|
||||
.map(|c| c.as_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(column) = after.last() {
|
||||
if !keys.contains(&column.as_string()) {
|
||||
ReturnSuccess::value(move_after(&item, &keys, &after, &name)?)
|
||||
} else {
|
||||
let msg =
|
||||
format!("can't move column {} after itself", column.as_string());
|
||||
Err(ShellError::labeled_error(
|
||||
"can't move column",
|
||||
msg,
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
} else if let Some(before) = before {
|
||||
let member = columns.remove(0);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
let column_paths = vec![&member, &columns]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if before.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
.iter()
|
||||
.filter_map(|c| c.last())
|
||||
.map(|c| c.as_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(column) = before.last() {
|
||||
if !keys.contains(&column.as_string()) {
|
||||
ReturnSuccess::value(move_before(&item, &keys, &before, &name)?)
|
||||
} else {
|
||||
let msg =
|
||||
format!("can't move column {} before itself", column.as_string());
|
||||
Err(ShellError::labeled_error(
|
||||
"can't move column",
|
||||
msg,
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"no columns given",
|
||||
"no columns given",
|
||||
name,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn move_after(
|
||||
table: &Value,
|
||||
columns: &[String],
|
||||
from: &ColumnPath,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
from_fields,
|
||||
));
|
||||
};
|
||||
|
||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||
if columns.contains(&name) {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
});
|
||||
|
||||
let mut reordered_columns = vec![];
|
||||
let mut insert = false;
|
||||
let mut inserted = false;
|
||||
|
||||
for name in columns_moved.into_iter() {
|
||||
if let Some(name) = name {
|
||||
reordered_columns.push(Some(name.clone()));
|
||||
|
||||
if !inserted && name == from {
|
||||
insert = true;
|
||||
}
|
||||
} else {
|
||||
reordered_columns.push(None);
|
||||
}
|
||||
|
||||
if insert {
|
||||
for column in columns {
|
||||
reordered_columns.push(Some(column.clone()));
|
||||
}
|
||||
inserted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(select_fields(
|
||||
table,
|
||||
&reordered_columns
|
||||
.into_iter()
|
||||
.filter_map(|v| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&tag,
|
||||
))
|
||||
}
|
||||
|
||||
fn move_before(
|
||||
table: &Value,
|
||||
columns: &[String],
|
||||
from: &ColumnPath,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
from_fields,
|
||||
));
|
||||
};
|
||||
|
||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||
if columns.contains(&name) {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
});
|
||||
|
||||
let mut reordered_columns = vec![];
|
||||
let mut inserted = false;
|
||||
|
||||
for name in columns_moved.into_iter() {
|
||||
if let Some(name) = name {
|
||||
if !inserted && name == from {
|
||||
for column in columns {
|
||||
reordered_columns.push(Some(column.clone()));
|
||||
}
|
||||
|
||||
inserted = true;
|
||||
}
|
||||
|
||||
reordered_columns.push(Some(name.clone()));
|
||||
} else {
|
||||
reordered_columns.push(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(select_fields(
|
||||
table,
|
||||
&reordered_columns
|
||||
.into_iter()
|
||||
.filter_map(|v| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&tag,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -1,11 +1,20 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
after: Option<ColumnPath>,
|
||||
before: Option<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,34 +23,318 @@ impl WholeStreamCommand for Command {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("move")
|
||||
.rest(SyntaxShape::ColumnPath, "the columns to move")
|
||||
.named(
|
||||
"after",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column that will precede the columns moved",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"before",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column that will be next the columns moved",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Moves across desired subcommand."
|
||||
"Move columns."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
operate(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_test_support::value::*;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Move the column \"type\" before the column \"name\"",
|
||||
example: r#"ls | move type --before name | first"#,
|
||||
result: Some(vec![row! {
|
||||
"type".into() => string("File"),
|
||||
"name".into() => string("Andrés.txt"),
|
||||
"chickens".into() => int(10),
|
||||
"modified".into() => date("2019-07-23")
|
||||
}]),
|
||||
},
|
||||
Example {
|
||||
description: "or move the column \"chickens\" after \"name\"",
|
||||
example: r#"ls | move chickens --after name | first"#,
|
||||
result: Some(vec![row! {
|
||||
"name".into() => string("Andrés.txt"),
|
||||
"chickens".into() => int(10),
|
||||
"type".into() => string("File"),
|
||||
"modified".into() => date("2019-07-23")
|
||||
}]),
|
||||
},
|
||||
Example {
|
||||
description: "you can selectively move many columns in either direction",
|
||||
example: r#"ls | move name chickens --after type | first"#,
|
||||
result: Some(vec![row! {
|
||||
"type".into() => string("File"),
|
||||
"name".into() => string("Andrés.txt"),
|
||||
"chickens".into() => int(10),
|
||||
"modified".into() => date("2019-07-23")
|
||||
}]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
async fn operate(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = raw_args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
Arguments {
|
||||
rest: mut columns,
|
||||
before,
|
||||
after,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
if columns.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected columns",
|
||||
"expected columns",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(test_examples(Command {})?)
|
||||
if columns.iter().any(|c| c.members().len() > 1) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"expected columns",
|
||||
"expected columns",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
if vec![&after, &before]
|
||||
.iter()
|
||||
.map(|o| if o.is_some() { 1 } else { 0 })
|
||||
.sum::<usize>()
|
||||
> 1
|
||||
{
|
||||
return Err(ShellError::labeled_error(
|
||||
"can't move column(s)",
|
||||
"pick exactly one (before, after)",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
let member = columns.remove(0);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
let column_paths = vec![&member, &columns]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if after.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
.iter()
|
||||
.filter_map(|c| c.last())
|
||||
.map(|c| c.as_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(column) = after.last() {
|
||||
if !keys.contains(&column.as_string()) {
|
||||
ReturnSuccess::value(move_after(&item, &keys, &after)?)
|
||||
} else {
|
||||
let msg =
|
||||
format!("can't move column {} after itself", column.as_string());
|
||||
Err(ShellError::labeled_error(
|
||||
"can't move column",
|
||||
msg,
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
after_span,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
} else if let Some(before) = before {
|
||||
let member = columns.remove(0);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
let column_paths = vec![&member, &columns]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if before.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
.iter()
|
||||
.filter_map(|c| c.last())
|
||||
.map(|c| c.as_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(column) = before.last() {
|
||||
if !keys.contains(&column.as_string()) {
|
||||
ReturnSuccess::value(move_before(&item, &keys, &before)?)
|
||||
} else {
|
||||
let msg =
|
||||
format!("can't move column {} before itself", column.as_string());
|
||||
Err(ShellError::labeled_error(
|
||||
"can't move column",
|
||||
msg,
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"expected column",
|
||||
"expected column",
|
||||
before_span,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"no columns given",
|
||||
"no columns given",
|
||||
name,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn move_after(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
from_fields,
|
||||
));
|
||||
};
|
||||
|
||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||
if columns.contains(&name) {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
});
|
||||
|
||||
let mut reordered_columns = vec![];
|
||||
let mut insert = false;
|
||||
let mut inserted = false;
|
||||
|
||||
for name in columns_moved.into_iter() {
|
||||
if let Some(name) = name {
|
||||
reordered_columns.push(Some(name.clone()));
|
||||
|
||||
if !inserted && name == from {
|
||||
insert = true;
|
||||
}
|
||||
} else {
|
||||
reordered_columns.push(None);
|
||||
}
|
||||
|
||||
if insert {
|
||||
for column in columns {
|
||||
reordered_columns.push(Some(column.clone()));
|
||||
}
|
||||
inserted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(select_fields(
|
||||
table,
|
||||
&reordered_columns
|
||||
.into_iter()
|
||||
.filter_map(|v| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&table.tag,
|
||||
))
|
||||
}
|
||||
|
||||
fn move_before(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
from_fields,
|
||||
));
|
||||
};
|
||||
|
||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||
if columns.contains(&name) {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
});
|
||||
|
||||
let mut reordered_columns = vec![];
|
||||
let mut inserted = false;
|
||||
|
||||
for name in columns_moved.into_iter() {
|
||||
if let Some(name) = name {
|
||||
if !inserted && name == from {
|
||||
for column in columns {
|
||||
reordered_columns.push(Some(column.clone()));
|
||||
}
|
||||
|
||||
inserted = true;
|
||||
}
|
||||
|
||||
reordered_columns.push(Some(name.clone()));
|
||||
} else {
|
||||
reordered_columns.push(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(select_fields(
|
||||
table,
|
||||
&reordered_columns
|
||||
.into_iter()
|
||||
.filter_map(|v| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&table.tag,
|
||||
))
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
mod column;
|
||||
mod command;
|
||||
pub mod mv;
|
||||
|
||||
pub use column::SubCommand as MoveColumn;
|
||||
pub use command::Command as Move;
|
||||
pub use mv::Mv;
|
||||
|
@ -213,7 +213,8 @@ pub async fn fetch(
|
||||
)),
|
||||
};
|
||||
|
||||
let res = std::fs::read(location)?;
|
||||
let res = std::fs::read(location)
|
||||
.map_err(|_| ShellError::labeled_error("Can't open filename given", "can't open", span))?;
|
||||
|
||||
// If no encoding is provided we try to guess the encoding to read the file with
|
||||
let encoding = if encoding_choice.is_none() {
|
||||
|
@ -87,7 +87,7 @@ async fn process_row(
|
||||
let row_clone = row.clone();
|
||||
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
|
||||
|
||||
let scope = Scope::append_it(scope, row);
|
||||
let scope = Scope::append_var(scope, "$it", row);
|
||||
|
||||
Ok(run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await?)
|
||||
}
|
||||
@ -145,7 +145,7 @@ async fn reduce(
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
let scope = Scope::append_var(scope, "$acc".into(), f);
|
||||
let scope = Scope::append_var(scope, "$acc", f);
|
||||
process_row(block, scope, context, row).await
|
||||
}
|
||||
})
|
||||
@ -173,7 +173,7 @@ async fn reduce(
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
let scope = Scope::append_var(scope, "$acc".into(), f);
|
||||
let scope = Scope::append_var(scope, "$acc", f);
|
||||
process_row(block, scope, context, row).await
|
||||
}
|
||||
})
|
||||
|
@ -4,29 +4,22 @@ use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Scope, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_protocol::{hir::Block, PositionalType, Scope, Signature, UntaggedValue};
|
||||
|
||||
#[derive(new, Clone)]
|
||||
pub struct AliasCommand {
|
||||
name: String,
|
||||
args: Vec<(String, SyntaxShape)>,
|
||||
sig: Signature,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AliasCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
&self.sig.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
let mut alias = Signature::build(&self.name);
|
||||
|
||||
for (arg, shape) in &self.args {
|
||||
alias = alias.optional(arg, *shape, "");
|
||||
}
|
||||
|
||||
alias
|
||||
self.sig.clone()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -43,7 +36,7 @@ impl WholeStreamCommand for AliasCommand {
|
||||
let mut block = self.block.clone();
|
||||
block.set_redirect(call_info.args.external_redirection);
|
||||
|
||||
let alias_command = self.clone();
|
||||
// let alias_command = self.clone();
|
||||
let mut context = EvaluationContext::from_args(&args, ®istry);
|
||||
let input = args.input;
|
||||
|
||||
@ -51,21 +44,27 @@ impl WholeStreamCommand for AliasCommand {
|
||||
let evaluated = call_info.evaluate(®istry).await?;
|
||||
|
||||
let mut vars = IndexMap::new();
|
||||
|
||||
let mut num_positionals = 0;
|
||||
if let Some(positional) = &evaluated.args.positional {
|
||||
num_positionals = positional.len();
|
||||
for (pos, arg) in positional.iter().enumerate() {
|
||||
vars.insert(alias_command.args[pos].0.to_string(), arg.clone());
|
||||
for (idx, arg) in positional.iter().enumerate() {
|
||||
let pos_type = &self.sig.positional[idx].0;
|
||||
match pos_type {
|
||||
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
|
||||
vars.insert(name.clone(), arg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if alias_command.args.len() > num_positionals {
|
||||
for idx in 0..(alias_command.args.len() - num_positionals) {
|
||||
vars.insert(
|
||||
alias_command.args[idx + num_positionals].0.to_string(),
|
||||
UntaggedValue::nothing().into_untagged_value(),
|
||||
);
|
||||
//Fill out every missing argument with empty value
|
||||
if self.sig.positional.len() > num_positionals {
|
||||
for idx in num_positionals..self.sig.positional.len() {
|
||||
let pos_type = &self.sig.positional[idx].0;
|
||||
match pos_type {
|
||||
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
|
||||
vars.insert(name.clone(), UntaggedValue::nothing().into_untagged_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
327
crates/nu-cli/src/commands/seq.rs
Normal file
327
crates/nu-cli/src/commands/seq.rs
Normal file
@ -0,0 +1,327 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::value::StrExt;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::cmp;
|
||||
|
||||
pub struct Seq;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SeqArgs {
|
||||
rest: Vec<Tagged<f64>>,
|
||||
separator: Option<Tagged<String>>,
|
||||
terminator: Option<Tagged<String>>,
|
||||
widths: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Seq {
|
||||
fn name(&self) -> &str {
|
||||
"seq"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("seq")
|
||||
.rest(SyntaxShape::Number, "sequence values")
|
||||
.named(
|
||||
"separator",
|
||||
SyntaxShape::String,
|
||||
"separator character (defaults to \\n)",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"terminator",
|
||||
SyntaxShape::String,
|
||||
"terminator character (defaults to \\n)",
|
||||
Some('t'),
|
||||
)
|
||||
.switch(
|
||||
"widths",
|
||||
"equalize widths of all numbers by padding with zeros",
|
||||
Some('w'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"print sequences of numbers"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
seq(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "sequence 1 to 10 with newline separator",
|
||||
example: "seq 1 10",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("1").into(),
|
||||
UntaggedValue::string("2").into(),
|
||||
UntaggedValue::string("3").into(),
|
||||
UntaggedValue::string("4").into(),
|
||||
UntaggedValue::string("5").into(),
|
||||
UntaggedValue::string("6").into(),
|
||||
UntaggedValue::string("7").into(),
|
||||
UntaggedValue::string("8").into(),
|
||||
UntaggedValue::string("9").into(),
|
||||
UntaggedValue::string("10").into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "sequence 1 to 10 with pipe separator",
|
||||
example: "seq -s '|' 1 10",
|
||||
result: Some(vec![Value::from("1|2|3|4|5|6|7|8|9|10")]),
|
||||
},
|
||||
Example {
|
||||
description: "sequence 1 to 10 with pipe separator padded with 0",
|
||||
example: "seq -s '|' -w 1 10",
|
||||
result: Some(vec![Value::from("01|02|03|04|05|06|07|08|09|10")]),
|
||||
},
|
||||
Example {
|
||||
description: "sequence 1 to 10 with pipe separator padded by 2s",
|
||||
example: "seq -s ' | ' -w 1 2 10",
|
||||
result: Some(vec![Value::from("01 | 03 | 05 | 07 | 09")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn seq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
SeqArgs {
|
||||
rest: rest_nums,
|
||||
separator,
|
||||
terminator,
|
||||
widths,
|
||||
},
|
||||
_,
|
||||
) = args.process(®istry).await?;
|
||||
|
||||
if rest_nums.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"seq requires some parameters",
|
||||
"needs parameter",
|
||||
name,
|
||||
));
|
||||
}
|
||||
|
||||
let sep: String = match separator {
|
||||
Some(s) => {
|
||||
if s.item == r"\t" {
|
||||
'\t'.to_string()
|
||||
} else if s.item == r"\n" {
|
||||
'\n'.to_string()
|
||||
} else if s.item == r"\r" {
|
||||
'\r'.to_string()
|
||||
} else {
|
||||
let vec_s: Vec<char> = s.chars().collect();
|
||||
if vec_s.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a single separator char from --separator",
|
||||
"requires a single character string input",
|
||||
&s.tag,
|
||||
));
|
||||
};
|
||||
vec_s.iter().collect()
|
||||
}
|
||||
}
|
||||
_ => '\n'.to_string(),
|
||||
};
|
||||
|
||||
let term: String = match terminator {
|
||||
Some(t) => {
|
||||
if t.item == r"\t" {
|
||||
'\t'.to_string()
|
||||
} else if t.item == r"\n" {
|
||||
'\n'.to_string()
|
||||
} else if t.item == r"\r" {
|
||||
'\r'.to_string()
|
||||
} else {
|
||||
let vec_t: Vec<char> = t.chars().collect();
|
||||
if vec_t.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a single terminator char from --terminator",
|
||||
"requires a single character string input",
|
||||
&t.tag,
|
||||
));
|
||||
};
|
||||
vec_t.iter().collect()
|
||||
}
|
||||
}
|
||||
_ => '\n'.to_string(),
|
||||
};
|
||||
|
||||
let rest_nums: Vec<String> = rest_nums.iter().map(|n| n.item.to_string()).collect();
|
||||
|
||||
run_seq(sep, Some(term), widths.item, rest_nums)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Seq;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Seq {})?)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_float(mut s: &str) -> Result<f64, String> {
|
||||
if s.starts_with('+') {
|
||||
s = &s[1..];
|
||||
}
|
||||
match s.parse() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) => Err(format!(
|
||||
"seq: invalid floating point argument `{}`: {}",
|
||||
s, e
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_sequences(s: &str) -> String {
|
||||
s.replace("\\n", "\n").replace("\\t", "\t")
|
||||
}
|
||||
|
||||
pub fn run_seq(
|
||||
sep: String,
|
||||
termy: Option<String>,
|
||||
widths: bool,
|
||||
free: Vec<String>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut largest_dec = 0;
|
||||
let mut padding = 0;
|
||||
let first = if free.len() > 1 {
|
||||
let slice = &free[0][..];
|
||||
let len = slice.len();
|
||||
let dec = slice.find('.').unwrap_or(len);
|
||||
largest_dec = len - dec;
|
||||
padding = dec;
|
||||
match parse_float(slice) {
|
||||
Ok(n) => n,
|
||||
Err(s) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
s,
|
||||
"error parsing float",
|
||||
Tag::unknown(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let step = if free.len() > 2 {
|
||||
let slice = &free[1][..];
|
||||
let len = slice.len();
|
||||
let dec = slice.find('.').unwrap_or(len);
|
||||
largest_dec = cmp::max(largest_dec, len - dec);
|
||||
padding = cmp::max(padding, dec);
|
||||
match parse_float(slice) {
|
||||
Ok(n) => n,
|
||||
Err(s) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
s,
|
||||
"error parsing float",
|
||||
Tag::unknown(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let last = {
|
||||
let slice = &free[free.len() - 1][..];
|
||||
padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len()));
|
||||
match parse_float(slice) {
|
||||
Ok(n) => n,
|
||||
Err(s) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
s,
|
||||
"error parsing float",
|
||||
Tag::unknown(),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
if largest_dec > 0 {
|
||||
largest_dec -= 1;
|
||||
}
|
||||
let separator = escape_sequences(&sep[..]);
|
||||
let terminator = match termy {
|
||||
Some(term) => escape_sequences(&term[..]),
|
||||
None => separator.clone(),
|
||||
};
|
||||
print_seq(
|
||||
first,
|
||||
step,
|
||||
last,
|
||||
largest_dec,
|
||||
separator,
|
||||
terminator,
|
||||
widths,
|
||||
padding,
|
||||
)
|
||||
}
|
||||
|
||||
fn done_printing(next: f64, step: f64, last: f64) -> bool {
|
||||
if step >= 0f64 {
|
||||
next > last
|
||||
} else {
|
||||
next < last
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_seq(
|
||||
first: f64,
|
||||
step: f64,
|
||||
last: f64,
|
||||
largest_dec: usize,
|
||||
separator: String,
|
||||
terminator: String,
|
||||
pad: bool,
|
||||
padding: usize,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut i = 0isize;
|
||||
let mut value = first + i as f64 * step;
|
||||
let mut ret_str = "".to_owned();
|
||||
while !done_printing(value, step, last) {
|
||||
let istr = format!("{:.*}", largest_dec, value);
|
||||
let ilen = istr.len();
|
||||
let before_dec = istr.find('.').unwrap_or(ilen);
|
||||
if pad && before_dec < padding {
|
||||
for _ in 0..(padding - before_dec) {
|
||||
ret_str.push_str("0");
|
||||
}
|
||||
}
|
||||
ret_str.push_str(&istr);
|
||||
i += 1;
|
||||
value = first + i as f64 * step;
|
||||
if !done_printing(value, step, last) {
|
||||
ret_str.push_str(&separator);
|
||||
}
|
||||
}
|
||||
|
||||
if (first >= last && step < 0f64) || (first <= last && step > 0f64) {
|
||||
ret_str.push_str(&terminator);
|
||||
}
|
||||
|
||||
let rows: Vec<Value> = ret_str
|
||||
.lines()
|
||||
.map(|v| v.to_str_value_create_tag())
|
||||
.collect();
|
||||
Ok(futures::stream::iter(rows.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
}
|
@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
.skip_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
|
@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
let item = item.clone();
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
||||
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||
trace!("ITEM = {:?}", item);
|
||||
|
||||
async move {
|
||||
|
@ -101,9 +101,10 @@ pub fn split(
|
||||
mod tests {
|
||||
use super::split;
|
||||
use super::ShellError;
|
||||
use nu_data::utils::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::committers_grouped_by_date;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::*;
|
||||
use nu_test_support::value::{date, int, row, string, table};
|
||||
|
||||
#[test]
|
||||
fn splits_inner_tables_by_key() {
|
||||
|
@ -112,8 +112,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -46,8 +46,8 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{to_camel_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -46,8 +46,8 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{to_kebab_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -46,8 +46,8 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{to_pascal_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -46,8 +46,8 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{to_screaming_snake_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -46,8 +46,8 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{to_snake_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -130,9 +130,9 @@ fn action(
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -100,8 +100,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -149,8 +149,8 @@ fn action(
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, FindReplace, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -248,9 +248,9 @@ fn process_range(input: &Value, range: &Value) -> Result<IndexOfOptionalBounds,
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -145,9 +145,9 @@ fn action(
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -145,9 +145,9 @@ fn action(
|
||||
mod tests {
|
||||
use super::{action, SubCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -101,8 +101,8 @@ fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Valu
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, Replace, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -285,8 +285,8 @@ fn process_arguments(range: Value, name: impl Into<Tag>) -> Result<(isize, isize
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand, Substring};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -176,9 +176,9 @@ fn action(
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, DatetimeFormat, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -114,8 +114,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::{decimal_from_float, string};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::{decimal_from_float, string};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -5,15 +5,16 @@ use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use std::str::FromStr;
|
||||
use num_traits::Num;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
radix: Option<Tagged<u32>>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
@ -25,10 +26,12 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str to-int").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into integer by column paths",
|
||||
)
|
||||
Signature::build("str to-int")
|
||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into integer by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -44,11 +47,28 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert to an integer",
|
||||
example: "echo '255' | str to-int",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert to an integer",
|
||||
example: "echo '255' | str to-int",
|
||||
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Convert str column to an integer",
|
||||
example: "echo [['count']; ['255']] | str to-int count | get count",
|
||||
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Convert to integer from binary",
|
||||
example: "echo '1101' | str to-int -r 2",
|
||||
result: Some(vec![UntaggedValue::int(13).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Convert to integer from hex",
|
||||
example: "echo 'FF' | str to-int -r 16",
|
||||
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,21 +78,23 @@ async fn operate(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (Arguments { rest }, input) = args.process(®istry).await?;
|
||||
let (Arguments { rest, radix }, input) = args.process(®istry).await?;
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
let radix = radix.map(|r| r.item).unwrap_or(10);
|
||||
|
||||
let column_paths: Vec<ColumnPath> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag())?)
|
||||
ReturnSuccess::value(action(&v, v.tag(), radix)?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag())),
|
||||
Box::new(move |old| action(old, old.tag(), radix)),
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -82,21 +104,54 @@ async fn operate(
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
fn action(input: &Value, tag: impl Into<Tag>, radix: u32) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let other = s.trim();
|
||||
let out = match BigInt::from_str(other) {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as an integer",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
let trimmed = s.trim();
|
||||
|
||||
let out = match trimmed {
|
||||
b if b.starts_with("0b") => {
|
||||
let num = match BigInt::from_str_radix(b.trim_start_matches("0b"), 2) {
|
||||
Ok(n) => n,
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as integer",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
};
|
||||
UntaggedValue::int(num)
|
||||
}
|
||||
h if h.starts_with("0x") => {
|
||||
let num = match BigInt::from_str_radix(h.trim_start_matches("0x"), 16) {
|
||||
Ok(n) => n,
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as int",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
};
|
||||
UntaggedValue::int(num)
|
||||
}
|
||||
_ => {
|
||||
let num = match BigInt::from_str_radix(trimmed, radix) {
|
||||
Ok(n) => n,
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as int",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
};
|
||||
UntaggedValue::int(num)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
other => {
|
||||
@ -114,8 +169,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::{int, string};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::{int, string};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
@ -129,15 +184,29 @@ mod tests {
|
||||
let word = string("10");
|
||||
let expected = int(10);
|
||||
|
||||
let actual = action(&word, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, Tag::unknown(), 10).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_binary_to_integer() {
|
||||
let s = string("0b101");
|
||||
let actual = action(&s, Tag::unknown(), 10).unwrap();
|
||||
assert_eq!(actual, int(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_hex_to_integer() {
|
||||
let s = string("0xFF");
|
||||
let actual = action(&s, Tag::unknown(), 16).unwrap();
|
||||
assert_eq!(actual, int(255));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||
let integer_str = string("36anra");
|
||||
|
||||
let actual = action(&integer_str, Tag::unknown());
|
||||
let actual = action(&integer_str, Tag::unknown(), 10);
|
||||
|
||||
assert!(actual.is_err());
|
||||
}
|
||||
|
@ -65,11 +65,9 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{trim, SubCommand};
|
||||
use crate::commands::str_::trim::{action, ActionMode};
|
||||
use nu_plugin::{
|
||||
row,
|
||||
test_helpers::value::{int, string, table},
|
||||
};
|
||||
use nu_protocol::row;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::{int, string, table};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -66,11 +66,9 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{trim_left, SubCommand};
|
||||
use crate::commands::str_::trim::{action, ActionMode};
|
||||
use nu_plugin::{
|
||||
row,
|
||||
test_helpers::value::{int, string, table},
|
||||
};
|
||||
use nu_protocol::row;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::{int, string, table};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -66,11 +66,9 @@ mod tests {
|
||||
use super::ShellError;
|
||||
use super::{trim_right, SubCommand};
|
||||
use crate::commands::str_::trim::{action, ActionMode};
|
||||
use nu_plugin::{
|
||||
row,
|
||||
test_helpers::value::{int, string, table},
|
||||
};
|
||||
use nu_protocol::row;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::{int, string, table};
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -100,8 +100,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
|
@ -59,6 +59,8 @@ pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||
Ok(m) if m == "compact_double" => nu_table::Theme::compact_double(),
|
||||
Ok(m) if m == "rounded" => nu_table::Theme::rounded(),
|
||||
Ok(m) if m == "reinforced" => nu_table::Theme::reinforced(),
|
||||
Ok(m) if m == "heavy" => nu_table::Theme::heavy(),
|
||||
Ok(m) if m == "none" => nu_table::Theme::none(),
|
||||
_ => nu_table::Theme::compact(),
|
||||
})
|
||||
}
|
||||
|
@ -7,6 +7,11 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct ToMarkdown;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ToMarkdownArgs {
|
||||
pretty: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToMarkdown {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,7 +19,11 @@ impl WholeStreamCommand for ToMarkdown {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to md")
|
||||
Signature::build("to md").switch(
|
||||
"pretty",
|
||||
"Formats the Markdown table to vertically align items",
|
||||
Some('p'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -28,55 +37,161 @@ impl WholeStreamCommand for ToMarkdown {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_md(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an unformatted md string representing the contents of ls",
|
||||
example: "ls | to md",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Outputs a formatted md string representing the contents of ls",
|
||||
example: "ls | to md -p",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_md(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let name_tag = args.name_tag();
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (ToMarkdownArgs { pretty }, input) = args.process(®istry).await?;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = String::new();
|
||||
|
||||
let mut escaped_headers: Vec<String> = Vec::new();
|
||||
let mut column_widths: Vec<usize> = Vec::new();
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
output_string.push_str(&htmlescape::encode_minimal(&header));
|
||||
output_string.push_str("|");
|
||||
let escaped_header_string = htmlescape::encode_minimal(&header);
|
||||
column_widths.push(escaped_header_string.len());
|
||||
escaped_headers.push(escaped_header_string);
|
||||
}
|
||||
output_string.push_str("\n|");
|
||||
for _ in &headers {
|
||||
output_string.push_str("-");
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
|
||||
for row in input {
|
||||
match row.value {
|
||||
let mut escaped_rows: Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for row in &input {
|
||||
let mut escaped_row: Vec<String> = Vec::new();
|
||||
|
||||
match row.value.clone() {
|
||||
UntaggedValue::Row(row) => {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
let data = row.get_data(header);
|
||||
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
|
||||
output_string.push_str("|");
|
||||
for i in 0..headers.len() {
|
||||
let data = row.get_data(&headers[i]);
|
||||
let value_string = format_leaf(data.borrow()).plain_string(100_000);
|
||||
let new_column_width = value_string.len();
|
||||
|
||||
escaped_row.push(value_string);
|
||||
|
||||
if column_widths[i] < new_column_width {
|
||||
column_widths[i] = new_column_width;
|
||||
}
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
p => {
|
||||
output_string.push_str(
|
||||
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
|
||||
);
|
||||
output_string.push_str("\n");
|
||||
let value_string =
|
||||
htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000));
|
||||
escaped_row.push(value_string);
|
||||
}
|
||||
}
|
||||
|
||||
escaped_rows.push(escaped_row);
|
||||
}
|
||||
|
||||
let output_string = get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty);
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_output_string(
|
||||
headers: &[String],
|
||||
rows: &[Vec<String>],
|
||||
column_widths: &[usize],
|
||||
pretty: bool,
|
||||
) -> String {
|
||||
let mut output_string = String::new();
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push_str(&get_padded_string(
|
||||
headers[i].clone(),
|
||||
column_widths[i],
|
||||
' ',
|
||||
));
|
||||
output_string.push_str(" ");
|
||||
} else {
|
||||
output_string.push_str(headers[i].as_str());
|
||||
}
|
||||
|
||||
output_string.push_str("|");
|
||||
}
|
||||
|
||||
output_string.push_str("\n|");
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push_str(&get_padded_string(
|
||||
String::from("-"),
|
||||
column_widths[i],
|
||||
'-',
|
||||
));
|
||||
output_string.push_str(" ");
|
||||
} else {
|
||||
output_string.push_str("-");
|
||||
}
|
||||
|
||||
output_string.push_str("|");
|
||||
}
|
||||
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
|
||||
for row in rows {
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
}
|
||||
|
||||
for i in 0..row.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' '));
|
||||
output_string.push_str(" ");
|
||||
} else {
|
||||
output_string.push_str(row[i].as_str());
|
||||
}
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
}
|
||||
}
|
||||
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
|
||||
output_string
|
||||
}
|
||||
|
||||
fn get_padded_string(text: String, desired_length: usize, padding_character: char) -> String {
|
||||
format!(
|
||||
"{}{}",
|
||||
text,
|
||||
padding_character
|
||||
.to_string()
|
||||
.repeat(desired_length - text.len())
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
|
@ -92,7 +92,7 @@ async fn process_row(
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let scope = Scope::append_it(scope, input.clone());
|
||||
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||
|
||||
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
||||
|
||||
@ -149,7 +149,7 @@ async fn process_row(
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match scope
|
||||
.it()
|
||||
.var("$it")
|
||||
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
|
||||
.replace_data_at_column_path(&field, replacement.clone())
|
||||
{
|
||||
|
@ -106,7 +106,7 @@ async fn where_command(
|
||||
.filter_map(move |input| {
|
||||
let condition = condition.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = Scope::append_it(scope.clone(), input.clone());
|
||||
let scope = Scope::append_var(scope.clone(), "$it", input.clone());
|
||||
|
||||
async move {
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
|
@ -65,6 +65,8 @@ fn pathext() -> Option<Vec<String>> {
|
||||
std::env::var_os("PATHEXT").map(|v| {
|
||||
v.to_string_lossy()
|
||||
.split(';')
|
||||
// Filter out empty tokens and ';' at the end
|
||||
.filter(|f| f.len() > 1)
|
||||
// Cut off the leading '.' character
|
||||
.map(|ext| ext[1..].to_string())
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -38,7 +38,7 @@ impl<'s> Flatten<'s> {
|
||||
.collect(),
|
||||
Expression::Command => vec![LocationType::Command.spanned(e.span)],
|
||||
Expression::Path(path) => self.expression(&path.head),
|
||||
Expression::Variable(_) => vec![LocationType::Variable.spanned(e.span)],
|
||||
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
|
||||
|
||||
Expression::Boolean(_)
|
||||
| Expression::FilePath(_)
|
||||
|
@ -32,7 +32,7 @@ pub(crate) async fn evaluate_baseline_expr(
|
||||
Expression::Synthetic(hir::Synthetic::String(s)) => {
|
||||
Ok(UntaggedValue::string(s).into_untagged_value())
|
||||
}
|
||||
Expression::Variable(var) => evaluate_reference(&var, scope, tag),
|
||||
Expression::Variable(var, _) => evaluate_reference(&var, scope, tag),
|
||||
Expression::Command => unimplemented!(),
|
||||
Expression::Invocation(block) => evaluate_invocation(block, registry, scope).await,
|
||||
Expression::ExternalCommand(_) => unimplemented!(),
|
||||
@ -172,7 +172,7 @@ pub(crate) async fn evaluate_baseline_expr(
|
||||
|
||||
Ok(item.value.into_value(tag))
|
||||
}
|
||||
Expression::Boolean(_boolean) => unimplemented!(),
|
||||
Expression::Boolean(_boolean) => Ok(UntaggedValue::boolean(*_boolean).into_value(tag)),
|
||||
Expression::Garbage => unimplemented!(),
|
||||
}
|
||||
}
|
||||
@ -199,38 +199,36 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_reference(
|
||||
name: &hir::Variable,
|
||||
scope: Arc<Scope>,
|
||||
tag: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
fn evaluate_reference(name: &str, scope: Arc<Scope>, tag: Tag) -> Result<Value, ShellError> {
|
||||
match name {
|
||||
hir::Variable::It(_) => match scope.it() {
|
||||
"$nu" => crate::evaluate::variables::nu(&scope.env(), tag),
|
||||
|
||||
"$true" => Ok(Value {
|
||||
value: UntaggedValue::boolean(true),
|
||||
tag,
|
||||
}),
|
||||
|
||||
"$false" => Ok(Value {
|
||||
value: UntaggedValue::boolean(false),
|
||||
tag,
|
||||
}),
|
||||
|
||||
"$it" => match scope.var("$it") {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"$it variable not in scope",
|
||||
"not in scope (are you missing an 'each'?)",
|
||||
"Variable not in scope",
|
||||
"missing '$it' (note: $it is only available inside of a block)",
|
||||
tag.span,
|
||||
)),
|
||||
},
|
||||
hir::Variable::Other(name, _) => match name {
|
||||
x if x == "$nu" => crate::evaluate::variables::nu(&scope.env(), tag),
|
||||
x if x == "$true" => Ok(Value {
|
||||
value: UntaggedValue::boolean(true),
|
||||
tag,
|
||||
}),
|
||||
x if x == "$false" => Ok(Value {
|
||||
value: UntaggedValue::boolean(false),
|
||||
tag,
|
||||
}),
|
||||
x => match scope.var(x) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Variable not in scope",
|
||||
"unknown variable",
|
||||
tag.span,
|
||||
)),
|
||||
},
|
||||
|
||||
x => match scope.var(x) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Variable not in scope",
|
||||
"unknown variable",
|
||||
tag.span,
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -244,7 +242,7 @@ async fn evaluate_invocation(
|
||||
let mut context = EvaluationContext::basic()?;
|
||||
context.registry = registry.clone();
|
||||
|
||||
let input = match scope.it() {
|
||||
let input = match scope.var("$it") {
|
||||
Some(it) => InputStream::one(it),
|
||||
None => InputStream::empty(),
|
||||
};
|
||||
|
@ -7,17 +7,14 @@ use nu_source::{AnchorLocation, TaggedItem};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use indexmap::indexmap;
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::command::CommandArgs;
|
||||
use crate::commands::{
|
||||
whole_stream_command, BuildString, Command, Each, Echo, Get, Keep, StrCollect,
|
||||
WholeStreamCommand, Wrap,
|
||||
whole_stream_command, BuildString, Command, Each, Echo, First, Get, Keep, Last, Nth,
|
||||
StrCollect, WholeStreamCommand, Wrap,
|
||||
};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::stream::{InputStream, OutputStream};
|
||||
@ -37,9 +34,12 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
|
||||
// Minimal restricted commands to aid in testing
|
||||
whole_stream_command(Echo {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(First {}),
|
||||
whole_stream_command(Get {}),
|
||||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(Last {}),
|
||||
whole_stream_command(Nth {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
@ -150,9 +150,12 @@ pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
||||
whole_stream_command(MockEcho {}),
|
||||
whole_stream_command(MockLs {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(First {}),
|
||||
whole_stream_command(Get {}),
|
||||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(Last {}),
|
||||
whole_stream_command(Nth {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
@ -197,8 +200,7 @@ fn parse_line(line: &str, ctx: &mut EvaluationContext) -> Result<ClassifiedBlock
|
||||
let lite_result = nu_parser::lite_parse(&line, 0)?;
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
classified_block.block.expand_it_usage();
|
||||
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||
Ok(classified_block)
|
||||
}
|
||||
|
||||
@ -351,16 +353,33 @@ impl WholeStreamCommand for MockEcho {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => futures::stream::iter(
|
||||
table
|
||||
.into_iter()
|
||||
.map(move |mut v| {
|
||||
} => {
|
||||
if table.len() == 1 && table[0].is_table() {
|
||||
let mut values: Vec<Value> =
|
||||
table[0].table_entries().map(Clone::clone).collect();
|
||||
|
||||
for v in values.iter_mut() {
|
||||
v.tag = base_value.tag();
|
||||
v
|
||||
})
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream(),
|
||||
}
|
||||
|
||||
let subtable =
|
||||
vec![UntaggedValue::Table(values).into_value(base_value.tag())];
|
||||
|
||||
futures::stream::iter(subtable.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream()
|
||||
} else {
|
||||
futures::stream::iter(
|
||||
table
|
||||
.into_iter()
|
||||
.map(move |mut v| {
|
||||
v.tag = base_value.tag();
|
||||
v
|
||||
})
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream()
|
||||
}
|
||||
}
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: i.value.clone(),
|
||||
tag: base_value.tag,
|
||||
@ -427,10 +446,6 @@ fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
UntaggedValue::row(entries).into_untagged_value()
|
||||
}
|
||||
|
||||
fn date(input: impl Into<String>) -> Value {
|
||||
let key = input.into().tagged_unknown();
|
||||
crate::value::Date::naive_from_str(key.borrow_tagged())
|
||||
@ -440,30 +455,30 @@ fn date(input: impl Into<String>) -> Value {
|
||||
|
||||
fn file_listing() -> Vec<Value> {
|
||||
vec![
|
||||
row(indexmap! {
|
||||
"modified".to_string() => date("2019-07-23"),
|
||||
row! {
|
||||
"name".to_string() => string("Andrés.txt"),
|
||||
"type".to_string() => string("File"),
|
||||
"chickens".to_string() => int(10),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"modified".to_string() => date("2019-07-23"),
|
||||
"modified".to_string() => date("2019-07-23")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Jonathan"),
|
||||
"type".to_string() => string("Dir"),
|
||||
"chickens".to_string() => int(5),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"modified".to_string() => date("2019-09-24"),
|
||||
"modified".to_string() => date("2019-07-23")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Andrés.txt"),
|
||||
"type".to_string() => string("File"),
|
||||
"chickens".to_string() => int(20),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"modified".to_string() => date("2019-09-24"),
|
||||
"modified".to_string() => date("2019-09-24")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Yehuda"),
|
||||
"type".to_string() => string("Dir"),
|
||||
"chickens".to_string() => int(4),
|
||||
}),
|
||||
"modified".to_string() => date("2019-09-24")
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ mod path;
|
||||
mod plugin;
|
||||
mod shell;
|
||||
mod stream;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -68,7 +68,7 @@ macro_rules! trace_out_stream {
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use nu_protocol::{errln, out, outln};
|
||||
pub(crate) use nu_protocol::{errln, out, outln, row};
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
pub(crate) use crate::command_registry::CommandRegistry;
|
||||
|
@ -615,7 +615,7 @@ impl Shell for FilesystemShell {
|
||||
.map(|val| val.is_true())
|
||||
.unwrap_or(false);
|
||||
result = if _trash.item || (rm_always_trash && !_permanent.item) {
|
||||
trash::remove(&f).map_err(|e: trash::Error| {
|
||||
trash::delete(&f).map_err(|e: trash::Error| {
|
||||
Error::new(ErrorKind::Other, format!("{:?}", e))
|
||||
})
|
||||
} else if metadata.is_file() {
|
||||
@ -737,15 +737,14 @@ impl Shell for FilesystemShell {
|
||||
|
||||
let mut codec = MaybeTextCodec::new(with_encoding);
|
||||
|
||||
match codec.decode(&mut bytes_mut).map_err(|e| {
|
||||
ShellError::unexpected(format!("AsyncRead failed in open function: {:?}", e))
|
||||
match codec.decode(&mut bytes_mut).map_err(|_| {
|
||||
ShellError::labeled_error("Error opening file", "error opening file", name)
|
||||
})? {
|
||||
Some(sb) => Ok(futures::stream::iter(vec![Ok(sb)].into_iter()).boxed()),
|
||||
None => Ok(futures::stream::iter(vec![].into_iter()).boxed()),
|
||||
}
|
||||
} else {
|
||||
// We don't know that this is a finite file, so treat it as a stream
|
||||
|
||||
let f = std::fs::File::open(&path).map_err(|e| {
|
||||
ShellError::labeled_error(
|
||||
format!("Error opening file: {:?}", e),
|
||||
@ -755,8 +754,8 @@ impl Shell for FilesystemShell {
|
||||
})?;
|
||||
let async_reader = futures::io::AllowStdIo::new(f);
|
||||
let sob_stream = FramedRead::new(async_reader, MaybeTextCodec::new(with_encoding))
|
||||
.map_err(|e| {
|
||||
ShellError::unexpected(format!("AsyncRead failed in open function: {:?}", e))
|
||||
.map_err(move |_| {
|
||||
ShellError::labeled_error("Error opening file", "error opening file", name)
|
||||
})
|
||||
.into_stream();
|
||||
|
||||
@ -943,6 +942,7 @@ pub(crate) fn dir_entry_dict(
|
||||
"type",
|
||||
"target",
|
||||
"num_links",
|
||||
"inode",
|
||||
"readonly",
|
||||
"mode",
|
||||
"uid",
|
||||
@ -1020,6 +1020,9 @@ pub(crate) fn dir_entry_dict(
|
||||
let nlinks = md.nlink();
|
||||
dict.insert_untagged("num_links", UntaggedValue::string(nlinks.to_string()));
|
||||
|
||||
let inode = md.ino();
|
||||
dict.insert_untagged("inode", UntaggedValue::string(inode.to_string()));
|
||||
|
||||
if let Some(user) = users::get_user_by_uid(md.uid()) {
|
||||
dict.insert_untagged(
|
||||
"uid",
|
||||
|
@ -7,7 +7,7 @@ use nu_source::{Tagged, TaggedItem};
|
||||
pub struct InputStream {
|
||||
values: BoxStream<'static, Value>,
|
||||
|
||||
// Whether or not an empty stream was explicitly requeted via InputStream::empty
|
||||
// Whether or not an empty stream was explicitly requested via InputStream::empty
|
||||
empty: bool,
|
||||
}
|
||||
|
||||
|
1
crates/nu-cli/src/types.rs
Normal file
1
crates/nu-cli/src/types.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod deduction;
|
1103
crates/nu-cli/src/types/deduction.rs
Normal file
1103
crates/nu-cli/src/types/deduction.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,114 +1,299 @@
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
||||
#[test]
|
||||
fn alias_args_work() {
|
||||
Playground::setup("append_test_1", |dirs, _| {
|
||||
#[test]
|
||||
fn alias_without_args() {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias double_echo [a b] {echo $a $b}
|
||||
alias -i e [] {^echo hi nushell | to json}
|
||||
e
|
||||
"#
|
||||
);
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(actual.out, "\"hi nushell\\n\"");
|
||||
#[cfg(windows)]
|
||||
assert_eq!(actual.out, "\"hi nushell\\r\\n\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_args_work() {
|
||||
Playground::setup("append_test_2", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
alias -i double_echo [b] {echo $b | to json}
|
||||
double_echo 1kb
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "1024");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_args_double_echo() {
|
||||
Playground::setup("append_test_1", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
alias -i double_echo [a b] {echo $a $b}
|
||||
double_echo 1 2 | to json
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "[1,2]");
|
||||
})
|
||||
}
|
||||
assert_eq!(actual.out, "[1,2]");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_missing_args_work() {
|
||||
Playground::setup("append_test_1", |dirs, _| {
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn alias_parses_path_tilde() {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
alias double_echo [a b] {^echo $a $b}
|
||||
double_echo bob
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "bob");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn alias_parses_path_tilde() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
r#"
|
||||
alias -i new-cd [dir] { cd $dir }
|
||||
new-cd ~
|
||||
pwd
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
assert!(actual.out.contains("home"));
|
||||
#[cfg(target_os = "macos")]
|
||||
assert!(actual.out.contains("Users"));
|
||||
}
|
||||
//If this fails for you, check for any special unicode characters in your ~ path
|
||||
assert!(actual.out.chars().filter(|c| c.clone() == '/').count() == 2);
|
||||
#[cfg(target_os = "linux")]
|
||||
assert!(actual.out.contains("home"));
|
||||
#[cfg(target_os = "macos")]
|
||||
assert!(actual.out.contains("Users"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_shallow() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
#[test]
|
||||
fn alias_missing_args_work() {
|
||||
Playground::setup("append_test_1", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
alias double_echo [a b] {^echo $a $b}
|
||||
double_echo bob
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "bob");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_in_str_var_right() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lw [newbie] {echo 1 2 3 | where "hello_world" in $newbie | to json}
|
||||
lw [hello_world_test_repo]
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "[1,2,3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_with_in_str_var_right_mismatch() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lw [rust_newbie] { echo 1 2 3 | where "hello_world" in $rust_newbie | to json }
|
||||
lw [ big_brain_programmer ]
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_with_in_err() {
|
||||
//in operator only applicable for strings atm
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lw [p] {echo 1 2 3 | where $p in [1 3 2] | to json}
|
||||
lw /root/sys
|
||||
"#
|
||||
);
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_contains() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lw [p] {echo 1 2 3 | where $p in [1 hi 3] | to json}
|
||||
lw 1
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "[1,2,3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_contains_and_var_is_right_side() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lw [p] {echo 1 2 3 | where 1 in $p | to json}
|
||||
lw [1 2 hi]
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "[1,2,3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_shallow() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i round-to [num digits] { echo $num | str from -d $digits }
|
||||
round-to 3.45 a
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deep_invocation() {
|
||||
let actual = nu!(
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deep_invocation() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i round-to [nums digits] { echo $nums | each {= $(str from -d $digits)}}
|
||||
round-to 3.45 a
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deep_binary() {
|
||||
let actual = nu!(
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deep_binary() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i round-plus-one [nums digits] { echo $nums | each {= $(str from -d $digits | str to-decimal) + 1}}
|
||||
round-plus-one 3.45 a
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deeper_binary() {
|
||||
let actual = nu!(
|
||||
#[test]
|
||||
fn error_alias_wrong_shape_deeper_binary() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i round-one-more [num digits] { echo $num | str from -d $(= $digits + 1) }
|
||||
round-one-more 3.45 a
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
assert!(actual.err.contains("Type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_alias_syntax_shape_clash() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i clash [a] { echo 1.1 2 3 | each { str from -d $a } | range $a } }
|
||||
#[test]
|
||||
fn error_alias_syntax_shape_clash() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i clash [a] { echo 1.1 2 3 | each { str from -d $a } | range $a }
|
||||
"#
|
||||
);
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("alias"));
|
||||
assert!(actual.err.contains("Contrary types for variable $a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_math_var() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i echo_math [math] { echo {= 1 + $math}}
|
||||
echo_math 1 + 1 | to json
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_math_var2() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i round-plus-one [nums digits math] { echo $nums | each {= $(str from -d $digits | str to-decimal) + $math}}
|
||||
round-plus-one 3.45 2 1 + 1 | to json
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "5.45");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_with_true_and_false() {
|
||||
//https://github.com/nushell/nushell/issues/2416
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i is_empty [a] {if $(echo $a | empty?) == $true { echo $true } { echo $false }}
|
||||
is_empty ""
|
||||
"#
|
||||
);
|
||||
assert!(actual.out.contains("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_sent_env() {
|
||||
//https://github.com/nushell/nushell/issues/1835
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i set-env [name value] { echo $nu.env | insert $name $value | get SHELL | to json }
|
||||
set-env SHELL /bin/nu
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "\"/bin/nu\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn alias_with_math_arg() {
|
||||
//Failing
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
alias -i lswh [math] { echo 1 2 3 | where $math | to json }
|
||||
lswh $it > 2
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn alias_ls() {
|
||||
//https://github.com/nushell/nushell/issues/1632
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"
|
||||
touch /tmp/nushell_alias_test
|
||||
alias -i l [x] { ls $x }
|
||||
l /tmp | to json
|
||||
"#
|
||||
);
|
||||
assert!(actual.out.contains("nushell_alias_test"));
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ fn adds_a_row_to_the_end() {
|
||||
| lines
|
||||
| append "pollo loco"
|
||||
| nth 3
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -71,7 +71,7 @@ fn cal_sees_pipeline_year() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo 1020 | cal --full-year $it | get monday | first 3 | to json
|
||||
cal --full-year 1020 | get monday | first 3 | to json
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -10,7 +10,7 @@ fn filesystem_change_from_current_directory_using_relative_path() {
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
cd cd_test_1
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ fn filesystem_change_from_current_directory_using_absolute_path() {
|
||||
cwd: dirs.test(),
|
||||
r#"
|
||||
cd "{}"
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#,
|
||||
dirs.formats()
|
||||
);
|
||||
@ -44,7 +44,7 @@ fn filesystem_switch_back_to_previous_working_directory() {
|
||||
r#"
|
||||
cd {}
|
||||
cd -
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#,
|
||||
dirs.test()
|
||||
);
|
||||
@ -62,7 +62,7 @@ fn filesytem_change_from_current_directory_using_relative_path_and_dash() {
|
||||
cwd: dirs.test(),
|
||||
r#"
|
||||
cd odin/-
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -80,7 +80,7 @@ fn filesystem_change_current_directory_to_parent_directory() {
|
||||
cwd: dirs.test(),
|
||||
r#"
|
||||
cd ..
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -97,7 +97,7 @@ fn filesystem_change_current_directory_to_two_parents_up_using_multiple_dots() {
|
||||
cwd: dirs.test().join("foo/bar"),
|
||||
r#"
|
||||
cd ...
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -116,7 +116,7 @@ fn filesystem_change_current_directory_to_parent_directory_after_delete_cwd() {
|
||||
rm {}/foo/bar
|
||||
echo ","
|
||||
cd ..
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#,
|
||||
dirs.test()
|
||||
);
|
||||
@ -135,7 +135,7 @@ fn filesystem_change_to_home_directory() {
|
||||
cwd: dirs.test(),
|
||||
r#"
|
||||
cd ~
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -152,7 +152,7 @@ fn filesystem_change_to_a_directory_containing_spaces() {
|
||||
cwd: dirs.test(),
|
||||
r#"
|
||||
cd "robalino turner katz"
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -219,7 +219,7 @@ fn filesystem_change_directory_to_symlink_relative() {
|
||||
cwd: dirs.test().join("boo"),
|
||||
r#"
|
||||
cd ../foo_link
|
||||
pwd | echo $it
|
||||
echo $(pwd)
|
||||
"#
|
||||
);
|
||||
|
||||
@ -249,7 +249,7 @@ fn valuesystem_change_from_current_path_using_relative_path() {
|
||||
r#"
|
||||
enter sample.toml
|
||||
cd bin
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
@ -283,7 +283,7 @@ fn valuesystem_change_from_current_path_using_absolute_path() {
|
||||
enter sample.toml
|
||||
cd bin
|
||||
cd /dependencies
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
@ -319,7 +319,7 @@ fn valuesystem_switch_back_to_previous_working_path() {
|
||||
cd dependencies
|
||||
cd /bin
|
||||
cd -
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
@ -353,7 +353,7 @@ fn valuesystem_change_from_current_path_using_relative_path_and_dash() {
|
||||
cd package/-
|
||||
cd /bin
|
||||
cd -
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
@ -380,7 +380,7 @@ fn valuesystem_change_current_path_to_parent_path() {
|
||||
enter sample.toml
|
||||
cd package/emberenios
|
||||
cd ..
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
@ -405,7 +405,7 @@ fn valuesystem_change_to_a_path_containing_spaces() {
|
||||
r#"
|
||||
enter sample.toml
|
||||
cd "pa que te"
|
||||
pwd | echo $it
|
||||
pwd
|
||||
exit
|
||||
"#
|
||||
);
|
||||
|
@ -26,7 +26,6 @@ fn discards_rows_where_given_column_is_empty() {
|
||||
| get amigos
|
||||
| compact rusty_luck
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -43,7 +42,6 @@ fn discards_empty_rows_by_default() {
|
||||
| from json
|
||||
| compact
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -27,7 +27,6 @@ fn adds_row_data_if_column_missing() {
|
||||
| default rusty_luck 1
|
||||
| where rusty_luck == 1
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -4,7 +4,7 @@ use nu_test_support::{nu, pipeline};
|
||||
fn drop_rows() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum | echo $it"#
|
||||
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum "#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
|
@ -5,7 +5,7 @@ fn each_works_separately() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
|
||||
echo [1 2 3] | each { echo $it 10 | math sum } | to json
|
||||
"#
|
||||
));
|
||||
|
||||
@ -47,3 +47,27 @@ fn each_window_stride() {
|
||||
|
||||
assert_eq!(actual.out, "[[1,2,3],[3,4,5]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_no_args_in_block() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [[foo bar]; [a b] [c d] [e f]] | each { to json } | nth 1 | str collect
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, r#"{"foo":"c","bar":"d"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_implicit_it_in_block() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [[foo bar]; [a b] [c d] [e f]] | each { nu --testbin cococo $it.foo }
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "ace");
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ fn echo_range_is_lazy() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo 1..10000000000 | first 3 | echo $it | to json
|
||||
echo 1..10000000000 | first 3 | to json
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -75,7 +75,7 @@ fn passing_a_block_will_set_contents_on_empty_cells_and_leave_non_empty_ones_unt
|
||||
]
|
||||
| empty? LVL { = 9 }
|
||||
| empty? HP {
|
||||
get LVL | = $it * 1000
|
||||
= $it.LVL * 1000
|
||||
}
|
||||
| math sum
|
||||
| get HP
|
||||
|
@ -18,7 +18,6 @@ fn gets_first_rows_by_amount() {
|
||||
ls
|
||||
| first 3
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -42,7 +41,6 @@ fn gets_all_rows_if_amount_higher_than_all_rows() {
|
||||
ls
|
||||
| first 99
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -61,7 +59,6 @@ fn gets_first_row_when_no_amount_given() {
|
||||
ls
|
||||
| first
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
184
crates/nu-cli/tests/commands/flatten.rs
Normal file
184
crates/nu-cli/tests/commands/flatten.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn flatten_nested_tables_with_columns() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo [[origin, people]; [Ecuador, $(= 'Andres' | wrap name)]]
|
||||
[[origin, people]; [Nu, $(= 'nuno' | wrap name)]]
|
||||
| flatten
|
||||
| get name
|
||||
| str collect ','
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "Andres,nuno");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_nested_tables_that_have_many_columns() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]]
|
||||
[[origin, people]; [USA, $(echo [[name, meal]; ['Katz', 'nurepa']])]]
|
||||
| flatten
|
||||
| get meal
|
||||
| str collect ','
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "arepa,nurepa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_nested_tables() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo [[Andrés, Nicolás, Robalino]] | flatten | nth 1
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "Nicolás");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_row_column_explictly() {
|
||||
Playground::setup("flatten_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"katz.json",
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"people": {
|
||||
"name": "Andres",
|
||||
"meal": "arepa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"people": {
|
||||
"name": "Katz",
|
||||
"meal": "nurepa"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"open katz.json | flatten people | where name == Andres | count"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "1");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_row_columns_having_same_column_names_flats_separately() {
|
||||
Playground::setup("flatten_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"katz.json",
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"people": {
|
||||
"name": "Andres",
|
||||
"meal": "arepa"
|
||||
},
|
||||
"city": [{"name": "Guayaquil"}, {"name": "Samborondón"}]
|
||||
},
|
||||
{
|
||||
"people": {
|
||||
"name": "Katz",
|
||||
"meal": "nurepa"
|
||||
},
|
||||
"city": [{"name": "Oregon"}, {"name": "Brooklin"}]
|
||||
}
|
||||
]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"open katz.json | flatten | flatten people city | get city_name | count"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "4");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_table_columns_explictly() {
|
||||
Playground::setup("flatten_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"katz.json",
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"people": {
|
||||
"name": "Andres",
|
||||
"meal": "arepa"
|
||||
},
|
||||
"city": ["Guayaquil", "Samborondón"]
|
||||
},
|
||||
{
|
||||
"people": {
|
||||
"name": "Katz",
|
||||
"meal": "nurepa"
|
||||
},
|
||||
"city": ["Oregon", "Brooklin"]
|
||||
}
|
||||
]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"open katz.json | flatten city | where people.name == Katz | count"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "2");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_more_than_one_column_that_are_subtables_not_supported() {
|
||||
Playground::setup("flatten_test_4", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"katz.json",
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"people": {
|
||||
"name": "Andres",
|
||||
"meal": "arepa"
|
||||
}
|
||||
"tags": ["carbohydrate", "corn", "maiz"],
|
||||
"city": ["Guayaquil", "Samborondón"]
|
||||
},
|
||||
{
|
||||
"people": {
|
||||
"name": "Katz",
|
||||
"meal": "nurepa"
|
||||
},
|
||||
"tags": ["carbohydrate", "shell food", "amigos flavor"],
|
||||
"city": ["Oregon", "Brooklin"]
|
||||
}
|
||||
]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"open katz.json | flatten tags city"
|
||||
);
|
||||
|
||||
assert!(actual.err.contains("tried flattening"));
|
||||
assert!(actual.err.contains("but is flattened already"));
|
||||
})
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
@ -8,7 +10,6 @@ fn creates_the_resulting_string_from_the_given_fields() {
|
||||
open cargo_sample.toml
|
||||
| get package
|
||||
| format "{name} has license {license}"
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -22,7 +23,6 @@ fn given_fields_can_be_column_paths() {
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| format "{package.name} is {package.description}"
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -36,9 +36,31 @@ fn can_use_variables() {
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| format "{$it.package.name} is {$it.package.description}"
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "nu is a new type of shell");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_filesize_works() {
|
||||
Playground::setup("format_filesize_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("yehuda.txt"),
|
||||
EmptyFile("jonathan.txt"),
|
||||
EmptyFile("andres.txt"),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
ls
|
||||
| format filesize size KB
|
||||
| get size
|
||||
| first
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0.01 KB");
|
||||
})
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ fn fetches_a_row() {
|
||||
r#"
|
||||
open sample.toml
|
||||
| get nu_party_venue
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -44,7 +43,6 @@ fn fetches_by_index() {
|
||||
r#"
|
||||
open sample.toml
|
||||
| get package.authors.2
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -67,7 +65,6 @@ fn fetches_by_column_path() {
|
||||
r#"
|
||||
open sample.toml
|
||||
| get package.name
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -93,7 +90,6 @@ fn column_paths_are_either_double_quoted_or_regular_unquoted_words_separated_by_
|
||||
open sample.toml
|
||||
| get package."9999"
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -127,7 +123,6 @@ fn fetches_more_than_one_column_path() {
|
||||
open sample.toml
|
||||
| get fortune_tellers.2.name fortune_tellers.0.name fortune_tellers.1.name
|
||||
| nth 2
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -250,7 +245,7 @@ fn errors_fetching_by_index_out_of_bounds() {
|
||||
fn quoted_column_access() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
r#"echo '[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz | echo $it"#
|
||||
r#"echo '[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz "#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "4");
|
||||
|
@ -22,7 +22,6 @@ fn groups() {
|
||||
| group-by rusty_at
|
||||
| get "10/11/2013"
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -22,7 +22,6 @@ fn summarizes_by_column_given() {
|
||||
| histogram rusty_at countries
|
||||
| where rusty_at == "Ecuador"
|
||||
| get countries
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -55,7 +54,6 @@ fn summarizes_by_values() {
|
||||
| histogram
|
||||
| where value == "Estados Unidos"
|
||||
| get count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -8,7 +8,6 @@ fn sets_the_column_from_a_block_run_output() {
|
||||
open cargo_sample.toml
|
||||
| insert dev-dependencies.newdep "1"
|
||||
| get dev-dependencies.newdep
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -24,7 +23,6 @@ fn sets_the_column_from_a_block_full_stream_output() {
|
||||
| insert content { open --raw cargo_sample.toml | lines | first 5 }
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
@ -40,7 +38,6 @@ fn sets_the_column_from_an_invocation() {
|
||||
| insert content $(open --raw cargo_sample.toml | lines | first 5)
|
||||
| get content.1
|
||||
| str contains "nu"
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -5,7 +5,7 @@ fn into_int_filesize() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
into-int 1kb | = $it / 1024
|
||||
into-int 1kb | each {= $it / 1024 }
|
||||
"#
|
||||
));
|
||||
|
||||
@ -17,7 +17,7 @@ fn into_int_int() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
into-int 1024 | = $it / 1024
|
||||
into-int 1024 | each {= $it / 1024 }
|
||||
"#
|
||||
));
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user