forked from extern/nushell
Compare commits
167 Commits
Author | SHA1 | Date | |
---|---|---|---|
3ffa804088 | |||
98810d22b1 | |||
5e72b2a797 | |||
7e4e7fa4a6 | |||
d297199d7c | |||
17a433996e | |||
b9bb4692a4 | |||
d05dcdda02 | |||
27fe356214 | |||
fc44df1e45 | |||
77f915befe | |||
a5f7600f6f | |||
7eb8634ad7 | |||
452d8c06e9 | |||
48f535f02e | |||
43c10b0625 | |||
328b09fe04 | |||
15d49e4096 | |||
3ef53fe2cd | |||
7d8e759e98 | |||
69b3be61a4 | |||
79476a5cb2 | |||
f449baf8de | |||
5ff4bcfb7a | |||
98537ce8b7 | |||
d2a00a2daa | |||
f22938fc4a | |||
1e67ae8e94 | |||
c012d648fb | |||
67acaae53c | |||
e3da546e23 | |||
e5b136f70d | |||
058ef69da3 | |||
2a483531a4 | |||
05202671db | |||
8509873043 | |||
57a2d695e2 | |||
0b5ab1ef22 | |||
2eac79569c | |||
ac578b8491 | |||
5183fd25bb | |||
10f5a8ef78 | |||
a30837298d | |||
f377a3a7b4 | |||
83c874666a | |||
e000ed47cd | |||
af2f064f42 | |||
9c7b25134b | |||
2d15df9e6c | |||
d2ab287756 | |||
e73278990c | |||
12bc92df35 | |||
f19a801022 | |||
b193303aa3 | |||
e299e76fcf | |||
c857e18c4a | |||
5fb3df4054 | |||
8b597187fc | |||
930f9f0063 | |||
63d4df9810 | |||
13ba533fc4 | |||
6d60bab2fd | |||
5be774b2e5 | |||
b412ff92c0 | |||
5a75e11b0e | |||
e66bf70589 | |||
3924e9d50a | |||
8df748463d | |||
0113661c81 | |||
0ee054b14d | |||
80b39454ff | |||
97f3671e2c | |||
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 | |||
2df8775b48 | |||
e02b4f1443 | |||
194782215f | |||
df17d28c0f | |||
5f43c8f024 | |||
1a18734f9a | |||
4a70c1ff4f | |||
770e5d89f2 | |||
cfac8e84dd | |||
43d90c1745 | |||
38bdb053d2 | |||
95e61773a5 | |||
4e931fa73f | |||
2573441e28 | |||
5770b15270 | |||
6817b472d0 | |||
2b076369e0 | |||
973a8ee8f3 | |||
1159d3365a | |||
152ba32eb7 | |||
153320ef33 | |||
ff236da72c | |||
54326869e4 | |||
f14f4e39c5 | |||
a18b2702ca | |||
93410c470e | |||
5d945ef869 | |||
df07be6a42 | |||
3c32d4947c | |||
2ea5235aea | |||
c096f031ce | |||
ae1d4bdb4c | |||
0adf2accdd | |||
4201f48be5 | |||
b076e375ca | |||
2f1016d44f | |||
ddf9d61346 | |||
f0b7ab5ecc | |||
e4c6336bd4 | |||
66061192f8 | |||
b7bc4c1f80 | |||
a56abb6502 | |||
892a416211 | |||
f45adecd01 | |||
bd015e82dc | |||
cf43b74f26 | |||
18909ec14a | |||
ed243c88d2 | |||
cb7723f423 | |||
9dc88f8a95 | |||
0a439fe52f | |||
a8b65e35ec | |||
bd9e598bf0 | |||
75f8247af1 | |||
e8ec5027ff |
6
.cargo/config.toml
Normal file
6
.cargo/config.toml
Normal file
@ -0,0 +1,6 @@
|
||||
# use LLD as linker on Windows because it could be faster
|
||||
# for full and incremental builds compared to default
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
#linker = "lld-link.exe"
|
||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -23,8 +23,25 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Configuration (please complete the following information):**
|
||||
- OS (e.g. Windows):
|
||||
- Nu version (you can use the `version` command to find out):
|
||||
- Optional features (if any):
|
||||
|
||||
Add any other context about the problem here.
|
||||
Run `version | pivot` and paste the output to show OS, features, etc.
|
||||
|
||||
```
|
||||
> version | pivot
|
||||
╭───┬────────────────────┬───────────────────────────────────────────────────────────────────────╮
|
||||
│ # │ Column0 │ Column1 │
|
||||
├───┼────────────────────┼───────────────────────────────────────────────────────────────────────┤
|
||||
│ 0 │ version │ 0.24.1 │
|
||||
│ 1 │ build_os │ macos-x86_64 │
|
||||
│ 2 │ rust_version │ rustc 1.48.0 │
|
||||
│ 3 │ cargo_version │ cargo 1.48.0 │
|
||||
│ 4 │ pkg_version │ 0.24.1 │
|
||||
│ 5 │ build_time │ 2020-12-18 09:54:09 │
|
||||
│ 6 │ build_rust_channel │ release │
|
||||
│ 7 │ features │ ctrlc, default, directories, dirs, git, ichwh, ptree, rich-benchmark, │
|
||||
│ │ │ rustyline, term, uuid, which, zip │
|
||||
╰───┴────────────────────┴───────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
|
||||
**Add any other context about the problem here.**
|
||||
|
6
.gitpod.Dockerfile
vendored
6
.gitpod.Dockerfile
vendored
@ -1,5 +1,9 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
|
||||
# To force a rebuild, simply increase this counter:
|
||||
ENV TRIGGER_REBUILD 1
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get update && \
|
||||
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
|
||||
rust-lldb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUST_LLDB=/usr/bin/lldb-8
|
||||
ENV RUST_LLDB=/usr/bin/lldb-11
|
||||
|
@ -31,6 +31,11 @@ cargo build
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
- Build and run with extra features:
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
@ -60,3 +65,11 @@ cargo build
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
### Debugging Tips
|
||||
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
|
||||
```
|
||||
|
2669
Cargo.lock
generated
2669
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
165
Cargo.toml
165
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.20.0"
|
||||
version = "0.25.1"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,51 +18,60 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.20.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.20.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
|
||||
nu-cli = {version = "0.25.1", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.25.1", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.25.1", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.25.1", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.25.1", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.25.1", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.25.1", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.25.1", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.20.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.20.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.20.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.20.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.20.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.20.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.20.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
|
||||
crossterm = {version = "0.17", optional = true}
|
||||
semver = {version = "0.10.0", optional = true}
|
||||
url = {version = "2.1.1", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.25.1", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.25.1", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.25.1", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.25.1", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.25.1", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.25.1", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.25.1", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.25.1", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.25.1", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.25.1", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_selector = {version = "0.25.1", path = "./crates/nu_plugin_selector", optional = true}
|
||||
nu_plugin_start = {version = "0.25.1", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.25.1", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.25.1", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.25.1", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.25.1", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.25.1", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.25.1", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = "3.1.6"
|
||||
dunce = "1.0.1"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
quick-xml = "0.18.1"
|
||||
syn = "=1.0.57"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.20.0", path = "./crates/nu-test-support"}
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = {version = "0.25.1", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
@ -80,39 +89,43 @@ default = [
|
||||
"post",
|
||||
"fetch",
|
||||
"rich-benchmark",
|
||||
"zip-support",
|
||||
]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
|
||||
stable = ["default"]
|
||||
|
||||
# Default
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["crossterm", "url", "nu_plugin_textview"]
|
||||
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
trace = ["nu-parser/trace"]
|
||||
|
||||
# Stable (Default)
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
inc = ["nu_plugin_inc"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
zip-support = ["nu-cli/zip"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
|
||||
[profile.release]
|
||||
#strip = "symbols" #Couldn't get working +nightly
|
||||
codegen-units = 1 #Reduce parallel codegen units
|
||||
lto = true #Link Time Optimization
|
||||
opt-level = 'z' #Optimize for size
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -174,6 +187,46 @@ name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_bar"
|
||||
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_line"
|
||||
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_xpath"
|
||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||
required-features = ["xpath"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_selector"
|
||||
path = "src/plugins/nu_plugin_extra_selector.rs"
|
||||
required-features = ["selector"]
|
||||
|
||||
[[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"
|
||||
|
20
README.md
20
README.md
@ -7,7 +7,7 @@
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||
|
||||
## Nu Shell
|
||||
## Nushell
|
||||
|
||||
A new type of shell.
|
||||
|
||||
@ -34,7 +34,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
|
||||
|
||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
||||
|
||||
You can also find more learning resources in our [documentation](https://www.nushell.sh/documentation.html) site.
|
||||
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
Try it in Gitpod.
|
||||
|
||||
@ -44,9 +44,9 @@ Try it in Gitpod.
|
||||
|
||||
### Local
|
||||
|
||||
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.
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/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:
|
||||
|
||||
@ -64,7 +64,7 @@ To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/en/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```bash
|
||||
cargo build --workspace --features=extra
|
||||
@ -219,22 +219,22 @@ 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.
|
||||
|
||||
### Configuration
|
||||
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/en/configuration.html).
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
|
||||
|
||||
To set one of these variables, you can use `config set`. For example:
|
||||
|
||||
@ -307,7 +307,7 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
||||
|
||||
## Current Roadmap
|
||||
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -1,34 +1,40 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.20.0"
|
||||
version = "0.25.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = {version = "0.20.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.20.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.20.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.20.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.20.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.20.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.20.0", path = "../nu-value-ext"}
|
||||
nu-data = {version = "0.25.1", path = "../nu-data"}
|
||||
nu-errors = {version = "0.25.1", path = "../nu-errors"}
|
||||
nu-json = {version = "0.25.1", path = "../nu-json"}
|
||||
nu-parser = {version = "0.25.1", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.25.1", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.25.1", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.25.1", path = "../nu-source"}
|
||||
nu-stream = {version = "0.25.1", path = "../nu-stream"}
|
||||
nu-table = {version = "0.25.1", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.25.1", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.25.1", path = "../nu-value-ext"}
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
arboard = {version = "1.1.0", optional = true}
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.40"
|
||||
base64 = "0.12.3"
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "0.5.6"
|
||||
calamine = "0.16.1"
|
||||
chrono = {version = "0.4.15", features = ["serde"]}
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1.3"
|
||||
@ -36,9 +42,10 @@ ctrlc = {version = "3.1.6", optional = true}
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "3.0.1", optional = true}
|
||||
dirs = {version = "3.0.1", optional = true}
|
||||
dtparse = "1.1.0"
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.24"
|
||||
filesize = "0.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
@ -47,15 +54,15 @@ futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||
glob = "0.3.0"
|
||||
heim = {version = "0.1.0-beta.3", optional = true}
|
||||
heim = {version = "0.1.0-rc.1", optional = true}
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.0"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
natural = "0.5.0"
|
||||
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.12"
|
||||
@ -64,13 +71,14 @@ pin-utils = "0.1.0"
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = {version = "0.3.0", optional = true}
|
||||
query_interface = "0.3.5"
|
||||
quick-xml = "0.18.1"
|
||||
rand = "0.7.3"
|
||||
rayon = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.13.0"
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = {version = "6.3.0", optional = true}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.57"
|
||||
@ -79,25 +87,22 @@ serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.6.1", optional = true}
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
titlecase = "1.0"
|
||||
toml = "0.5.6"
|
||||
trash = {version = "1.2.0", optional = true}
|
||||
unicode-segmentation = "1.6.0"
|
||||
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||
uom = {version = "0.30.0", features = ["f64", "try-from"]}
|
||||
url = "2.1.1"
|
||||
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}
|
||||
|
||||
Inflector = "0.11"
|
||||
clipboard = {version = "0.5.0", optional = true}
|
||||
encoding_rs = "0.8.24"
|
||||
quick-xml = "0.18.1"
|
||||
rayon = "1.4.0"
|
||||
trash = {version = "1.1.1", optional = true}
|
||||
url = "2.1.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
@ -111,17 +116,17 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.0"
|
||||
version = "0.24.2"
|
||||
|
||||
[build-dependencies]
|
||||
git2 = {version = "0.13.11", optional = true}
|
||||
shadow-rs = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["clipboard"]
|
||||
clipboard-cli = ["arboard"]
|
||||
rich-benchmark = ["heim"]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
|
@ -1,36 +1,3 @@
|
||||
use std::path::Path;
|
||||
use std::{env, fs, io};
|
||||
|
||||
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 \
|
||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
||||
");
|
||||
|
||||
let latest_commit_hash = latest_commit_hash(env::current_dir()?).unwrap_or_default();
|
||||
|
||||
let commit_hash_path = Path::new(&out_dir).join("git_commit_hash");
|
||||
fs::write(commit_hash_path, latest_commit_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn latest_commit_hash<P: AsRef<Path>>(dir: P) -> Result<String, Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "git2")]
|
||||
{
|
||||
use git2::Repository;
|
||||
let dir = dir.as_ref();
|
||||
Ok(Repository::discover(dir)?
|
||||
.head()?
|
||||
.peel_to_commit()?
|
||||
.id()
|
||||
.to_string())
|
||||
}
|
||||
#[cfg(not(feature = "git2"))]
|
||||
{
|
||||
Ok(String::new())
|
||||
}
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::commands::default_context::create_default_context;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::script::{print_err, run_script_standalone};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use crate::script::{process_script, LineResult};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
@ -22,8 +24,7 @@ use rustyline::{
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
@ -56,232 +57,8 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||
let mut context = EvaluationContext::basic()?;
|
||||
|
||||
{
|
||||
use crate::commands::*;
|
||||
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(NuPlugin),
|
||||
// System/file operations
|
||||
whole_stream_command(Exec),
|
||||
whole_stream_command(Pwd),
|
||||
whole_stream_command(Ls),
|
||||
whole_stream_command(Du),
|
||||
whole_stream_command(Cd),
|
||||
whole_stream_command(Remove),
|
||||
whole_stream_command(Open),
|
||||
whole_stream_command(Config),
|
||||
whole_stream_command(ConfigGet),
|
||||
whole_stream_command(ConfigSet),
|
||||
whole_stream_command(ConfigSetInto),
|
||||
whole_stream_command(ConfigClear),
|
||||
whole_stream_command(ConfigLoad),
|
||||
whole_stream_command(ConfigRemove),
|
||||
whole_stream_command(ConfigPath),
|
||||
whole_stream_command(Help),
|
||||
whole_stream_command(History),
|
||||
whole_stream_command(Save),
|
||||
whole_stream_command(Touch),
|
||||
whole_stream_command(Cpy),
|
||||
whole_stream_command(Date),
|
||||
whole_stream_command(DateNow),
|
||||
whole_stream_command(DateUTC),
|
||||
whole_stream_command(DateFormat),
|
||||
whole_stream_command(Cal),
|
||||
whole_stream_command(Mkdir),
|
||||
whole_stream_command(Mv),
|
||||
whole_stream_command(Kill),
|
||||
whole_stream_command(Version),
|
||||
whole_stream_command(Clear),
|
||||
whole_stream_command(What),
|
||||
whole_stream_command(Which),
|
||||
whole_stream_command(Debug),
|
||||
whole_stream_command(Alias),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
whole_stream_command(Sleep),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
whole_stream_command(Benchmark),
|
||||
// Metadata
|
||||
whole_stream_command(Tags),
|
||||
// Shells
|
||||
whole_stream_command(Next),
|
||||
whole_stream_command(Previous),
|
||||
whole_stream_command(Shells),
|
||||
whole_stream_command(Enter),
|
||||
whole_stream_command(Exit),
|
||||
// Viewers
|
||||
whole_stream_command(Autoview),
|
||||
whole_stream_command(Table),
|
||||
// Text manipulation
|
||||
whole_stream_command(Split),
|
||||
whole_stream_command(SplitColumn),
|
||||
whole_stream_command(SplitRow),
|
||||
whole_stream_command(SplitChars),
|
||||
whole_stream_command(Lines),
|
||||
whole_stream_command(Echo),
|
||||
whole_stream_command(Parse),
|
||||
whole_stream_command(Str),
|
||||
whole_stream_command(StrToDecimal),
|
||||
whole_stream_command(StrToInteger),
|
||||
whole_stream_command(StrDowncase),
|
||||
whole_stream_command(StrUpcase),
|
||||
whole_stream_command(StrCapitalize),
|
||||
whole_stream_command(StrFindReplace),
|
||||
whole_stream_command(StrFrom),
|
||||
whole_stream_command(StrSubstring),
|
||||
whole_stream_command(StrSet),
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrContains),
|
||||
whole_stream_command(StrIndexOf),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(StrTrimLeft),
|
||||
whole_stream_command(StrTrimRight),
|
||||
whole_stream_command(StrStartsWith),
|
||||
whole_stream_command(StrEndsWith),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(StrLength),
|
||||
whole_stream_command(StrReverse),
|
||||
whole_stream_command(StrCamelCase),
|
||||
whole_stream_command(StrPascalCase),
|
||||
whole_stream_command(StrKebabCase),
|
||||
whole_stream_command(StrSnakeCase),
|
||||
whole_stream_command(StrScreamingSnakeCase),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(MoveColumn),
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(IntoInt),
|
||||
whole_stream_command(SplitBy),
|
||||
// Row manipulation
|
||||
whole_stream_command(Reverse),
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(Prepend),
|
||||
whole_stream_command(SortBy),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(GroupByDate),
|
||||
whole_stream_command(First),
|
||||
whole_stream_command(Last),
|
||||
whole_stream_command(Every),
|
||||
whole_stream_command(Nth),
|
||||
whole_stream_command(Drop),
|
||||
whole_stream_command(Format),
|
||||
whole_stream_command(Where),
|
||||
whole_stream_command(If),
|
||||
whole_stream_command(Compact),
|
||||
whole_stream_command(Default),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(SkipUntil),
|
||||
whole_stream_command(SkipWhile),
|
||||
whole_stream_command(Keep),
|
||||
whole_stream_command(KeepUntil),
|
||||
whole_stream_command(KeepWhile),
|
||||
whole_stream_command(Range),
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(EachGroup),
|
||||
whole_stream_command(EachWindow),
|
||||
whole_stream_command(IsEmpty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Merge),
|
||||
whole_stream_command(Shuffle),
|
||||
whole_stream_command(Wrap),
|
||||
whole_stream_command(Pivot),
|
||||
whole_stream_command(Headers),
|
||||
whole_stream_command(Reduce),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Autoenv),
|
||||
whole_stream_command(AutoenvTrust),
|
||||
whole_stream_command(AutoenvUnTrust),
|
||||
whole_stream_command(Math),
|
||||
whole_stream_command(MathAverage),
|
||||
whole_stream_command(MathEval),
|
||||
whole_stream_command(MathMedian),
|
||||
whole_stream_command(MathMinimum),
|
||||
whole_stream_command(MathMode),
|
||||
whole_stream_command(MathMaximum),
|
||||
whole_stream_command(MathStddev),
|
||||
whole_stream_command(MathSummation),
|
||||
whole_stream_command(MathVariance),
|
||||
whole_stream_command(MathProduct),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
whole_stream_command(ToHTML),
|
||||
whole_stream_command(ToJSON),
|
||||
whole_stream_command(ToMarkdown),
|
||||
whole_stream_command(ToTOML),
|
||||
whole_stream_command(ToTSV),
|
||||
whole_stream_command(ToURL),
|
||||
whole_stream_command(ToYAML),
|
||||
whole_stream_command(ToXML),
|
||||
// File format input
|
||||
whole_stream_command(From),
|
||||
whole_stream_command(FromCSV),
|
||||
whole_stream_command(FromEML),
|
||||
whole_stream_command(FromTSV),
|
||||
whole_stream_command(FromSSV),
|
||||
whole_stream_command(FromINI),
|
||||
whole_stream_command(FromJSON),
|
||||
whole_stream_command(FromODS),
|
||||
whole_stream_command(FromTOML),
|
||||
whole_stream_command(FromURL),
|
||||
whole_stream_command(FromXLSX),
|
||||
whole_stream_command(FromXML),
|
||||
whole_stream_command(FromYAML),
|
||||
whole_stream_command(FromYML),
|
||||
whole_stream_command(FromIcs),
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
whole_stream_command(RunExternalCommand { interactive }),
|
||||
// Random value generation
|
||||
whole_stream_command(Random),
|
||||
whole_stream_command(RandomBool),
|
||||
whole_stream_command(RandomDice),
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
whole_stream_command(RandomUUID),
|
||||
whole_stream_command(RandomInteger),
|
||||
// Path
|
||||
whole_stream_command(PathBasename),
|
||||
whole_stream_command(PathCommand),
|
||||
whole_stream_command(PathDirname),
|
||||
whole_stream_command(PathExists),
|
||||
whole_stream_command(PathExpand),
|
||||
whole_stream_command(PathExtension),
|
||||
whole_stream_command(PathFilestem),
|
||||
whole_stream_command(PathType),
|
||||
// Url
|
||||
whole_stream_command(UrlCommand),
|
||||
whole_stream_command(UrlScheme),
|
||||
whole_stream_command(UrlPath),
|
||||
whole_stream_command(UrlHost),
|
||||
whole_stream_command(UrlQuery),
|
||||
]);
|
||||
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
{
|
||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
pub async fn run_vec_of_pipelines(
|
||||
pipelines: Vec<String>,
|
||||
pub async fn run_script_file(
|
||||
file_contents: String,
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
@ -303,9 +80,7 @@ pub async fn run_vec_of_pipelines(
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
|
||||
for pipeline in pipelines {
|
||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||
}
|
||||
run_script_standalone(file_contents, redirect_stdin, &context, true).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -313,6 +88,7 @@ pub async fn run_vec_of_pipelines(
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
@ -349,9 +125,15 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
|
||||
// Give ourselves a scope to work in
|
||||
context.scope.enter_scope();
|
||||
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let mut session_text = String::new();
|
||||
let mut line_start: usize = 0;
|
||||
|
||||
let skip_welcome_message = configuration
|
||||
.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
@ -382,25 +164,28 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(prompt) = configuration.var("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
Ok(result) => {
|
||||
let mut prompt_block =
|
||||
nu_parser::classify_block(&result, context.registry());
|
||||
context.scope.enter_scope();
|
||||
let (prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
||||
|
||||
let env = context.get_env();
|
||||
if err.is_some() {
|
||||
use crate::git::current_branch;
|
||||
context.scope.exit_scope();
|
||||
|
||||
prompt_block.block.expand_it_usage();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
)
|
||||
.await
|
||||
{
|
||||
} else {
|
||||
// let env = context.get_env();
|
||||
|
||||
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
|
||||
context.scope.exit_scope();
|
||||
|
||||
match run_result {
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
@ -428,13 +213,6 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
use crate::git::current_branch;
|
||||
format!(
|
||||
@ -464,8 +242,23 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
if let Ok(line) = &readline {
|
||||
line_start = session_text.len();
|
||||
session_text.push_str(line);
|
||||
session_text.push('\n');
|
||||
}
|
||||
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(s) => process_line(&s, &mut context, false, true).await,
|
||||
LineResult::Success(_) => {
|
||||
process_script(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
x => x,
|
||||
};
|
||||
|
||||
@ -491,7 +284,12 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
LineResult::Success(line) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
context.maybe_print_errors(Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
rl.clear_history();
|
||||
let _ = rl.save_history(&history_path);
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
@ -499,10 +297,10 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
print_err(err, &Text::from(session_text.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line.clone()));
|
||||
context.maybe_print_errors(Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
@ -587,8 +385,7 @@ async fn run_startup_commands(
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ =
|
||||
run_pipeline_standalone(pipeline_string, false, context, false).await;
|
||||
let _ = run_script_standalone(pipeline_string, false, context, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -603,50 +400,6 @@ async fn run_startup_commands(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut EvaluationContext,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(&pipeline, context, redirect_stdin, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
@ -667,6 +420,9 @@ fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
@ -825,276 +581,42 @@ fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hin
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
fn chomp_newline(s: &str) -> &str {
|
||||
if s.ends_with('\n') {
|
||||
&s[..s.len() - 1]
|
||||
} else {
|
||||
pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
||||
// FIXME: do we still need this?
|
||||
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
Break,
|
||||
CtrlC,
|
||||
CtrlD,
|
||||
}
|
||||
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||
let line = if line.ends_with('\n') {
|
||||
&line[..line.len() - 1]
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
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();
|
||||
ctx.scope.enter_scope();
|
||||
let (classified_block, err) = nu_parser::parse(&line, 0, &ctx.scope);
|
||||
if let Some(err) = err {
|
||||
ctx.scope.exit_scope();
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
ctx.scope.add_env(env);
|
||||
|
||||
run_block(
|
||||
&classified_block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await?
|
||||
.collect_string(Tag::unknown())
|
||||
.await
|
||||
.map(|x| x.item)
|
||||
}
|
||||
let result = run_block(&classified_block, ctx, input_stream).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||
pub async fn process_line(
|
||||
line: &str,
|
||||
ctx: &mut EvaluationContext,
|
||||
redirect_stdin: bool,
|
||||
cli_mode: bool,
|
||||
) -> LineResult {
|
||||
if line.trim() == "" {
|
||||
LineResult::Success(line.to_string())
|
||||
} else {
|
||||
let line = chomp_newline(line);
|
||||
ctx.raw_input = line.to_string();
|
||||
|
||||
let result = match nu_parser::lite_parse(&line, 0) {
|
||||
Err(err) => {
|
||||
return LineResult::Error(line.to_string(), err.into());
|
||||
}
|
||||
|
||||
Ok(val) => val,
|
||||
};
|
||||
|
||||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||
|
||||
debug!("{:#?}", classified_block);
|
||||
//println!("{:#?}", pipeline);
|
||||
|
||||
if let Some(failure) = classified_block.failed {
|
||||
return LineResult::Error(line.to_string(), failure.into());
|
||||
}
|
||||
|
||||
// There's a special case to check before we process the pipeline:
|
||||
// If we're giving a path by itself
|
||||
// ...and it's not a command in the path
|
||||
// ...and it doesn't have any arguments
|
||||
// ...and we're in the CLI
|
||||
// ...then change to this directory
|
||||
if cli_mode
|
||||
&& classified_block.block.block.len() == 1
|
||||
&& classified_block.block.block[0].list.len() == 1
|
||||
{
|
||||
if let ClassifiedCommand::Internal(InternalCommand {
|
||||
ref name, ref args, ..
|
||||
}) = classified_block.block.block[0].list[0]
|
||||
{
|
||||
let internal_name = name;
|
||||
let name = args
|
||||
.positional
|
||||
.as_ref()
|
||||
.and_then(|potionals| {
|
||||
potionals.get(0).map(|e| {
|
||||
if let Expression::Literal(Literal::String(ref s)) = e.expr {
|
||||
&s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or("");
|
||||
|
||||
if internal_name == "run_external"
|
||||
&& args
|
||||
.positional
|
||||
.as_ref()
|
||||
.map(|ref v| v.len() == 1)
|
||||
.unwrap_or(true)
|
||||
&& args
|
||||
.named
|
||||
.as_ref()
|
||||
.map(NamedArguments::is_empty)
|
||||
.unwrap_or(true)
|
||||
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
|
||||
&& Path::new(&name).is_dir()
|
||||
&& !crate::commands::classified::external::did_find_command(&name)
|
||||
{
|
||||
// Here we work differently if we're in Windows because of the expected Windows behavior
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if name.ends_with(':') {
|
||||
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
|
||||
// But first, we need to save where we are now
|
||||
let current_path = ctx.shell_manager.path();
|
||||
|
||||
let split_path: Vec<_> = current_path.split(':').collect();
|
||||
if split_path.len() > 1 {
|
||||
ctx.windows_drives_previous_cwd
|
||||
.lock()
|
||||
.insert(split_path[0].to_string(), current_path);
|
||||
}
|
||||
|
||||
let name = name.to_uppercase();
|
||||
let new_drive: Vec<_> = name.split(':').collect();
|
||||
|
||||
if let Some(val) =
|
||||
ctx.windows_drives_previous_cwd.lock().get(new_drive[0])
|
||||
{
|
||||
ctx.shell_manager.set_path(val.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
} else {
|
||||
ctx.shell_manager
|
||||
.set_path(format!("{}\\", name.to_string()));
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
} else {
|
||||
ctx.shell_manager.set_path(name.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
ctx.shell_manager.set_path(name.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input_stream = if redirect_stdin {
|
||||
let file = futures::io::AllowStdIo::new(std::io::stdin());
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
|
||||
if let Ok(line) = line {
|
||||
let primitive = match line {
|
||||
StringOrBinary::String(s) => Primitive::String(s),
|
||||
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
|
||||
};
|
||||
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Primitive(primitive),
|
||||
tag: Tag::unknown(),
|
||||
})
|
||||
} else {
|
||||
panic!("Internal error: could not read lines of text from stdin")
|
||||
}
|
||||
});
|
||||
stream.to_input_stream()
|
||||
} else {
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
classified_block.block.expand_it_usage();
|
||||
|
||||
trace!("{:#?}", classified_block);
|
||||
let env = ctx.get_env();
|
||||
match run_block(
|
||||
&classified_block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(input) => {
|
||||
// Running a pipeline gives us back a stream that we can then
|
||||
// work through. At the top level, we just want to pull on the
|
||||
// values to compute them.
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let context = RunnableContext {
|
||||
input,
|
||||
shell_manager: ctx.shell_manager.clone(),
|
||||
host: ctx.host.clone(),
|
||||
ctrl_c: ctx.ctrl_c.clone(),
|
||||
current_errors: ctx.current_errors.clone(),
|
||||
registry: ctx.registry.clone(),
|
||||
name: Tag::unknown(),
|
||||
raw_input: line.to_string(),
|
||||
};
|
||||
|
||||
if let Ok(mut output_stream) =
|
||||
crate::commands::autoview::command::autoview(context).await
|
||||
{
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => return LineResult::Error(line.to_string(), e),
|
||||
Ok(Some(_item)) => {
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(e) => return LineResult::Error(line.to_string(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Success(line.to_string())
|
||||
}
|
||||
Err(err) => LineResult::Error(line.to_string(), err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
if let Some(diag) = err.into_diagnostic() {
|
||||
let source = source.to_string();
|
||||
let mut files = codespan_reporting::files::SimpleFiles::new();
|
||||
files.add("shell", source);
|
||||
|
||||
let writer = codespan_reporting::term::termcolor::StandardStream::stderr(
|
||||
codespan_reporting::term::termcolor::ColorChoice::Always,
|
||||
);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
|
||||
});
|
||||
}
|
||||
result?.collect_string(Tag::unknown()).await.map(|x| x.item)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
||||
let (tokens, err) = nu_parser::lex(&data, 0);
|
||||
let (lite_block, err2) = nu_parser::group(tokens);
|
||||
if err.is_none() && err2.is_none() {
|
||||
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||
let _ = nu_parser::classify_block(&lite_block, &context.scope);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
use crate::commands::Command;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::Signature;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CommandRegistry {
|
||||
registry: Arc<Mutex<IndexMap<String, Command>>>,
|
||||
}
|
||||
|
||||
impl SignatureRegistry for CommandRegistry {
|
||||
fn has(&self, name: &str) -> bool {
|
||||
let registry = self.registry.lock();
|
||||
registry.contains_key(name)
|
||||
}
|
||||
fn get(&self, name: &str) -> Option<Signature> {
|
||||
let registry = self.registry.lock();
|
||||
registry.get(name).map(|command| command.signature())
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandRegistry {
|
||||
pub fn new() -> CommandRegistry {
|
||||
CommandRegistry {
|
||||
registry: Arc::new(Mutex::new(IndexMap::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandRegistry {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
let registry = self.registry.lock();
|
||||
|
||||
registry.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
||||
self.get_command(name).ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(format!("Could not load command: {}", name))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
let registry = self.registry.lock();
|
||||
|
||||
registry.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, name: impl Into<String>, command: Command) {
|
||||
let mut registry = self.registry.lock();
|
||||
registry.insert(name.into(), command);
|
||||
}
|
||||
|
||||
pub fn names(&self) -> Vec<String> {
|
||||
let registry = self.registry.lock();
|
||||
registry.keys().cloned().collect()
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ pub(crate) mod macros;
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
@ -17,6 +16,7 @@ pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod chart;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
@ -28,17 +28,22 @@ pub(crate) mod count;
|
||||
pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod def;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod default_context;
|
||||
pub(crate) mod describe;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod empty;
|
||||
pub(crate) mod enter;
|
||||
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;
|
||||
@ -58,6 +63,7 @@ pub(crate) mod from_yaml;
|
||||
pub(crate) mod get;
|
||||
pub(crate) mod group_by;
|
||||
pub(crate) mod group_by_date;
|
||||
pub(crate) mod hash_;
|
||||
pub(crate) mod headers;
|
||||
pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
@ -65,9 +71,10 @@ pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod let_;
|
||||
pub(crate) mod let_env;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
pub(crate) mod math;
|
||||
@ -91,16 +98,18 @@ pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
pub(crate) mod rm;
|
||||
pub(crate) mod run_alias;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod seq;
|
||||
pub(crate) mod seq_dates;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod sleep;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod source;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
pub(crate) mod str_;
|
||||
@ -120,7 +129,6 @@ pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod url_;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
@ -132,9 +140,8 @@ pub(crate) use command::{
|
||||
whole_stream_command, Command, Example, UnevaluatedCallInfo, WholeStreamCommand,
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use append::Command as Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||
@ -142,15 +149,18 @@ pub(crate) use benchmark::Benchmark;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use chart::Chart;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::{
|
||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
};
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
||||
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use def::Def;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use describe::Describe;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
@ -158,10 +168,10 @@ pub(crate) use each::Each;
|
||||
pub(crate) use each::EachGroup;
|
||||
pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use empty::Command as Empty;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use is_empty::IsEmpty;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Update;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
@ -172,7 +182,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;
|
||||
@ -190,25 +201,28 @@ pub(crate) use from_xml::FromXML;
|
||||
pub(crate) use from_yaml::FromYAML;
|
||||
pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use get::Get;
|
||||
pub(crate) use group_by::GroupBy;
|
||||
pub(crate) use group_by::Command as GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use hash_::{Hash, HashBase64};
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use insert::Command as Insert;
|
||||
pub(crate) use into_int::IntoInt;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use let_::Let;
|
||||
pub(crate) use let_env::LetEnv;
|
||||
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, MathAbs, 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;
|
||||
@ -223,7 +237,9 @@ pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
||||
pub(crate) use random::{
|
||||
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
|
||||
};
|
||||
pub(crate) use range::Range;
|
||||
pub(crate) use reduce::Reduce;
|
||||
pub(crate) use reject::Reject;
|
||||
@ -233,19 +249,22 @@ 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 seq_dates::SeqDates;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
|
||||
pub(crate) use sleep::Sleep;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use source::Source;
|
||||
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
|
||||
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||
StrReverse, StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring,
|
||||
StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
@ -263,8 +282,50 @@ pub(crate) use touch::Touch;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
pub(crate) use what::What;
|
||||
pub(crate) use where_::Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::commands::whole_stream_command;
|
||||
use crate::examples::{test_anchors, test_examples};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
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 only_examples() {
|
||||
test_examples(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracks_metadata() -> Result<(), ShellError> {
|
||||
for cmd in full_tests() {
|
||||
test_anchors(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,350 +0,0 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
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,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
pub infer: Option<bool>,
|
||||
pub save: Option<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "the name of the alias")
|
||||
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run as the body of the alias",
|
||||
)
|
||||
.switch("infer", "infer argument types (experimental)", Some('i'))
|
||||
.switch("save", "save the alias to your config", Some('s'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a shortcut for another command."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
alias(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "An alias without parameters",
|
||||
example: "alias say-hi [] { echo 'Hello!' }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "An alias with a single parameter",
|
||||
example: "alias l [x] { ls $x }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn alias(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let mut raw_input = args.raw_input.clone();
|
||||
let (
|
||||
AliasArgs {
|
||||
name,
|
||||
args: list,
|
||||
block,
|
||||
infer,
|
||||
save,
|
||||
},
|
||||
_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)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "") // TODO using regex (or reconstruct string from AST?)
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input 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
|
||||
match result.get_mut("startup") {
|
||||
Some(startup) => {
|
||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||
if let Some(command) = commands.iter_mut().find(|command| {
|
||||
let cmd_str = command.as_string().unwrap_or_default();
|
||||
cmd_str.starts_with(&raw_input[..alias_start])
|
||||
}) {
|
||||
*command = alias;
|
||||
} else {
|
||||
commands.push(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let table = UntaggedValue::table(&[alias]);
|
||||
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
||||
}
|
||||
}
|
||||
config::write(&result, &None)?;
|
||||
}
|
||||
|
||||
for item in list.iter() {
|
||||
if let Ok(string) = item.as_string() {
|
||||
processed_args.push(format!("${}", string));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a string",
|
||||
"expected a string",
|
||||
item.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
}
|
||||
}
|
@ -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(
|
||||
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,18 +107,53 @@ 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",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color, escape, osc }, _) = args.process().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 +167,7 @@ impl WholeStreamCommand for Ansi {
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,11 +229,12 @@ pub fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
Ok(test_examples(Ansi {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AppendArgs {
|
||||
row: Value,
|
||||
struct Arguments {
|
||||
value: Value,
|
||||
}
|
||||
|
||||
pub struct Append;
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Append {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
}
|
||||
@ -26,24 +25,44 @@ impl WholeStreamCommand for Append {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append the given row to the table"
|
||||
"Append a row to the table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { mut value }, input) = args.process().await?;
|
||||
|
||||
let eos = futures::stream::iter(vec![row]);
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
Ok(input.chain(eos).to_output_stream())
|
||||
if let Some(first) = input.get(0) {
|
||||
value.tag = first.tag();
|
||||
}
|
||||
|
||||
// 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",
|
||||
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(),
|
||||
@ -51,18 +70,16 @@ impl WholeStreamCommand for Append {
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Append;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Append {})
|
||||
},
|
||||
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")},
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -61,14 +61,9 @@ The file can contain several optional sections:
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv")
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, ®istry))
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
@ -22,14 +22,11 @@ impl WholeStreamCommand for AutoenvTrust {
|
||||
"Trust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
|
||||
let file_to_trust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||
let file_to_trust = match args.call_info.evaluate(&ctx).await?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
|
@ -26,13 +26,10 @@ impl WholeStreamCommand for AutoenvUnTrust {
|
||||
"Untrust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let file_to_untrust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let file_to_untrust = match args.call_info.evaluate(&ctx).await?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
|
@ -5,7 +5,7 @@ use crate::primitive::get_color_config;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
|
||||
use nu_table::TextStyle;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
@ -26,20 +26,15 @@ impl WholeStreamCommand for Command {
|
||||
"View the contents of the pipeline as a table or list."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
autoview(RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
scope: args.scope.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,
|
||||
})
|
||||
.await
|
||||
}
|
||||
@ -65,7 +60,7 @@ pub struct RunnableContextWithoutInput {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub registry: CommandRegistry,
|
||||
pub scope: Scope,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
@ -76,7 +71,7 @@ impl RunnableContextWithoutInput {
|
||||
host: context.host,
|
||||
ctrl_c: context.ctrl_c,
|
||||
current_errors: context.current_errors,
|
||||
registry: context.registry,
|
||||
scope: context.scope,
|
||||
name: context.name,
|
||||
};
|
||||
(context.input, new_context)
|
||||
@ -109,7 +104,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
let result = table.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
}
|
||||
}
|
||||
@ -126,7 +121,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
let result = text.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}", s);
|
||||
@ -149,7 +144,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args, &context.registry).await?;
|
||||
let result = text.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{}\n", s);
|
||||
@ -224,7 +219,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args, &context.registry).await?;
|
||||
let result = binary.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
@ -276,7 +271,12 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
|
||||
nu_table::draw_table(&table, term_width, &color_hm);
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {
|
||||
// Do nothing
|
||||
}
|
||||
Value {
|
||||
value: ref item, ..
|
||||
} => {
|
||||
@ -285,7 +285,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args, &context.registry).await?;
|
||||
let result = table.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
@ -318,19 +318,20 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: context.name.clone(),
|
||||
scope: Scope::new(),
|
||||
},
|
||||
scope: Scope::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
||||
|
@ -10,25 +10,16 @@ pub enum AutoPivotMode {
|
||||
|
||||
impl AutoPivotMode {
|
||||
pub fn is_auto(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Auto => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Auto)
|
||||
}
|
||||
|
||||
pub fn is_always(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Always => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Always)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn is_never(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Never => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, AutoPivotMode::Never)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,12 @@ use crate::prelude::*;
|
||||
use heim::cpu::time;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
|
||||
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::{Block, CapturedBlock, ClassifiedCommand, Group, InternalCommand, Pipeline},
|
||||
Dictionary, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use rand::{
|
||||
distributions::Alphanumeric,
|
||||
prelude::{thread_rng, Rng},
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, Instant};
|
||||
@ -15,8 +19,8 @@ pub struct Benchmark;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BenchmarkArgs {
|
||||
block: Block,
|
||||
passthrough: Option<Block>,
|
||||
block: CapturedBlock,
|
||||
passthrough: Option<CapturedBlock>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -44,12 +48,8 @@ impl WholeStreamCommand for Benchmark {
|
||||
"Runs a block and returns the time it took to execute it"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -68,31 +68,25 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = raw_args.call_info.args.span;
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(®istry).await?;
|
||||
let mut context = EvaluationContext::from_raw(&raw_args);
|
||||
let scope = raw_args.scope.clone();
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process().await?;
|
||||
|
||||
let env = scope.get_env_vars();
|
||||
let name = generate_free_name(&env);
|
||||
|
||||
scope.add_env_var(name, generate_random_env_value());
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
let start = time().await;
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&block.block, &context, input).await;
|
||||
context.scope.exit_scope();
|
||||
let output = result?.into_vec().await;
|
||||
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
@ -108,7 +102,7 @@ async fn benchmark(
|
||||
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context).await
|
||||
}
|
||||
// return advanced stats
|
||||
#[cfg(feature = "rich-benchmark")]
|
||||
@ -127,10 +121,10 @@ async fn benchmark(
|
||||
let idle_time = into_big_int(end.idle() - start.idle());
|
||||
indexmap.insert("idle time".to_string(), idle_time);
|
||||
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context).await
|
||||
} else {
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"Could not retreive CPU time",
|
||||
"Could not retrieve CPU time",
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -138,10 +132,9 @@ async fn benchmark(
|
||||
async fn benchmark_output<T, Output>(
|
||||
indexmap: IndexMap<String, BigInt>,
|
||||
block_output: Output,
|
||||
passthrough: Option<Block>,
|
||||
passthrough: Option<CapturedBlock>,
|
||||
tag: T,
|
||||
context: &mut EvaluationContext,
|
||||
scope: &Scope,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
T: Into<Tag> + Copy,
|
||||
@ -159,17 +152,12 @@ where
|
||||
let benchmark_output = InputStream::one(value);
|
||||
|
||||
// add autoview for an empty block
|
||||
let time_block = add_implicit_autoview(time_block);
|
||||
let time_block = add_implicit_autoview(time_block.block);
|
||||
|
||||
let _ = run_block(
|
||||
&time_block,
|
||||
context,
|
||||
benchmark_output,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?;
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&time_block, context, benchmark_output).await;
|
||||
context.scope.exit_scope();
|
||||
result?;
|
||||
context.clear_errors();
|
||||
|
||||
Ok(block_output.into())
|
||||
@ -181,15 +169,19 @@ where
|
||||
|
||||
fn add_implicit_autoview(mut block: Block) -> Block {
|
||||
if block.block.is_empty() {
|
||||
block.push({
|
||||
let mut commands = Commands::new(block.span);
|
||||
let group = Group::new(
|
||||
vec![{
|
||||
let mut commands = Pipeline::new(block.span);
|
||||
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
|
||||
"autoview".to_string(),
|
||||
block.span,
|
||||
block.span,
|
||||
)));
|
||||
commands
|
||||
});
|
||||
}],
|
||||
block.span,
|
||||
);
|
||||
block.push(group);
|
||||
}
|
||||
block
|
||||
}
|
||||
@ -200,3 +192,19 @@ fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
|
||||
.as_nanos()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn generate_random_env_value() -> String {
|
||||
let mut thread_rng = thread_rng();
|
||||
let len = thread_rng.gen_range(1, 16 * 1024);
|
||||
thread_rng.sample_iter(&Alphanumeric).take(len).collect()
|
||||
}
|
||||
|
||||
fn generate_free_name(env: &indexmap::IndexMap<String, String>) -> String {
|
||||
let mut thread_rng = thread_rng();
|
||||
loop {
|
||||
let candidate_name = format!("NU_RANDOM_VALUE_{}", thread_rng.gen::<usize>());
|
||||
if !env.contains_key(&candidate_name) {
|
||||
return candidate_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,9 @@ impl WholeStreamCommand for BuildString {
|
||||
"Builds a string from the arguments"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (BuildStringArgs { rest }, _) = args.process(®istry).await?;
|
||||
let (BuildStringArgs { rest }, _) = args.process().await?;
|
||||
|
||||
let mut output_string = String::new();
|
||||
|
||||
|
@ -41,12 +41,8 @@ impl WholeStreamCommand for Cal {
|
||||
"Display a calendar."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
cal(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
cal(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -70,12 +66,8 @@ impl WholeStreamCommand for Cal {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cal(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
pub async fn cal(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let mut calendar_vec_deque = VecDeque::new();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
@ -339,11 +331,12 @@ fn add_month_to_table(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cal;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cal {})
|
||||
Ok(test_examples(Cal {})?)
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,10 @@ impl WholeStreamCommand for Cd {
|
||||
"Change to a new path."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (args, _): (CdArgs, _) = args.process(®istry).await?;
|
||||
let (args, _): (CdArgs, _) = args.process().await?;
|
||||
shell_manager.cd(args, name)
|
||||
}
|
||||
|
||||
@ -72,11 +68,12 @@ impl WholeStreamCommand for Cd {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cd;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cd {})
|
||||
Ok(test_examples(Cd {})?)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
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,31 +48,52 @@ 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}")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name, unicode }, _) = args.process().await?;
|
||||
|
||||
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 {
|
||||
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(
|
||||
"Unknown character",
|
||||
"unknown character",
|
||||
"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 {
|
||||
@ -78,6 +102,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 +121,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 +147,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,
|
||||
}
|
||||
}
|
||||
@ -130,11 +155,12 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Char {})
|
||||
Ok(test_examples(Char {})?)
|
||||
}
|
||||
}
|
||||
|
48
crates/nu-cli/src/commands/chart.rs
Normal file
48
crates/nu-cli/src/commands/chart.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chart;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Chart {
|
||||
fn name(&self) -> &str {
|
||||
"chart"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("chart")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Displays charts."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.scope.get_command("chart bar").is_none() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"nu_plugin_chart not installed.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Chart, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Chart;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Chart {})?)
|
||||
}
|
||||
}
|
@ -2,41 +2,68 @@ use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use async_recursion::async_recursion;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::hir::{
|
||||
Block, Call, ClassifiedCommand, Expression, Pipeline, SpannedExpression, Synthetic,
|
||||
};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_stream::InputStream;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
#[async_recursion]
|
||||
pub async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut EvaluationContext,
|
||||
ctx: &EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for pipeline in &block.block {
|
||||
for (_, definition) in block.definitions.iter() {
|
||||
ctx.scope.add_definition(definition.clone());
|
||||
}
|
||||
|
||||
for group in &block.block {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
loop {
|
||||
// Run autoview on the values we've seen so far
|
||||
// We may want to make this configurable for other kinds of hosting
|
||||
if let Some(autoview) = ctx.get_command("autoview") {
|
||||
let mut output_stream = match ctx
|
||||
.run_command(
|
||||
autoview,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".into())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
inp,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => return Err(e),
|
||||
}))) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
return Ok(InputStream::empty());
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
@ -44,9 +71,10 @@ pub(crate) async fn run_block(
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,37 +82,131 @@ pub(crate) async fn run_block(
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
||||
output = Ok(InputStream::empty());
|
||||
for pipeline in &group.pipelines {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
// This early return doesn't return the result
|
||||
// we have so far, but breaking out of this loop
|
||||
// causes lifetime issues. A future contribution
|
||||
// could attempt to return the current output.
|
||||
// https://github.com/nushell/nushell/pull/2830#discussion_r550319687
|
||||
return Ok(InputStream::empty());
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut EvaluationContext,
|
||||
commands: &Pipeline,
|
||||
ctx: &EvaluationContext,
|
||||
mut input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
for item in commands.list.clone() {
|
||||
input = match item {
|
||||
ClassifiedCommand::Dynamic(_) => {
|
||||
return Err(ShellError::unimplemented("Dynamic commands"))
|
||||
ClassifiedCommand::Dynamic(call) => {
|
||||
let mut args = vec![];
|
||||
if let Some(positional) = call.positional {
|
||||
for pos in &positional {
|
||||
let result = run_expression_block(pos, ctx).await?.into_vec().await;
|
||||
args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => {
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
match &call.head.expr {
|
||||
Expression::Block(block) => {
|
||||
ctx.scope.enter_scope();
|
||||
for (param, value) in block.params.positional.iter().zip(args.iter()) {
|
||||
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||
}
|
||||
let result = run_block(&block, ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let result = result?;
|
||||
return Ok(result);
|
||||
}
|
||||
Expression::Variable(v, span) => {
|
||||
if let Some(value) = ctx.scope.get_var(v) {
|
||||
match &value.value {
|
||||
UntaggedValue::Block(captured_block) => {
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_vars(&captured_block.captured.entries);
|
||||
for (param, value) in captured_block
|
||||
.block
|
||||
.params
|
||||
.positional
|
||||
.iter()
|
||||
.zip(args.iter())
|
||||
{
|
||||
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||
}
|
||||
let result = run_block(&captured_block.block, ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let result = result?;
|
||||
return Ok(result);
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Variable not found",
|
||||
"variable not found",
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => run_expression_block(&*expr, ctx).await?,
|
||||
|
||||
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||
|
||||
ClassifiedCommand::Internal(left) => {
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
}
|
||||
ClassifiedCommand::Internal(left) => run_internal_command(left, ctx, input).await?,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,17 @@ use log::{log_enabled, trace};
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
use nu_protocol::Value;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: SpannedExpression,
|
||||
context: &mut EvaluationContext,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
expr: &SpannedExpression,
|
||||
ctx: &EvaluationContext,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||
}
|
||||
|
||||
let registry = context.registry().clone();
|
||||
let output = evaluate_baseline_expr(&expr, ®istry, it, vars, env).await?;
|
||||
let output = evaluate_baseline_expr(expr, ctx).await?;
|
||||
|
||||
Ok(once(async { Ok(output) }).to_input_stream())
|
||||
}
|
||||
|
@ -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,15 +14,15 @@ 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_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
@ -34,14 +35,13 @@ pub(crate) async fn run_external_command(
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, scope, external_redirection).await
|
||||
run_with_stdin(command, context, input, external_redirection).await
|
||||
}
|
||||
|
||||
async fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
scope: &Scope,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
@ -50,9 +50,8 @@ async fn run_with_stdin(
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let value =
|
||||
evaluate_baseline_expr(arg, &context.registry, &scope.it, &scope.vars, &scope.env)
|
||||
.await?;
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, context).await?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
@ -67,8 +66,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(
|
||||
@ -82,14 +83,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")]
|
||||
@ -105,8 +106,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()
|
||||
}
|
||||
@ -128,7 +130,7 @@ async fn run_with_stdin(
|
||||
&process_args[..],
|
||||
input,
|
||||
external_redirection,
|
||||
scope,
|
||||
&context.scope,
|
||||
)
|
||||
}
|
||||
|
||||
@ -169,7 +171,7 @@ fn spawn(
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.env.iter());
|
||||
process.envs(scope.get_env_vars());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
@ -221,36 +223,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!(
|
||||
@ -496,11 +481,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;
|
||||
@ -511,10 +491,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) {
|
||||
@ -540,7 +530,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};
|
||||
@ -550,8 +540,6 @@ mod tests {
|
||||
#[cfg(feature = "which")]
|
||||
use nu_errors::ShellError;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_protocol::Scope;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
|
||||
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
@ -576,15 +564,11 @@ mod tests {
|
||||
let mut ctx =
|
||||
EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(run_external_command(
|
||||
cmd,
|
||||
&mut ctx,
|
||||
input,
|
||||
&Scope::new(),
|
||||
ExternalRedirection::Stdout
|
||||
)
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
|
||||
.await
|
||||
.is_err());
|
||||
.is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -620,10 +604,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]
|
||||
@ -651,9 +635,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]
|
||||
|
@ -1,35 +1,30 @@
|
||||
use crate::commands::command::whole_stream_command;
|
||||
use crate::commands::run_alias::AliasCommand;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
use log::{log_enabled, trace};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ExternalRedirection, InternalCommand};
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
context: &EvaluationContext,
|
||||
input: InputStream,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
}
|
||||
|
||||
let scope = Scope {
|
||||
it: it.clone(),
|
||||
vars: vars.clone(),
|
||||
env: env.clone(),
|
||||
};
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
let internal_command = context.expect_command(&command.name);
|
||||
|
||||
let internal_command = context.scope.expect_command(&command.name);
|
||||
|
||||
if command.name == "autoenv untrust" {
|
||||
context.user_recently_used_autoenv_untrust = true;
|
||||
context
|
||||
.user_recently_used_autoenv_untrust
|
||||
.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let result = {
|
||||
@ -38,44 +33,38 @@ pub(crate) async fn run_internal_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
&scope,
|
||||
objects,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let head = Arc::new(command.args.head.clone());
|
||||
//let context = Arc::new(context.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
let scope = Arc::new(scope);
|
||||
// let scope = scope.clone();
|
||||
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
.then(move |item| {
|
||||
let head = head.clone();
|
||||
let command = command.clone();
|
||||
let mut context = context.clone();
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
async move {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.registry.get_command(&command_name)
|
||||
{
|
||||
if let Some(converter) = context.scope.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
@ -90,14 +79,11 @@ pub(crate) async fn run_internal_command(
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
scope: (&*scope).clone(),
|
||||
},
|
||||
scope: context.scope.clone(),
|
||||
};
|
||||
let result = converter
|
||||
.run(
|
||||
new_args.with_input(vec![tagged_contents]),
|
||||
&context.registry,
|
||||
)
|
||||
.run(new_args.with_input(vec![tagged_contents]))
|
||||
.await;
|
||||
|
||||
match result {
|
||||
@ -131,8 +117,8 @@ pub(crate) async fn run_internal_command(
|
||||
|
||||
futures::stream::iter(output).to_input_stream()
|
||||
}
|
||||
Err(e) => {
|
||||
context.add_error(e);
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
@ -148,13 +134,12 @@ pub(crate) async fn run_internal_command(
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.registry(),
|
||||
&context.scope,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
context.error(err);
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
@ -162,12 +147,11 @@ pub(crate) async fn run_internal_command(
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::index(&context.registry()) {
|
||||
match HelpShell::index(&context.scope) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err).into_untagged_value(),
|
||||
)
|
||||
context.error(err);
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
@ -185,21 +169,13 @@ pub(crate) async fn run_internal_command(
|
||||
match FilesystemShell::with_location(location) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return InputStream::one(
|
||||
UntaggedValue::Error(err.into())
|
||||
.into_untagged_value(),
|
||||
)
|
||||
context.error(err.into());
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddAlias(name, args, block) => {
|
||||
context.add_commands(vec![whole_stream_command(
|
||||
AliasCommand::new(name, args, block),
|
||||
)]);
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddPlugins(path) => {
|
||||
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||
Ok(plugins) => {
|
||||
@ -212,39 +188,37 @@ pub(crate) async fn run_internal_command(
|
||||
.collect(),
|
||||
);
|
||||
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
InputStream::empty()
|
||||
}
|
||||
Err(reason) => {
|
||||
context.error(reason.clone());
|
||||
InputStream::one(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)
|
||||
context.error(reason);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
InputStream::empty()
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
tag,
|
||||
..
|
||||
})) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_value(tag))
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||
@ -264,8 +238,8 @@ pub(crate) async fn run_internal_command(
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err.clone());
|
||||
InputStream::one(UntaggedValue::Error(err).into_untagged_value())
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,24 +104,13 @@ impl WholeStreamCommand for PluginFilter {
|
||||
&self.config.usage
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_filter(self.path.clone(), args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
run_filter(self.path.clone(), (args)).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_filter(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run_filter(path: String, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
trace!("filter_plugin :: {}", path);
|
||||
let registry = registry.clone();
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
|
||||
let bos = futures::stream::iter(vec![
|
||||
UntaggedValue::Primitive(Primitive::BeginningOfStream).into_untagged_value()
|
||||
@ -130,7 +119,7 @@ async fn run_filter(
|
||||
UntaggedValue::Primitive(Primitive::EndOfStream).into_untagged_value()
|
||||
]);
|
||||
|
||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
let args = args.evaluate_once().await?;
|
||||
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
@ -390,22 +379,13 @@ impl WholeStreamCommand for PluginSink {
|
||||
&self.config.usage
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_sink(self.path.clone(), args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
run_sink(self.path.clone(), args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_sink(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn run_sink(path: String, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
@ -20,7 +20,7 @@ impl WholeStreamCommand for Clear {
|
||||
"Clears the terminal"
|
||||
}
|
||||
|
||||
async fn run(&self, _: CommandArgs, _: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, _: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if cfg!(windows) {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", "cls"])
|
||||
@ -43,15 +43,3 @@ impl WholeStreamCommand for Clear {
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clear;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clear {})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, Value};
|
||||
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use arboard::Clipboard;
|
||||
|
||||
pub struct Clip;
|
||||
|
||||
@ -23,60 +22,57 @@ impl WholeStreamCommand for Clip {
|
||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
clip(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clip(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
vec![
|
||||
Example {
|
||||
description: "Save text to the clipboard",
|
||||
example: "echo 'secret value' | clip",
|
||||
result: None,
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Save numbers to the clipboard",
|
||||
example: "random integer 10000000..99999999 | clip",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clip(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn clip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if let Ok(clip_context) = ClipboardProvider::new() {
|
||||
let mut clip_context: ClipboardContext = clip_context;
|
||||
if let Ok(mut clip_context) = Clipboard::new() {
|
||||
let mut new_copy_data = String::new();
|
||||
|
||||
if !values.is_empty() {
|
||||
let mut first = true;
|
||||
for i in values.iter() {
|
||||
if !first {
|
||||
new_copy_data.push_str("\n");
|
||||
new_copy_data.push('\n');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
let string: String = match i.as_string() {
|
||||
Ok(string) => string.to_string(),
|
||||
Err(_) => {
|
||||
let string: String = i.convert_to_string();
|
||||
if string.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Given non-string data",
|
||||
"expected strings from pipeline",
|
||||
"Unable to convert to string",
|
||||
"Unable to convert to string",
|
||||
name,
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
new_copy_data.push_str(&string);
|
||||
}
|
||||
}
|
||||
|
||||
match clip_context.set_contents(new_copy_data) {
|
||||
match clip_context.set_text(new_copy_data) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
@ -99,11 +95,12 @@ pub async fn clip(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clip;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Clip {})
|
||||
Ok(test_examples(Clip {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,26 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::help::get_help;
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
use crate::{commands::help::get_help, run_block};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir;
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::hir::{self, Block};
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UnevaluatedCallInfo {
|
||||
pub args: hir::Call,
|
||||
pub name_tag: Tag,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub async fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, registry, &self.scope).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn evaluate_with_new_it(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
) -> Result<CallInfo, ShellError> {
|
||||
let mut scope = self.scope.clone();
|
||||
scope.it = it.clone();
|
||||
let args = evaluate_args(&self.args, registry, &scope).await?;
|
||||
pub async fn evaluate(self, ctx: &EvaluationContext) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, ctx).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
@ -58,8 +41,8 @@ pub struct CommandArgs {
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
pub scope: Scope,
|
||||
pub input: InputStream,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
#[derive(Getters, Clone)]
|
||||
@ -69,6 +52,7 @@ pub struct RawCommandArgs {
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub scope: Scope,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
}
|
||||
|
||||
@ -80,8 +64,8 @@ impl RawCommandArgs {
|
||||
current_errors: self.current_errors,
|
||||
shell_manager: self.shell_manager,
|
||||
call_info: self.call_info,
|
||||
scope: self.scope,
|
||||
input: input.into(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,15 +77,14 @@ impl std::fmt::Debug for CommandArgs {
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub async fn evaluate_once(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
pub async fn evaluate_once(self) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&self);
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = self.call_info.evaluate(registry).await?;
|
||||
let call_info = self.call_info.evaluate(&ctx).await?;
|
||||
let scope = self.scope.clone();
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
@ -109,39 +92,12 @@ impl CommandArgs {
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
scope,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn evaluate_once_with_scope(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
scope: &Scope,
|
||||
) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = UnevaluatedCallInfo {
|
||||
name_tag: self.call_info.name_tag,
|
||||
args: self.call_info.args,
|
||||
scope: scope.clone(),
|
||||
};
|
||||
let call_info = call_info.evaluate(registry).await?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn process<'de, T: Deserialize<'de>>(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<(T, InputStream), ShellError> {
|
||||
let args = self.evaluate_once(registry).await?;
|
||||
pub async fn process<'de, T: Deserialize<'de>>(self) -> Result<(T, InputStream), ShellError> {
|
||||
let args = self.evaluate_once().await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
@ -156,14 +112,13 @@ pub struct RunnableContext {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub registry: CommandRegistry,
|
||||
pub scope: Scope,
|
||||
pub name: Tag,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
self.scope.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,6 +141,7 @@ impl EvaluatedWholeStreamCommandArgs {
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
input: impl Into<InputStream>,
|
||||
scope: Scope,
|
||||
) -> EvaluatedWholeStreamCommandArgs {
|
||||
EvaluatedWholeStreamCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
@ -193,6 +149,7 @@ impl EvaluatedWholeStreamCommandArgs {
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
scope,
|
||||
},
|
||||
input: input.into(),
|
||||
}
|
||||
@ -215,37 +172,6 @@ impl EvaluatedWholeStreamCommandArgs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub"]
|
||||
pub struct EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedFilterCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedFilterCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
) -> EvaluatedFilterCommandArgs {
|
||||
EvaluatedFilterCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct EvaluatedCommandArgs {
|
||||
@ -253,6 +179,7 @@ pub struct EvaluatedCommandArgs {
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: CallInfo,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl EvaluatedCommandArgs {
|
||||
@ -293,11 +220,7 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
@ -313,6 +236,97 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom commands are blocks, so we can use the information in the block to also
|
||||
// implement a WholeStreamCommand
|
||||
#[allow(clippy::suspicious_else_formatting)]
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Block {
|
||||
fn name(&self) -> &str {
|
||||
&self.params.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
self.params.clone()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let mut block = self.clone();
|
||||
block.set_redirect(call_info.args.external_redirection);
|
||||
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let evaluated = call_info.evaluate(&ctx).await?;
|
||||
|
||||
let input = args.input;
|
||||
ctx.scope.enter_scope();
|
||||
if let Some(args) = evaluated.args.positional {
|
||||
// FIXME: do not do this
|
||||
for arg in args.into_iter().zip(self.params.positional.iter()) {
|
||||
let name = arg.1 .0.name();
|
||||
|
||||
if name.starts_with('$') {
|
||||
ctx.scope.add_var(name, arg.0);
|
||||
} else {
|
||||
ctx.scope.add_var(format!("${}", name), arg.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(args) = evaluated.args.named {
|
||||
for named in &block.params.named {
|
||||
let name = named.0;
|
||||
if let Some(value) = args.get(name) {
|
||||
if name.starts_with('$') {
|
||||
ctx.scope.add_var(name, value.clone());
|
||||
} else {
|
||||
ctx.scope.add_var(format!("${}", name), value.clone());
|
||||
}
|
||||
} else if name.starts_with('$') {
|
||||
ctx.scope
|
||||
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||
} else {
|
||||
ctx.scope.add_var(
|
||||
format!("${}", name),
|
||||
UntaggedValue::nothing().into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for named in &block.params.named {
|
||||
let name = named.0;
|
||||
if name.starts_with('$') {
|
||||
ctx.scope
|
||||
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||
} else {
|
||||
ctx.scope.add_var(
|
||||
format!("${}", name),
|
||||
UntaggedValue::nothing().into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = run_block(&block, &ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||
|
||||
@ -352,19 +366,14 @@ impl Command {
|
||||
self.0.examples()
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, ®istry)).into_value(Tag::unknown()),
|
||||
UntaggedValue::string(get_help(&*cl, &args.scope)).into_value(Tag::unknown()),
|
||||
))))
|
||||
} else {
|
||||
self.0.run(args, registry).await
|
||||
self.0.run(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,69 +390,6 @@ impl Command {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnFilterCommand {
|
||||
name: String,
|
||||
func: fn(EvaluatedFilterCommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FnFilterCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"usage"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
CommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
..
|
||||
}: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let func = self.func;
|
||||
|
||||
Ok(input
|
||||
.then(move |it| {
|
||||
let host = host.clone();
|
||||
let registry = registry.clone();
|
||||
let ctrl_c = ctrl_c.clone();
|
||||
let shell_manager = shell_manager.clone();
|
||||
let call_info = call_info.clone();
|
||||
async move {
|
||||
let call_info = match call_info.evaluate_with_new_it(&*registry, &it).await {
|
||||
Err(err) => {
|
||||
return OutputStream::one(Err(err));
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let args = EvaluatedFilterCommandArgs::new(
|
||||
host.clone(),
|
||||
ctrl_c.clone(),
|
||||
shell_manager.clone(),
|
||||
call_info,
|
||||
);
|
||||
|
||||
match func(args) {
|
||||
Err(err) => return OutputStream::one(Err(err)),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
@ -28,40 +27,21 @@ impl WholeStreamCommand for Compact {
|
||||
"Creates a table with non-empty rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
compact(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
compact(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter out all null entries in a list",
|
||||
example: "echo [1 2 $null 3 $null $null] | compact",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
vec![Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -la | compact target",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compact(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (CompactArgs { rest: columns }, input) = args.process(®istry).await?;
|
||||
pub async fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CompactArgs { rest: columns }, input) = args.process().await?;
|
||||
Ok(input
|
||||
.filter_map(move |item| {
|
||||
future::ready(if columns.is_empty() {
|
||||
@ -95,11 +75,12 @@ pub async fn compact(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Compact;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Compact {})
|
||||
Ok(test_examples(Compact {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -20,12 +19,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
clear(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clear(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -37,10 +32,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::{CommandArgs, CommandRegistry, OutputStream};
|
||||
use crate::{CommandArgs, OutputStream};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
@ -20,11 +20,7 @@ impl WholeStreamCommand for Command {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
@ -1,15 +1,13 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
get: Tagged<String>,
|
||||
path: ColumnPath,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -21,7 +19,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config get").required(
|
||||
"get",
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::ColumnPath,
|
||||
"value to get from the config",
|
||||
)
|
||||
}
|
||||
@ -30,12 +28,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
get(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -47,21 +41,15 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (GetArgs { get }, _) = args.process(®istry).await?;
|
||||
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (GetArgs { path }, _) = args.process().await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
.get(&key)
|
||||
.ok_or_else(|| ShellError::labeled_error("Missing key in config", "key", get.tag()))?;
|
||||
let value = crate::commands::get::get_column_path(&path, &result)?;
|
||||
|
||||
Ok(match value {
|
||||
Value {
|
||||
@ -75,9 +63,6 @@ pub async fn get(
|
||||
|
||||
futures::stream::iter(list).to_output_stream()
|
||||
}
|
||||
x => {
|
||||
let x = x.clone();
|
||||
OutputStream::one(ReturnSuccess::value(x))
|
||||
}
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -31,22 +30,15 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Loads the config from the path given"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (LoadArgs { load }, _) = args.process(®istry).await?;
|
||||
let (LoadArgs { load }, _) = args.process().await?;
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -20,12 +19,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
path(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
path(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -37,10 +32,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn path(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let path = config::default_path()?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -30,12 +29,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
remove(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -47,12 +42,9 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||
let (RemoveArgs { remove }, _) = args.process().await?;
|
||||
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
key: Tagged<String>,
|
||||
path: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
@ -21,7 +19,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set")
|
||||
.required("key", SyntaxShape::String, "variable name to set")
|
||||
.required("key", SyntaxShape::ColumnPath, "variable name to set")
|
||||
.required("value", SyntaxShape::Any, "value to use")
|
||||
}
|
||||
|
||||
@ -29,39 +27,63 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set nonzero_exit_errors to true",
|
||||
example: "config set nonzero_exit_errors $true",
|
||||
vec![
|
||||
Example {
|
||||
description: "Set auto pivoting",
|
||||
example: "config set pivot_mode always",
|
||||
result: None,
|
||||
}]
|
||||
},
|
||||
Example {
|
||||
description: "Set line editor options",
|
||||
example: "config set line_editor [[edit_mode, completion_type]; [emacs circular]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set coloring options",
|
||||
example: "config set color_config [[header_align header_bold]; [left $true]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set nested options",
|
||||
example: "config set color_config.header_color white",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (SetArgs { key, value }, _) = args.process(®istry).await?;
|
||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (SetArgs { path, mut value }, _) = args.process().await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
let raw_entries = nu_data::config::read(&name_tag, &None)?;
|
||||
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
if let UntaggedValue::Table(rows) = &value.value {
|
||||
if rows.len() == 1 && rows[0].is_row() {
|
||||
value = rows[0].clone();
|
||||
}
|
||||
}
|
||||
|
||||
config::write(&result, &None)?;
|
||||
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Row(changes),
|
||||
..
|
||||
}) => {
|
||||
config::write(&changes.entries, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
||||
UntaggedValue::Row(changes).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -30,12 +29,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set_into(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set_into(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -47,14 +42,11 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_into(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (SetIntoArgs { set_into: v }, input) = args.process(®istry).await?;
|
||||
let (SetIntoArgs { set_into: v }, input) = args.process().await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
@ -30,13 +29,9 @@ impl WholeStreamCommand for Count {
|
||||
"Show the total number of rows or items."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (CountArgs { column }, input) = args.process(®istry).await?;
|
||||
let (CountArgs { column }, input) = args.process().await?;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
let count = if column {
|
||||
@ -80,11 +75,12 @@ impl WholeStreamCommand for Count {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Count {})
|
||||
Ok(test_examples(Count {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -36,14 +35,10 @@ impl WholeStreamCommand for Cpy {
|
||||
"Copy files."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _) = args.process(®istry).await?;
|
||||
let (args, _) = args.process().await?;
|
||||
shell_manager.cp(args, name)
|
||||
}
|
||||
|
||||
@ -66,11 +61,12 @@ impl WholeStreamCommand for Cpy {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cpy;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cpy {})
|
||||
Ok(test_examples(Cpy {})?)
|
||||
}
|
||||
}
|
||||
|
@ -19,15 +19,9 @@ impl WholeStreamCommand for Command {
|
||||
"Apply date function"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
@ -36,11 +30,12 @@ impl WholeStreamCommand for Command {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Dictionary, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::fmt::{self, write};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
format: Tagged<String>,
|
||||
raw: Option<bool>,
|
||||
table: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -24,40 +24,86 @@ impl WholeStreamCommand for Date {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date format")
|
||||
.required("format", SyntaxShape::String, "strftime format")
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
.switch("table", "print date in a table", Some('t'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"format the current date using the given format string."
|
||||
"Format a given date using the given format string."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
format(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
format(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Format the current date",
|
||||
example: "date now | date format '%Y.%m.%d_%H %M %S,%z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Format the current date and print in a table",
|
||||
example: "date now | date format -t '%Y-%m-%d_%H:%M:%S %z'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn format(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
pub async fn format(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (FormatArgs { format, raw }, _) = args.process(®istry).await?;
|
||||
let (FormatArgs { format, table }, input) = args.process().await?;
|
||||
|
||||
let dt_fmt = format.to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if let Some(true) = raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => {
|
||||
let mut output = String::new();
|
||||
if let Err(fmt::Error) =
|
||||
write(&mut output, format_args!("{}", dt.format(&format.item)))
|
||||
{
|
||||
Err(ShellError::labeled_error(
|
||||
"The date format is invalid",
|
||||
"invalid strftime format",
|
||||
&format.tag,
|
||||
))
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
let value = if table {
|
||||
let mut indexmap = IndexMap::new();
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(&output).into_value(&tag),
|
||||
);
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
} else {
|
||||
UntaggedValue::string(&output).into_value(&tag)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
75
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
75
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono_tz::TZ_VARIANTS;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date list-timezone"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date list-timezone")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List supported time zones."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
list_timezone(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "List all supported time zones",
|
||||
example: "date list-timezone",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all supported European time zones",
|
||||
example: "date list-timezone | where timezone =~ Europe",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let list = TZ_VARIANTS.iter().map(move |tz| {
|
||||
let mut entries = IndexMap::new();
|
||||
|
||||
entries.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(tz.name()).into_value(&tag),
|
||||
);
|
||||
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(&tag),
|
||||
))
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(list).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
pub mod command;
|
||||
pub mod format;
|
||||
pub mod list_timezone;
|
||||
pub mod now;
|
||||
pub mod utc;
|
||||
pub mod to_table;
|
||||
pub mod to_timezone;
|
||||
|
||||
mod utils;
|
||||
mod parser;
|
||||
|
||||
pub use command::Command as Date;
|
||||
pub use format::Date as DateFormat;
|
||||
pub use list_timezone::Date as DateListTimeZone;
|
||||
pub use now::Date as DateNow;
|
||||
pub use utc::Date as DateUTC;
|
||||
pub use to_table::Date as DateToTable;
|
||||
pub use to_timezone::Date as DateToTimeZone;
|
||||
|
@ -1,10 +1,8 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
@ -19,32 +17,34 @@ impl WholeStreamCommand for Date {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date."
|
||||
"Get the current date."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
now(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
now(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn now(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
pub async fn now(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
let now: DateTime<Local> = Local::now();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
@ -0,0 +1,107 @@
|
||||
// Modified from chrono::format::scan
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local, Offset, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
use titlecase::titlecase;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum ParseErrorKind {
|
||||
/// Given field is out of permitted range.
|
||||
OutOfRange,
|
||||
|
||||
/// The input string has some invalid character sequence for given formatting items.
|
||||
Invalid,
|
||||
|
||||
/// The input string has been prematurely ended.
|
||||
TooShort,
|
||||
}
|
||||
|
||||
pub fn datetime_in_timezone(
|
||||
dt: &DateTime<FixedOffset>,
|
||||
s: &str,
|
||||
) -> Result<DateTime<FixedOffset>, ParseErrorKind> {
|
||||
match timezone_offset_internal(s, true, true) {
|
||||
Ok(offset) => match FixedOffset::east_opt(offset) {
|
||||
Some(offset) => Ok(dt.with_timezone(&offset)),
|
||||
None => Err(ParseErrorKind::OutOfRange),
|
||||
},
|
||||
Err(ParseErrorKind::Invalid) => {
|
||||
if s.to_lowercase() == "local" {
|
||||
Ok(dt.with_timezone(Local::now().offset()))
|
||||
} else {
|
||||
let tz: Tz = parse_timezone_internal(s)?;
|
||||
let offset = tz.offset_from_utc_datetime(&dt.naive_utc()).fix();
|
||||
Ok(dt.with_timezone(&offset))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_timezone_internal(s: &str) -> Result<Tz, ParseErrorKind> {
|
||||
if let Ok(tz) = s.parse() {
|
||||
Ok(tz)
|
||||
} else if let Ok(tz) = titlecase(s).parse() {
|
||||
Ok(tz)
|
||||
} else if let Ok(tz) = s.to_uppercase().parse() {
|
||||
Ok(tz)
|
||||
} else {
|
||||
Err(ParseErrorKind::Invalid)
|
||||
}
|
||||
}
|
||||
|
||||
fn timezone_offset_internal(
|
||||
mut s: &str,
|
||||
consume_colon: bool,
|
||||
allow_missing_minutes: bool,
|
||||
) -> Result<i32, ParseErrorKind> {
|
||||
fn digits(s: &str) -> Result<(u8, u8), ParseErrorKind> {
|
||||
let b = s.as_bytes();
|
||||
if b.len() < 2 {
|
||||
Err(ParseErrorKind::TooShort)
|
||||
} else {
|
||||
Ok((b[0], b[1]))
|
||||
}
|
||||
}
|
||||
let negative = match s.as_bytes().first() {
|
||||
Some(&b'+') => false,
|
||||
Some(&b'-') => true,
|
||||
Some(_) => return Err(ParseErrorKind::Invalid),
|
||||
None => return Err(ParseErrorKind::TooShort),
|
||||
};
|
||||
s = &s[1..];
|
||||
|
||||
// hours (00--99)
|
||||
let hours = match digits(s)? {
|
||||
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
|
||||
_ => return Err(ParseErrorKind::Invalid),
|
||||
};
|
||||
s = &s[2..];
|
||||
|
||||
// colons (and possibly other separators)
|
||||
if consume_colon {
|
||||
s = s.trim_start_matches(|c: char| c == ':' || c.is_whitespace());
|
||||
}
|
||||
|
||||
// minutes (00--59)
|
||||
// if the next two items are digits then we have to add minutes
|
||||
let minutes = if let Ok(ds) = digits(s) {
|
||||
match ds {
|
||||
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
|
||||
(b'6'..=b'9', b'0'..=b'9') => return Err(ParseErrorKind::OutOfRange),
|
||||
_ => return Err(ParseErrorKind::Invalid),
|
||||
}
|
||||
} else if allow_missing_minutes {
|
||||
0
|
||||
} else {
|
||||
return Err(ParseErrorKind::TooShort);
|
||||
};
|
||||
match s.len() {
|
||||
len if len >= 2 => &s[2..],
|
||||
len if len == 0 => s,
|
||||
_ => return Err(ParseErrorKind::TooShort),
|
||||
};
|
||||
|
||||
let seconds = hours * 3600 + minutes * 60;
|
||||
Ok(if negative { -seconds } else { seconds })
|
||||
}
|
105
crates/nu-cli/src/commands/date/to_table.rs
Normal file
105
crates/nu-cli/src/commands/date/to_table.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-table"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date to-table")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the date in a structured table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
to_table(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Print the current date in a table",
|
||||
example: "date now | date to-table",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_table(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let input = args.input;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => {
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
110
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
110
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use crate::commands::date::parser::{datetime_in_timezone, ParseErrorKind};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DateToTimeZoneArgs {
|
||||
timezone: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-timezone"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date to-timezone").required(
|
||||
"time zone",
|
||||
SyntaxShape::String,
|
||||
"time zone description",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert a date to a given time zone.
|
||||
|
||||
Use `date list-timezone` to list all supported time zones.
|
||||
"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
to_timezone(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current date in UTC+05:00",
|
||||
example: "date now | date to-timezone +0500",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current local date",
|
||||
example: "date now | date to-timezone local",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current date in Hawaii",
|
||||
example: "date now | date to-timezone US/Hawaii",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DateToTimeZoneArgs { timezone }, input) = args.process().await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => match datetime_in_timezone(&dt, &timezone.item) {
|
||||
Ok(dt) => {
|
||||
let value = UntaggedValue::date(dt).into_value(&tag);
|
||||
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
error_message(e),
|
||||
"invalid time zone",
|
||||
&timezone.tag,
|
||||
)),
|
||||
},
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn error_message(err: ParseErrorKind) -> &'static str {
|
||||
match err {
|
||||
ParseErrorKind::Invalid => "The time zone description is invalid",
|
||||
ParseErrorKind::OutOfRange => "The time zone offset is out of range",
|
||||
ParseErrorKind::TooShort => "The format of the time zone is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
@ -22,21 +22,13 @@ impl WholeStreamCommand for Date {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
utc(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
utc(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
pub async fn utc(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
@ -48,3 +40,16 @@ pub async fn utc(
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::DateTime;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::UntaggedValue;
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
@ -24,21 +24,13 @@ impl WholeStreamCommand for Debug {
|
||||
"Print the Rust debug representation of the values"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
debug_value(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
debug_value(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn debug_value(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (DebugArgs { raw }, input) = args.process(®istry).await?;
|
||||
async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DebugArgs { raw }, input) = args.process().await?;
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if raw {
|
||||
@ -55,11 +47,12 @@ async fn debug_value(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Debug;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Debug {})
|
||||
Ok(test_examples(Debug {})?)
|
||||
}
|
||||
}
|
||||
|
48
crates/nu-cli/src/commands/def.rs
Normal file
48
crates/nu-cli/src/commands/def.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::CapturedBlock, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Def;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DefArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Tagged<Vec<Value>>,
|
||||
pub block: CapturedBlock,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Def {
|
||||
fn name(&self) -> &str {
|
||||
"def"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("def")
|
||||
.required("name", SyntaxShape::String, "the name of the command")
|
||||
.required(
|
||||
"params",
|
||||
SyntaxShape::Table,
|
||||
"the parameters of the command",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "the body of the command")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a command and set it to a definition."
|
||||
}
|
||||
|
||||
async fn run(&self, _args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
// Currently, we don't do anything here because we should have already
|
||||
// installed the definition as we entered the scope
|
||||
// We just create a command so that we can get proper coloring
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -34,12 +33,8 @@ impl WholeStreamCommand for Default {
|
||||
"Sets a default row's column if missing."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
default(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
default(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -51,12 +46,8 @@ impl WholeStreamCommand for Default {
|
||||
}
|
||||
}
|
||||
|
||||
async fn default(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (DefaultArgs { column, value }, input) = args.process(®istry).await?;
|
||||
async fn default(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DefaultArgs { column, value }, input) = args.process().await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
@ -83,11 +74,12 @@ async fn default(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Default;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Default {})
|
||||
Ok(test_examples(Default {})?)
|
||||
}
|
||||
}
|
||||
|
248
crates/nu-cli/src/commands/default_context.rs
Normal file
248
crates/nu-cli/src/commands/default_context.rs
Normal file
@ -0,0 +1,248 @@
|
||||
use crate::prelude::*;
|
||||
use std::error::Error;
|
||||
|
||||
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
|
||||
let context = EvaluationContext::basic()?;
|
||||
|
||||
{
|
||||
use crate::commands::*;
|
||||
|
||||
context.add_commands(vec![
|
||||
// Fundamentals
|
||||
whole_stream_command(NuPlugin),
|
||||
whole_stream_command(Let),
|
||||
whole_stream_command(LetEnv),
|
||||
whole_stream_command(Def),
|
||||
whole_stream_command(Source),
|
||||
// System/file operations
|
||||
whole_stream_command(Exec),
|
||||
whole_stream_command(Pwd),
|
||||
whole_stream_command(Ls),
|
||||
whole_stream_command(Du),
|
||||
whole_stream_command(Cd),
|
||||
whole_stream_command(Remove),
|
||||
whole_stream_command(Open),
|
||||
whole_stream_command(Config),
|
||||
whole_stream_command(ConfigGet),
|
||||
whole_stream_command(ConfigSet),
|
||||
whole_stream_command(ConfigSetInto),
|
||||
whole_stream_command(ConfigClear),
|
||||
whole_stream_command(ConfigLoad),
|
||||
whole_stream_command(ConfigRemove),
|
||||
whole_stream_command(ConfigPath),
|
||||
whole_stream_command(Help),
|
||||
whole_stream_command(History),
|
||||
whole_stream_command(Save),
|
||||
whole_stream_command(Touch),
|
||||
whole_stream_command(Cpy),
|
||||
whole_stream_command(Date),
|
||||
whole_stream_command(DateListTimeZone),
|
||||
whole_stream_command(DateNow),
|
||||
whole_stream_command(DateToTable),
|
||||
whole_stream_command(DateToTimeZone),
|
||||
whole_stream_command(DateFormat),
|
||||
whole_stream_command(Cal),
|
||||
whole_stream_command(Mkdir),
|
||||
whole_stream_command(Mv),
|
||||
whole_stream_command(Kill),
|
||||
whole_stream_command(Version),
|
||||
whole_stream_command(Clear),
|
||||
whole_stream_command(Describe),
|
||||
whole_stream_command(Which),
|
||||
whole_stream_command(Debug),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
whole_stream_command(Sleep),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
whole_stream_command(Benchmark),
|
||||
// Metadata
|
||||
whole_stream_command(Tags),
|
||||
// Shells
|
||||
whole_stream_command(Next),
|
||||
whole_stream_command(Previous),
|
||||
whole_stream_command(Shells),
|
||||
whole_stream_command(Enter),
|
||||
whole_stream_command(Exit),
|
||||
// Viz
|
||||
whole_stream_command(Chart),
|
||||
// Viewers
|
||||
whole_stream_command(Autoview),
|
||||
whole_stream_command(Table),
|
||||
// Text manipulation
|
||||
whole_stream_command(Hash),
|
||||
whole_stream_command(HashBase64),
|
||||
whole_stream_command(Split),
|
||||
whole_stream_command(SplitColumn),
|
||||
whole_stream_command(SplitRow),
|
||||
whole_stream_command(SplitChars),
|
||||
whole_stream_command(Lines),
|
||||
whole_stream_command(Echo),
|
||||
whole_stream_command(Parse),
|
||||
whole_stream_command(Str),
|
||||
whole_stream_command(StrToDecimal),
|
||||
whole_stream_command(StrToInteger),
|
||||
whole_stream_command(StrDowncase),
|
||||
whole_stream_command(StrUpcase),
|
||||
whole_stream_command(StrCapitalize),
|
||||
whole_stream_command(StrFindReplace),
|
||||
whole_stream_command(StrFrom),
|
||||
whole_stream_command(StrSubstring),
|
||||
whole_stream_command(StrSet),
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrContains),
|
||||
whole_stream_command(StrIndexOf),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(StrTrimLeft),
|
||||
whole_stream_command(StrTrimRight),
|
||||
whole_stream_command(StrStartsWith),
|
||||
whole_stream_command(StrEndsWith),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(StrLength),
|
||||
whole_stream_command(StrLPad),
|
||||
whole_stream_command(StrReverse),
|
||||
whole_stream_command(StrRPad),
|
||||
whole_stream_command(StrCamelCase),
|
||||
whole_stream_command(StrPascalCase),
|
||||
whole_stream_command(StrKebabCase),
|
||||
whole_stream_command(StrSnakeCase),
|
||||
whole_stream_command(StrScreamingSnakeCase),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(IntoInt),
|
||||
whole_stream_command(SplitBy),
|
||||
// Row manipulation
|
||||
whole_stream_command(Reverse),
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(Prepend),
|
||||
whole_stream_command(SortBy),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(GroupByDate),
|
||||
whole_stream_command(First),
|
||||
whole_stream_command(Last),
|
||||
whole_stream_command(Every),
|
||||
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),
|
||||
whole_stream_command(Default),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(SkipUntil),
|
||||
whole_stream_command(SkipWhile),
|
||||
whole_stream_command(Keep),
|
||||
whole_stream_command(KeepUntil),
|
||||
whole_stream_command(KeepWhile),
|
||||
whole_stream_command(Range),
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(EachGroup),
|
||||
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),
|
||||
whole_stream_command(Wrap),
|
||||
whole_stream_command(Pivot),
|
||||
whole_stream_command(Headers),
|
||||
whole_stream_command(Reduce),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Autoenv),
|
||||
whole_stream_command(AutoenvTrust),
|
||||
whole_stream_command(AutoenvUnTrust),
|
||||
whole_stream_command(Math),
|
||||
whole_stream_command(MathAbs),
|
||||
whole_stream_command(MathAverage),
|
||||
whole_stream_command(MathEval),
|
||||
whole_stream_command(MathMedian),
|
||||
whole_stream_command(MathMinimum),
|
||||
whole_stream_command(MathMode),
|
||||
whole_stream_command(MathMaximum),
|
||||
whole_stream_command(MathStddev),
|
||||
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),
|
||||
whole_stream_command(ToHTML),
|
||||
whole_stream_command(ToJSON),
|
||||
whole_stream_command(ToMarkdown),
|
||||
whole_stream_command(ToTOML),
|
||||
whole_stream_command(ToTSV),
|
||||
whole_stream_command(ToURL),
|
||||
whole_stream_command(ToYAML),
|
||||
whole_stream_command(ToXML),
|
||||
// File format input
|
||||
whole_stream_command(From),
|
||||
whole_stream_command(FromCSV),
|
||||
whole_stream_command(FromEML),
|
||||
whole_stream_command(FromTSV),
|
||||
whole_stream_command(FromSSV),
|
||||
whole_stream_command(FromINI),
|
||||
whole_stream_command(FromJSON),
|
||||
whole_stream_command(FromODS),
|
||||
whole_stream_command(FromTOML),
|
||||
whole_stream_command(FromURL),
|
||||
whole_stream_command(FromXLSX),
|
||||
whole_stream_command(FromXML),
|
||||
whole_stream_command(FromYAML),
|
||||
whole_stream_command(FromYML),
|
||||
whole_stream_command(FromIcs),
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
whole_stream_command(RunExternalCommand { interactive }),
|
||||
// Random value generation
|
||||
whole_stream_command(Random),
|
||||
whole_stream_command(RandomBool),
|
||||
whole_stream_command(RandomDice),
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
whole_stream_command(RandomUUID),
|
||||
whole_stream_command(RandomInteger),
|
||||
whole_stream_command(RandomDecimal),
|
||||
whole_stream_command(RandomChars),
|
||||
// Path
|
||||
whole_stream_command(PathBasename),
|
||||
whole_stream_command(PathCommand),
|
||||
whole_stream_command(PathDirname),
|
||||
whole_stream_command(PathExists),
|
||||
whole_stream_command(PathExpand),
|
||||
whole_stream_command(PathExtension),
|
||||
whole_stream_command(PathFilestem),
|
||||
whole_stream_command(PathType),
|
||||
// Url
|
||||
whole_stream_command(UrlCommand),
|
||||
whole_stream_command(UrlScheme),
|
||||
whole_stream_command(UrlPath),
|
||||
whole_stream_command(UrlHost),
|
||||
whole_stream_command(UrlQuery),
|
||||
whole_stream_command(Seq),
|
||||
whole_stream_command(SeqDates),
|
||||
]);
|
||||
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
{
|
||||
context.add_commands(vec![whole_stream_command(crate::commands::clip::Clip)]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
@ -4,13 +4,13 @@ use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct What;
|
||||
pub struct Describe;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WhatArgs {}
|
||||
pub struct DescribeArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for What {
|
||||
impl WholeStreamCommand for Describe {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
@ -23,19 +23,12 @@ impl WholeStreamCommand for What {
|
||||
"Describes the objects in the stream."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
what(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
describe(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn what(
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn describe(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(|row| {
|
||||
@ -49,12 +42,13 @@ pub async fn what(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::What;
|
||||
use super::Describe;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(What {})
|
||||
Ok(test_examples(Describe {})?)
|
||||
}
|
||||
}
|
@ -2,15 +2,13 @@ use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::ExternalRedirection, ReturnSuccess, Signature, SyntaxShape, Value,
|
||||
};
|
||||
use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DoArgs {
|
||||
block: Block,
|
||||
block: CapturedBlock,
|
||||
ignore_errors: bool,
|
||||
}
|
||||
|
||||
@ -34,12 +32,8 @@ impl WholeStreamCommand for Do {
|
||||
"Runs a block, optionally ignoring errors"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
do_(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
do_(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -52,28 +46,23 @@ impl WholeStreamCommand for Do {
|
||||
Example {
|
||||
description: "Run the block and ignore errors",
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: Some(vec![Value::nothing()]),
|
||||
result: Some(vec![]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||
|
||||
let mut context = EvaluationContext::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let context = EvaluationContext::from_raw(&raw_args);
|
||||
let (
|
||||
DoArgs {
|
||||
ignore_errors,
|
||||
mut block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
) = raw_args.process().await?;
|
||||
|
||||
let block_redirection = match external_redirection {
|
||||
ExternalRedirection::None => {
|
||||
@ -93,17 +82,10 @@ async fn do_(
|
||||
x => x,
|
||||
};
|
||||
|
||||
block.set_redirect(block_redirection);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
block.block.set_redirect(block_redirection);
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&block.block, &context, input).await;
|
||||
context.scope.exit_scope();
|
||||
|
||||
if ignore_errors {
|
||||
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
||||
@ -115,7 +97,7 @@ async fn do_(
|
||||
context.clear_errors();
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
|
||||
Err(_) => Ok(OutputStream::empty()),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_output_stream())
|
||||
@ -125,11 +107,12 @@ async fn do_(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Do;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Do {})
|
||||
Ok(test_examples(Do {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -30,12 +29,8 @@ impl WholeStreamCommand for Drop {
|
||||
"Remove the last number of rows. If you want to remove columns, try 'reject'."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
drop(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
drop(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -57,8 +52,8 @@ impl WholeStreamCommand for Drop {
|
||||
}
|
||||
}
|
||||
|
||||
async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let (DropArgs { rows }, input) = args.process(®istry).await?;
|
||||
async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DropArgs { rows }, input) = args.process().await?;
|
||||
let v: Vec<_> = input.into_vec().await;
|
||||
|
||||
let rows_to_drop = if let Some(quantity) = rows {
|
||||
@ -85,11 +80,12 @@ async fn drop(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Drop;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Drop {})
|
||||
Ok(test_examples(Drop {})?)
|
||||
}
|
||||
}
|
||||
|
@ -73,12 +73,8 @@ impl WholeStreamCommand for Du {
|
||||
"Find disk usage sizes of specified items"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
du(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
du(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -90,13 +86,12 @@ impl WholeStreamCommand for Du {
|
||||
}
|
||||
}
|
||||
|
||||
async fn du(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn du(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c.clone();
|
||||
let ctrl_c_copy = ctrl_c.clone();
|
||||
|
||||
let (args, _): (DuArgs, _) = args.process(®istry).await?;
|
||||
let (args, _): (DuArgs, _) = args.process().await?;
|
||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||
Pattern::new(&x.item)
|
||||
.map(Option::Some)
|
||||
@ -427,11 +422,12 @@ impl From<FileInfo> for Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Du;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Du {})
|
||||
Ok(test_examples(Du {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
@ -6,8 +5,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::CapturedBlock, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
@ -15,7 +13,7 @@ pub struct Each;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachArgs {
|
||||
block: Block,
|
||||
block: CapturedBlock,
|
||||
numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
@ -39,12 +37,8 @@ impl WholeStreamCommand for Each {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
each(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
each(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -73,36 +67,39 @@ 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>,
|
||||
captured_block: Arc<Box<CapturedBlock>>,
|
||||
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 !captured_block.block.params.positional.is_empty() {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
};
|
||||
Ok(run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_vars(&captured_block.captured.entries);
|
||||
|
||||
if !captured_block.block.params.positional.is_empty() {
|
||||
// FIXME: add check for more than parameter, once that's supported
|
||||
context
|
||||
.scope
|
||||
.add_var(captured_block.block.params.positional[0].0.name(), input);
|
||||
} else {
|
||||
context.scope.add_var("$it", input);
|
||||
}
|
||||
|
||||
let result = run_block(&captured_block.block, &*context, input_stream).await;
|
||||
|
||||
context.scope.exit_scope();
|
||||
|
||||
Ok(result?.to_output_stream())
|
||||
}
|
||||
|
||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
@ -113,29 +110,22 @@ pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
dict.into_value()
|
||||
}
|
||||
|
||||
async fn each(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
async fn each(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process().await?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
|
||||
if each_args.numbered.item {
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.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, context, row).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
@ -147,12 +137,10 @@ async fn each(
|
||||
Ok(input
|
||||
.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, context, input).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
@ -166,11 +154,12 @@ async fn each(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Each;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Each {})
|
||||
Ok(test_examples(Each {})?)
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
hir::CapturedBlock, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
@ -14,7 +13,7 @@ pub struct EachGroup;
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachGroupArgs {
|
||||
group_size: Tagged<usize>,
|
||||
block: Block,
|
||||
block: CapturedBlock,
|
||||
//numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
@ -46,29 +45,14 @@ impl WholeStreamCommand for EachGroup {
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
async fn run(&self, raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process().await?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
|
||||
Ok(input
|
||||
.chunks(each_args.group_size.item)
|
||||
.then(move |input| {
|
||||
run_block_on_vec(
|
||||
input,
|
||||
block.clone(),
|
||||
scope.clone(),
|
||||
head.clone(),
|
||||
context.clone(),
|
||||
)
|
||||
})
|
||||
.then(move |input| run_block_on_vec(input, block.clone(), context.clone()))
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
@ -76,9 +60,7 @@ impl WholeStreamCommand for EachGroup {
|
||||
|
||||
pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Block>,
|
||||
scope: Arc<Scope>,
|
||||
head: Arc<Box<SpannedExpression>>,
|
||||
block: Arc<Box<CapturedBlock>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
let value = Value {
|
||||
@ -87,7 +69,7 @@ pub(crate) fn run_block_on_vec(
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, scope, head, context, value).await {
|
||||
match process_row(block, 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.
|
||||
@ -123,11 +105,12 @@ pub(crate) fn run_block_on_vec(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachGroup;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachGroup {})
|
||||
Ok(test_examples(EachGroup {})?)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
//use itertools::Itertools;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Primitive, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_protocol::{hir::CapturedBlock, Primitive, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
@ -12,7 +12,7 @@ pub struct EachWindow;
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachWindowArgs {
|
||||
window_size: Tagged<usize>,
|
||||
block: Block,
|
||||
block: CapturedBlock,
|
||||
stride: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
@ -50,17 +50,10 @@ impl WholeStreamCommand for EachWindow {
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(®istry).await?;
|
||||
let block = Arc::new(each_args.block);
|
||||
async fn run(&self, raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process().await?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
|
||||
let mut window: Vec<_> = input
|
||||
.by_ref()
|
||||
@ -81,14 +74,12 @@ impl WholeStreamCommand for EachWindow {
|
||||
window.push(input);
|
||||
|
||||
let block = block.clone();
|
||||
let scope = scope.clone();
|
||||
let head = head.clone();
|
||||
let context = context.clone();
|
||||
let local_window = window.clone();
|
||||
|
||||
async move {
|
||||
if i % stride == 0 {
|
||||
Some(run_block_on_vec(local_window, block, scope, head, context).await)
|
||||
Some(run_block_on_vec(local_window, block, context).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -103,11 +94,12 @@ impl WholeStreamCommand for EachWindow {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachWindow;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(EachWindow {})
|
||||
Ok(test_examples(EachWindow {})?)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
||||
|
||||
pub struct Echo;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
@ -27,12 +27,8 @@ impl WholeStreamCommand for Echo {
|
||||
"Echo the arguments back to the user."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
echo(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
echo(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -51,9 +47,8 @@ impl WholeStreamCommand for Echo {
|
||||
}
|
||||
}
|
||||
|
||||
async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||
async fn echo(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (args, _): (EchoArgs, _) = args.process().await?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i.as_string() {
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
@ -69,7 +64,7 @@ async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag,
|
||||
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
|
||||
x => OutputStream::one(Ok(ReturnSuccess::Value(x))),
|
||||
},
|
||||
});
|
||||
|
||||
@ -162,11 +157,12 @@ impl Iterator for RangeIterator {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Echo {})
|
||||
Ok(test_examples(Echo {})?)
|
||||
}
|
||||
}
|
||||
|
275
crates/nu-cli/src/commands/empty.rs
Normal file
275
crates/nu-cli/src/commands/empty.rs
Normal file
@ -0,0 +1,275 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::{as_string, ValueExt};
|
||||
|
||||
use futures::stream::once;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness. Pass an optional block to replace if empty",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check for empty values"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Check if a value is empty",
|
||||
example: "echo '' | empty?",
|
||||
result: Some(vec![UntaggedValue::boolean(true).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "more than one column",
|
||||
example: "echo [[meal size]; [arepa small] [taco '']] | empty? meal size",
|
||||
result: Some(
|
||||
vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"meal".to_string() => Value::from(false),
|
||||
"size".to_string() => Value::from(false),
|
||||
})
|
||||
.into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"meal".to_string() => Value::from(false),
|
||||
"size".to_string() => Value::from(true),
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
),
|
||||
},Example {
|
||||
description: "use a block if setting the empty cell contents is wanted",
|
||||
example: "echo [[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 { = [33 37] }",
|
||||
result: Some(
|
||||
vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"2020/04/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(33).into(), UntaggedValue::int(37).into()]).into(),
|
||||
"2020/07/10".to_string() => UntaggedValue::table(&[UntaggedValue::int(27).into()]).into(),
|
||||
"2020/11/16".to_string() => UntaggedValue::table(&[UntaggedValue::int(37).into()]).into(),
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let name_tag = Arc::new(args.call_info.name_tag.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args));
|
||||
let (Arguments { rest }, input) = args.process().await?;
|
||||
let (columns, default_block): (Vec<ColumnPath>, Option<Box<CapturedBlock>>) = arguments(rest)?;
|
||||
let default_block = Arc::new(default_block);
|
||||
|
||||
if input.is_empty() {
|
||||
let stream = futures::stream::iter(vec![
|
||||
UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)
|
||||
]);
|
||||
|
||||
return Ok(InputStream::from_stream(stream)
|
||||
.then(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = vec![];
|
||||
|
||||
async {
|
||||
match process_row(context, input, block, columns, tag).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream());
|
||||
}
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = columns.clone();
|
||||
|
||||
async {
|
||||
match process_row(context, input, block, columns, tag).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn arguments(
|
||||
rest: Vec<Value>,
|
||||
) -> Result<(Vec<ColumnPath>, Option<Box<CapturedBlock>>), ShellError> {
|
||||
let mut rest = rest;
|
||||
let mut columns = vec![];
|
||||
let mut default = None;
|
||||
|
||||
let last_argument = rest.pop();
|
||||
|
||||
match last_argument {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Block(call),
|
||||
..
|
||||
}) => default = Some(call),
|
||||
Some(other) => {
|
||||
let Tagged { item: path, .. } = other.as_column_path()?;
|
||||
|
||||
columns = vec![path];
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
for argument in rest {
|
||||
let Tagged { item: path, .. } = argument.as_column_path()?;
|
||||
|
||||
columns.push(path);
|
||||
}
|
||||
|
||||
Ok((columns, default))
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
default_block: Arc<Option<Box<CapturedBlock>>>,
|
||||
column_paths: Vec<ColumnPath>,
|
||||
tag: Arc<Tag>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let _tag = &*tag;
|
||||
let mut out = Arc::new(None);
|
||||
let results = Arc::make_mut(&mut out);
|
||||
|
||||
if let Some(default_block) = &*default_block {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_vars(&default_block.captured.entries);
|
||||
context.scope.add_var("$it", input.clone());
|
||||
|
||||
let stream = run_block(&default_block.block, &*context, input_stream).await;
|
||||
context.scope.exit_scope();
|
||||
|
||||
let mut stream = stream?;
|
||||
*results = Some({
|
||||
let values = stream.drain_vec().await;
|
||||
|
||||
let errors = context.get_errors();
|
||||
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value."))?;
|
||||
|
||||
Value {
|
||||
value: value.value.clone(),
|
||||
tag: input.tag.clone(),
|
||||
}
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_value(&input.tag)
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_value(&input.tag)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
ref tag,
|
||||
} => {
|
||||
if column_paths.is_empty() {
|
||||
Ok(OutputStream::one(ReturnSuccess::value({
|
||||
let is_empty = input.is_empty();
|
||||
|
||||
if default_block.is_some() {
|
||||
if is_empty {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||
} else {
|
||||
input.clone()
|
||||
}
|
||||
} else {
|
||||
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
let mut obj = input.clone();
|
||||
|
||||
for column in column_paths.clone() {
|
||||
let path = UntaggedValue::Primitive(Primitive::ColumnPath(column.clone()))
|
||||
.into_value(tag);
|
||||
let data = r.get_data(&as_string(&path)?).borrow().clone();
|
||||
let is_empty = data.is_empty();
|
||||
|
||||
let default = if default_block.is_some() {
|
||||
if is_empty {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(tag))
|
||||
} else {
|
||||
data.clone()
|
||||
}
|
||||
} else {
|
||||
UntaggedValue::boolean(is_empty).into_value(tag)
|
||||
};
|
||||
|
||||
if let Ok(value) =
|
||||
obj.swap_data_by_column_path(&column, Box::new(move |_| Ok(default)))
|
||||
{
|
||||
obj = value;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(obj)))
|
||||
}
|
||||
}
|
||||
other => Ok(OutputStream::one(ReturnSuccess::value({
|
||||
if other.is_empty() {
|
||||
results
|
||||
.clone()
|
||||
.unwrap_or_else(|| UntaggedValue::boolean(true).into_value(other.tag))
|
||||
} else {
|
||||
UntaggedValue::boolean(false).into_value(other.tag)
|
||||
}
|
||||
}))),
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
@ -50,12 +49,8 @@ For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
enter(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
enter(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -79,19 +74,15 @@ documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
}
|
||||
|
||||
async fn enter(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let scope = raw_args.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process(®istry).await?;
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process().await?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
|
||||
@ -101,7 +92,7 @@ async fn enter(
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
|
||||
if registry.has(command) {
|
||||
if scope.has_command(command) {
|
||||
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
@ -121,11 +112,12 @@ async fn enter(
|
||||
let cwd = shell_manager.path();
|
||||
|
||||
let full_path = std::path::PathBuf::from(cwd);
|
||||
let span = location.span();
|
||||
|
||||
let (file_extension, tagged_contents) = crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
tag.span,
|
||||
span,
|
||||
encoding,
|
||||
)
|
||||
.await?;
|
||||
@ -134,7 +126,7 @@ async fn enter(
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
if let Some(extension) = file_extension {
|
||||
let command_name = format!("from {}", extension);
|
||||
if let Some(converter) = registry.get_command(&command_name) {
|
||||
if let Some(converter) = scope.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
@ -149,12 +141,12 @@ async fn enter(
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
scope: scope.clone(),
|
||||
},
|
||||
scope: scope.clone(),
|
||||
};
|
||||
let tag = tagged_contents.tag.clone();
|
||||
let mut result = converter
|
||||
.run(new_args.with_input(vec![tagged_contents]), ®istry)
|
||||
.run(new_args.with_input(vec![tagged_contents]))
|
||||
.await?;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
@ -191,11 +183,12 @@ async fn enter(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Enter;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Enter {})
|
||||
Ok(test_examples(Enter {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -37,12 +36,8 @@ impl WholeStreamCommand for Every {
|
||||
"Show (or skip) every n-th row, starting from the first one."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
every(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
every(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -68,9 +63,8 @@ impl WholeStreamCommand for Every {
|
||||
}
|
||||
}
|
||||
|
||||
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (EveryArgs { stride, skip }, input) = args.process(®istry).await?;
|
||||
async fn every(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (EveryArgs { stride, skip }, input) = args.process().await?;
|
||||
|
||||
let stride = stride.item;
|
||||
let skip = skip.item;
|
||||
@ -93,11 +87,12 @@ async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Every;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Every {})
|
||||
Ok(test_examples(Every {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -30,12 +29,8 @@ impl WholeStreamCommand for Exec {
|
||||
"Execute command"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exec(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
exec(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -55,13 +50,12 @@ impl WholeStreamCommand for Exec {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
async fn exec(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _): (ExecArgs, _) = args.process(®istry).await?;
|
||||
let (args, _): (ExecArgs, _) = args.process().await?;
|
||||
|
||||
let mut command = Command::new(args.command.item);
|
||||
for tagged_arg in args.rest {
|
||||
@ -78,7 +72,7 @@ async fn exec(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn exec(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
async fn exec(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
"exec is not supported on your platform",
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::command::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -20,12 +19,8 @@ impl WholeStreamCommand for Exit {
|
||||
"Exit the current shell (or all shells)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
exit(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
exit(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -44,12 +39,8 @@ impl WholeStreamCommand for Exit {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exit(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
pub async fn exit(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
|
||||
let command_action = if args.call_info.args.has("now") {
|
||||
CommandAction::Exit
|
||||
@ -63,11 +54,12 @@ pub async fn exit(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Exit;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Exit {})
|
||||
Ok(test_examples(Exit {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
@ -30,12 +29,8 @@ impl WholeStreamCommand for First {
|
||||
"Show only the first number of rows."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
first(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
first(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -57,9 +52,8 @@ impl WholeStreamCommand for First {
|
||||
}
|
||||
}
|
||||
|
||||
async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (FirstArgs { rows }, input) = args.process(®istry).await?;
|
||||
async fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (FirstArgs { rows }, input) = args.process().await?;
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
@ -72,11 +66,12 @@ async fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::First;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(First {})
|
||||
Ok(test_examples(First {})?)
|
||||
}
|
||||
}
|
||||
|
183
crates/nu-cli/src/commands/flatten.rs
Normal file
183
crates/nu-cli/src/commands/flatten.rs
Normal file
@ -0,0 +1,183 @@
|
||||
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) -> Result<OutputStream, ShellError> {
|
||||
flatten(args).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) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (Arguments { rest: columns }, input) = args.process().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())
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
use crate::command_registry::CommandRegistry;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
@ -32,12 +31,8 @@ impl WholeStreamCommand for Format {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
format_command(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
format_command(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -49,13 +44,9 @@ impl WholeStreamCommand for Format {
|
||||
}
|
||||
}
|
||||
|
||||
async fn format_command(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(args.call_info.scope.clone());
|
||||
let (FormatArgs { pattern }, input) = args.process(®istry).await?;
|
||||
async fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let ctx = Arc::new(EvaluationContext::from_args(&args));
|
||||
let (FormatArgs { pattern }, input) = args.process().await?;
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = Arc::new(format_pattern);
|
||||
@ -64,8 +55,7 @@ async fn format_command(
|
||||
.then(move |value| {
|
||||
let mut output = String::new();
|
||||
let commands = commands.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let ctx = ctx.clone();
|
||||
|
||||
async move {
|
||||
for command in &*commands {
|
||||
@ -77,17 +67,13 @@ async fn format_command(
|
||||
// FIXME: use the correct spans
|
||||
let full_column_path = nu_parser::parse_full_column_path(
|
||||
&(c.to_string()).spanned(Span::unknown()),
|
||||
&*registry,
|
||||
&ctx.scope,
|
||||
);
|
||||
|
||||
let result = evaluate_baseline_expr(
|
||||
&full_column_path.0,
|
||||
®istry,
|
||||
&value,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_var("$it", value.clone());
|
||||
let result = evaluate_baseline_expr(&full_column_path.0, &*ctx).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
if let Ok(c) = result {
|
||||
output
|
||||
@ -153,11 +139,12 @@ fn format(input: &str) -> Vec<FormatCommand> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Format;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Format {})
|
||||
Ok(test_examples(Format {})?)
|
||||
}
|
||||
}
|
186
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
186
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
@ -0,0 +1,186 @@
|
||||
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) -> Result<OutputStream, ShellError> {
|
||||
filesize(args).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 existence check was already done, this shouldn't trigger never"),
|
||||
)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn filesize(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { field, format }, input) = raw_args.process().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;
|
@ -19,14 +19,9 @@ impl WholeStreamCommand for From {
|
||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, ®istry))
|
||||
UntaggedValue::string(crate::commands::help::get_help(&From, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
@ -35,11 +30,12 @@ impl WholeStreamCommand for From {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::From;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(From {})
|
||||
Ok(test_examples(From {})?)
|
||||
}
|
||||
}
|
||||
|
@ -37,12 +37,8 @@ impl WholeStreamCommand for FromCSV {
|
||||
"Parse text as .csv and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_csv(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_csv(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -66,11 +62,7 @@ impl WholeStreamCommand for FromCSV {
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_csv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
@ -79,7 +71,7 @@ async fn from_csv(
|
||||
separator,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
) = args.process().await?;
|
||||
let sep = match separator {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
@ -109,11 +101,12 @@ async fn from_csv(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromCSV;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromCSV {})
|
||||
Ok(test_examples(FromCSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ pub async fn from_delimited_data(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = name;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
let sample_lines = concat_string.item.lines().take(3).collect_vec().join("\n");
|
||||
|
||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
@ -65,10 +66,16 @@ pub async fn from_delimited_data(
|
||||
},
|
||||
Err(err) => {
|
||||
let line_one = match pretty_csv_error(err) {
|
||||
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
|
||||
None => format!("Could not parse as {}", format_name),
|
||||
Some(pretty) => format!(
|
||||
"Could not parse as {} split by '{}' ({})",
|
||||
format_name, sep, pretty
|
||||
),
|
||||
None => format!("Could not parse as {} split by '{}'", format_name, sep),
|
||||
};
|
||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
||||
let line_two = format!(
|
||||
"input cannot be parsed as {} split by '{}'. Input's first lines:\n{}",
|
||||
format_name, sep, sample_lines
|
||||
);
|
||||
|
||||
Err(ShellError::labeled_error_with_secondary(
|
||||
line_one,
|
||||
|
@ -35,12 +35,8 @@ impl WholeStreamCommand for FromEML {
|
||||
"Parse text as .eml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_eml(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_eml(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,13 +73,9 @@ fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedVal
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_eml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn from_eml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (eml_args, input): (FromEMLArgs, _) = args.process(®istry).await?;
|
||||
let (eml_args, input): (FromEMLArgs, _) = args.process().await?;
|
||||
let value = input.collect_string(tag.clone()).await?;
|
||||
|
||||
let body_preview = eml_args
|
||||
@ -130,11 +122,12 @@ async fn from_eml(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromEML;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromEML {})
|
||||
Ok(test_examples(FromEML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -23,21 +23,13 @@ impl WholeStreamCommand for FromIcs {
|
||||
"Parse text as .ics and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ics(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_ics(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_ics(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_ics(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -249,11 +241,12 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromIcs;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromIcs {})
|
||||
Ok(test_examples(FromIcs {})?)
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,8 @@ impl WholeStreamCommand for FromINI {
|
||||
"Parse text as .ini and create table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ini(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_ini(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,12 +60,8 @@ pub fn from_ini_string_to_value(
|
||||
Ok(convert_ini_top_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
async fn from_ini(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_ini(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
let concat_string = input.collect_string(tag.clone()).await?;
|
||||
@ -95,11 +87,12 @@ async fn from_ini(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromINI;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromINI {})
|
||||
Ok(test_examples(FromINI {})?)
|
||||
}
|
||||
}
|
||||
|
@ -28,35 +28,31 @@ impl WholeStreamCommand for FromJSON {
|
||||
"Parse text as .json and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_json(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_json(args).await
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||
fn convert_json_value_to_nu_value(v: &nu_json::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::String(s) => {
|
||||
nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
nu_json::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
nu_json::Value::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
||||
}
|
||||
serde_hjson::Value::Array(a) => UntaggedValue::Table(
|
||||
nu_json::Value::Array(a) => UntaggedValue::Table(
|
||||
a.iter()
|
||||
.map(|x| convert_json_value_to_nu_value(x, &tag))
|
||||
.collect(),
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_hjson::Value::Object(o) => {
|
||||
nu_json::Value::Object(o) => {
|
||||
let mut collected = TaggedDictBuilder::new(&tag);
|
||||
for (k, v) in o.iter() {
|
||||
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
|
||||
@ -67,19 +63,15 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson::Result<Value> {
|
||||
let v: serde_hjson::Value = serde_hjson::from_str(&s)?;
|
||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
|
||||
let v: nu_json::Value = nu_json::from_str(&s)?;
|
||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
async fn from_json(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
|
||||
let (FromJSONArgs { objects }, input) = args.process(®istry).await?;
|
||||
let (FromJSONArgs { objects }, input) = args.process().await?;
|
||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||
|
||||
let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
|
||||
@ -96,7 +88,7 @@ async fn from_json(
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
message.push(')');
|
||||
|
||||
Some(Err(ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
@ -125,7 +117,7 @@ async fn from_json(
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
message.push(')');
|
||||
|
||||
Ok(OutputStream::one(Err(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
@ -144,11 +136,12 @@ async fn from_json(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromJSON;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromJSON {})
|
||||
Ok(test_examples(FromJSON {})?)
|
||||
}
|
||||
}
|
||||
|
@ -31,29 +31,21 @@ impl WholeStreamCommand for FromODS {
|
||||
"Parse OpenDocument Spreadsheet(.ods) data and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ods(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_ods(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_ods(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn from_ods(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
|
||||
let (
|
||||
FromODSArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
) = args.process().await?;
|
||||
let bytes = input.collect_binary(tag.clone()).await?;
|
||||
let buf: Cursor<Vec<u8>> = Cursor::new(bytes.item);
|
||||
let mut ods = Ods::<_>::new(buf).map_err(|_| {
|
||||
@ -102,11 +94,12 @@ async fn from_ods(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromODS;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromODS {})
|
||||
Ok(test_examples(FromODS {})?)
|
||||
}
|
||||
}
|
||||
|
@ -46,12 +46,8 @@ impl WholeStreamCommand for FromSSV {
|
||||
"Parse text as space-separated values and create a table. The default minimum number of spaces counted as a separator is 2."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_ssv(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_ssv(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +131,7 @@ fn parse_aligned_columns<'a>(
|
||||
.flat_map(|s| find_indices(*s))
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
indices.sort();
|
||||
indices.sort_unstable();
|
||||
indices.dedup();
|
||||
|
||||
let headers: Vec<(String, usize)> = indices
|
||||
@ -251,12 +247,8 @@ fn from_ssv_string_to_value(
|
||||
Some(UntaggedValue::Table(rows).into_value(&tag))
|
||||
}
|
||||
|
||||
async fn from_ssv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn from_ssv(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromSSVArgs {
|
||||
headerless,
|
||||
@ -264,7 +256,7 @@ async fn from_ssv(
|
||||
minimum_spaces,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
) = args.process().await?;
|
||||
let concat_string = input.collect_string(name.clone()).await?;
|
||||
let split_at = match minimum_spaces {
|
||||
Some(number) => number.item,
|
||||
@ -302,7 +294,9 @@ async fn from_ssv(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::*;
|
||||
|
||||
fn owned(x: &str, y: &str) -> (String, String) {
|
||||
(String::from(x), String::from(y))
|
||||
}
|
||||
@ -504,10 +498,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use super::FromSSV;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromSSV {})
|
||||
Ok(test_examples(FromSSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,8 @@ impl WholeStreamCommand for FromTOML {
|
||||
"Parse text as .toml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_toml(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_toml(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,12 +61,8 @@ pub fn from_toml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
|
||||
Ok(convert_toml_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
pub async fn from_toml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
pub async fn from_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -101,11 +93,12 @@ pub async fn from_toml(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTOML;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTOML {})
|
||||
Ok(test_examples(FromTOML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -29,22 +29,14 @@ impl WholeStreamCommand for FromTSV {
|
||||
"Parse text as .tsv and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_tsv(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_tsv(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_tsv(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
async fn from_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (FromTSVArgs { headerless }, input) = args.process(®istry).await?;
|
||||
let (FromTSVArgs { headerless }, input) = args.process().await?;
|
||||
|
||||
from_delimited_data(headerless, '\t', "TSV", input, name).await
|
||||
}
|
||||
@ -52,11 +44,12 @@ async fn from_tsv(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromTSV;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromTSV {})
|
||||
Ok(test_examples(FromTSV {})?)
|
||||
}
|
||||
}
|
||||
|
@ -19,21 +19,13 @@ impl WholeStreamCommand for FromURL {
|
||||
"Parse url-encoded string as a table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_url(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_url(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_url(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_url(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -64,11 +56,12 @@ async fn from_url(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromURL;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromURL {})
|
||||
Ok(test_examples(FromURL {})?)
|
||||
}
|
||||
}
|
||||
|
@ -22,21 +22,13 @@ impl WholeStreamCommand for FromVcf {
|
||||
"Parse text as .vcf and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_vcf(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_vcf(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_vcf(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_vcf(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -104,11 +96,12 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromVcf;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromVcf {})
|
||||
Ok(test_examples(FromVcf {})?)
|
||||
}
|
||||
}
|
||||
|
@ -31,28 +31,20 @@ impl WholeStreamCommand for FromXLSX {
|
||||
"Parse binary Excel(.xlsx) data and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_xlsx(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_xlsx(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_xlsx(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
async fn from_xlsx(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let span = tag.span;
|
||||
let registry = registry.clone();
|
||||
let (
|
||||
FromXLSXArgs {
|
||||
headerless: _headerless,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
) = args.process().await?;
|
||||
let value = input.collect_binary(tag.clone()).await?;
|
||||
|
||||
let buf: Cursor<Vec<u8>> = Cursor::new(value.item);
|
||||
@ -102,11 +94,12 @@ async fn from_xlsx(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FromXLSX;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXLSX {})
|
||||
Ok(test_examples(FromXLSX {})?)
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,8 @@ impl WholeStreamCommand for FromXML {
|
||||
"Parse text as .xml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_xml(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_xml(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +35,7 @@ fn from_attributes_to_value(attributes: &[roxmltree::Attribute], tag: impl Into<
|
||||
collected.into_value()
|
||||
}
|
||||
|
||||
fn from_node_to_value<'a, 'd>(n: &roxmltree::Node<'a, 'd>, tag: impl Into<Tag>) -> Value {
|
||||
fn from_node_to_value(n: &roxmltree::Node, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
if n.is_element() {
|
||||
@ -99,12 +95,8 @@ pub fn from_xml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value,
|
||||
Ok(from_document_to_value(&parsed, tag))
|
||||
}
|
||||
|
||||
async fn from_xml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_xml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -135,6 +127,7 @@ async fn from_xml(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use crate::commands::from_xml;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
@ -304,10 +297,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use super::FromXML;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromXML {})
|
||||
Ok(test_examples(FromXML {})?)
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,8 @@ impl WholeStreamCommand for FromYAML {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,12 +40,8 @@ impl WholeStreamCommand for FromYML {
|
||||
"Parse text as .yaml/.yml and create table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
from_yaml(args).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,13 +60,12 @@ fn convert_yaml_value_to_nu_value(
|
||||
Ok(match v {
|
||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
UntaggedValue::int(n.as_i64().ok_or(err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal_from_float(n.as_f64().ok_or(err_not_compatible_number)?, span)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||
span,
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
let result: Result<Vec<Value>, ShellError> = a
|
||||
@ -142,12 +133,8 @@ pub fn from_yaml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value
|
||||
Ok(convert_yaml_value_to_nu_value(&v, tag)?)
|
||||
}
|
||||
|
||||
async fn from_yaml(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
async fn from_yaml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.name_tag();
|
||||
let input = args.input;
|
||||
|
||||
@ -173,15 +160,16 @@ async fn from_yaml(
|
||||
|
||||
#[cfg(test)]
|
||||
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() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(FromYAML {})
|
||||
Ok(test_examples(FromYAML {})?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -34,12 +34,8 @@ impl WholeStreamCommand for Get {
|
||||
"Open given cells as text."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
get(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -58,12 +54,8 @@ impl WholeStreamCommand for Get {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: column_paths }, mut input) = args.process(®istry).await?;
|
||||
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (GetArgs { rest: column_paths }, mut input) = args.process().await?;
|
||||
if column_paths.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
@ -129,10 +121,10 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.as_string()) {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
format!("did you mean '{}'?", suggestions[0]),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
} else {
|
||||
@ -155,8 +147,8 @@ pub fn get_column_path_from_table_error(
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.filter_map(|r| did_you_mean(&r, column_path_tried.as_string()))
|
||||
.map(|s| s[0].to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
@ -232,14 +224,14 @@ pub fn get_column_from_row_error(
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried.as_string()) {
|
||||
Some(ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
suggestions[0],
|
||||
&obj_source.data_descriptors().join(", ")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
@ -267,11 +259,12 @@ pub fn get_column_from_row_error(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
Ok(test_examples(Get {})?)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::indexmap;
|
||||
use crate::utils::suggestions::suggestions;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::as_string;
|
||||
|
||||
pub struct GroupBy;
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GroupByArgs {
|
||||
pub struct Arguments {
|
||||
grouper: Option<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for GroupBy {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
@ -31,25 +31,49 @@ impl WholeStreamCommand for GroupBy {
|
||||
"Create a new table grouped."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
group_by(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
group_by(args).await
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_data::value::date_naive_from_str as date;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "group items by column named \"type\"",
|
||||
example: r#"ls | group-by type"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "blocks can be used for generating a grouping key (same as above)",
|
||||
example: r#"ls | group-by { get type }"#,
|
||||
result: None,
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"File".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"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! {
|
||||
"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! {
|
||||
"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! {
|
||||
"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(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "you can also group by raw values by leaving out the argument",
|
||||
@ -74,10 +98,26 @@ impl WholeStreamCommand for GroupBy {
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "write pipelines for a more involved grouping key",
|
||||
example:
|
||||
"echo [1 3 1 3 2 1 1] | group-by { echo `({{$it}} - 1) % 3` | calc | str from }",
|
||||
result: None,
|
||||
description:
|
||||
"use the block form to generate a grouping key when each row gets processed",
|
||||
example: "echo [1 3 1 3 2 1 1] | group-by { = ($it - 1) mod 3 }",
|
||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||
"0".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(1).into(),
|
||||
|
||||
]).into(),
|
||||
"2".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
]).into(),
|
||||
"1".to_string() => UntaggedValue::Table(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
]).into(),
|
||||
})
|
||||
.into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -88,16 +128,10 @@ enum Grouper {
|
||||
ByBlock,
|
||||
}
|
||||
|
||||
pub async fn group_by(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
pub async fn group_by(args: CommandArgs) -> 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 = Arc::new(args.call_info.scope.clone());
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||
let (GroupByArgs { grouper }, input) = args.process(®istry).await?;
|
||||
let context = Arc::new(EvaluationContext::from_raw(&args));
|
||||
let (Arguments { grouper }, input) = args.process().await?;
|
||||
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let mut keys: Vec<Result<String, ShellError>> = vec![];
|
||||
@ -113,13 +147,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, context, value.clone()).await {
|
||||
Ok(mut s) => {
|
||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
s.drain_vec().await;
|
||||
@ -167,6 +197,14 @@ pub async fn group_by(
|
||||
));
|
||||
}
|
||||
|
||||
let first = values[0].clone();
|
||||
|
||||
let name = if first.tag.anchor().is_some() {
|
||||
first.tag
|
||||
} else {
|
||||
name
|
||||
};
|
||||
|
||||
let values = UntaggedValue::table(&values).into_value(&name);
|
||||
|
||||
let group_value = match group_strategy {
|
||||
@ -179,39 +217,14 @@ pub async fn group_by(
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), name)
|
||||
}
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, &name),
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(group_value?)))
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(
|
||||
column_name: &Option<Tagged<String>>,
|
||||
values: &Value,
|
||||
@ -250,9 +263,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> {
|
||||
@ -311,12 +325,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use super::GroupBy;
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupBy {})
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::suggestions::suggestions;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
@ -37,12 +38,8 @@ impl WholeStreamCommand for GroupByDate {
|
||||
"creates a table grouped by date."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
group_by_date(args, registry).await
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
group_by_date(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -62,11 +59,7 @@ enum GroupByColumn {
|
||||
Name(Option<Tagged<String>>),
|
||||
}
|
||||
|
||||
pub async fn group_by_date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
pub async fn group_by_date(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (
|
||||
GroupByDateArgs {
|
||||
@ -74,7 +67,7 @@ pub async fn group_by_date(
|
||||
format,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
) = args.process().await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
if values.is_empty() {
|
||||
@ -137,39 +130,15 @@ pub async fn group_by_date(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
let possibilities = for_value.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tried.tag(),
|
||||
)
|
||||
} else {
|
||||
ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tried.tag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GroupByDate;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(GroupByDate {})
|
||||
Ok(test_examples(GroupByDate {})?)
|
||||
}
|
||||
}
|
||||
|
305
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
305
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
@ -0,0 +1,305 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
|
||||
use base64::{decode_config, encode_config};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
pub rest: Vec<ColumnPath>,
|
||||
pub character_set: Option<Tagged<String>>,
|
||||
pub encode: Tagged<bool>,
|
||||
pub decode: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Base64Config {
|
||||
pub character_set: String,
|
||||
pub action_type: ActionType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum ActionType {
|
||||
Encode,
|
||||
Decode,
|
||||
}
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"hash base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("hash base64")
|
||||
.named(
|
||||
"character_set",
|
||||
SyntaxShape::String,
|
||||
"specify the character rules for encoding the input.\n\
|
||||
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
||||
'binhex', 'bcrypt', 'crypt'",
|
||||
Some('c'),
|
||||
)
|
||||
.switch(
|
||||
"encode",
|
||||
"encode the input as base64. This is the default behavior if not specified.",
|
||||
Some('e')
|
||||
)
|
||||
.switch(
|
||||
"decode",
|
||||
"decode the input from base64",
|
||||
Some('d'))
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally base64 encode / decode data by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"base64 encode or decode a value"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
operate(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Base64 encode a string with default settings",
|
||||
example: "echo 'username:password' | hash base64",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Base64 encode a string with the binhex character set",
|
||||
example: "echo 'username:password' | hash base64 --character_set binhex --encode",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("F@0NEPjJD97kE'&bEhFZEP3").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Base64 decode a value",
|
||||
example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("username:password").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = &args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
Arguments {
|
||||
encode,
|
||||
decode,
|
||||
character_set,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process().await?;
|
||||
|
||||
if encode.item && decode.item {
|
||||
return Ok(OutputStream::one(Err(ShellError::labeled_error(
|
||||
"only one of --decode and --encode flags can be used",
|
||||
"conflicting flags",
|
||||
name_tag,
|
||||
))));
|
||||
}
|
||||
|
||||
// Default the action to be encoding if no flags are specified.
|
||||
let action_type = if *decode.item() {
|
||||
ActionType::Decode
|
||||
} else {
|
||||
ActionType::Encode
|
||||
};
|
||||
|
||||
// Default the character set to standard if the argument is not specified.
|
||||
let character_set = match character_set {
|
||||
Some(inner_tag) => inner_tag.item().to_string(),
|
||||
None => "standard".to_string(),
|
||||
};
|
||||
|
||||
let encoding_config = Base64Config {
|
||||
character_set,
|
||||
action_type,
|
||||
};
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &encoding_config, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
let config = encoding_config.clone();
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &config, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
base64_config: &Base64Config,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let base64_config_enum: base64::Config = if &base64_config.character_set == "standard" {
|
||||
base64::STANDARD
|
||||
} else if &base64_config.character_set == "standard-no-padding" {
|
||||
base64::STANDARD_NO_PAD
|
||||
} else if &base64_config.character_set == "url-safe" {
|
||||
base64::URL_SAFE
|
||||
} else if &base64_config.character_set == "url-safe-no-padding" {
|
||||
base64::URL_SAFE_NO_PAD
|
||||
} else if &base64_config.character_set == "binhex" {
|
||||
base64::BINHEX
|
||||
} else if &base64_config.character_set == "bcrypt" {
|
||||
base64::BCRYPT
|
||||
} else if &base64_config.character_set == "crypt" {
|
||||
base64::CRYPT
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"value is not an accepted character set",
|
||||
format!(
|
||||
"{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.",
|
||||
&base64_config.character_set
|
||||
),
|
||||
tag.into().span,
|
||||
));
|
||||
};
|
||||
|
||||
match base64_config.action_type {
|
||||
ActionType::Encode => Ok(UntaggedValue::string(encode_config(
|
||||
&s,
|
||||
base64_config_enum,
|
||||
))
|
||||
.into_value(tag)),
|
||||
ActionType::Decode => {
|
||||
let decode_result = decode_config(&s, base64_config_enum);
|
||||
|
||||
match decode_result {
|
||||
Ok(decoded_value) => Ok(UntaggedValue::string(
|
||||
std::string::String::from_utf8_lossy(&decoded_value),
|
||||
)
|
||||
.into_value(tag)),
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"value could not be base64 decoded",
|
||||
format!(
|
||||
"invalid base64 input for character set {}",
|
||||
&base64_config.character_set
|
||||
),
|
||||
tag.into().span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, ActionType, Base64Config};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard() {
|
||||
let word = string("username:password");
|
||||
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "standard".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard_no_padding() {
|
||||
let word = string("username:password");
|
||||
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "standard-no-padding".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_url_safe() {
|
||||
let word = string("this is for url");
|
||||
let expected = UntaggedValue::string("dGhpcyBpcyBmb3IgdXJs").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "url-safe".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_binhex() {
|
||||
let word = string("A5\"KC9jRB@IIF'8bF!");
|
||||
let expected = UntaggedValue::string("a binhex test").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "binhex".to_string(),
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
44
crates/nu-cli/src/commands/hash_/command.rs
Normal file
44
crates/nu-cli/src/commands/hash_/command.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"hash"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("hash").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Apply hash function."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod base64_;
|
||||
mod command;
|
||||
|
||||
pub use base64_::SubCommand as HashBase64;
|
||||
pub use command::Command as Hash;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user