forked from extern/nushell
Compare commits
384 Commits
Author | SHA1 | Date | |
---|---|---|---|
388973e9ab | |||
2129ec7558 | |||
82f122525c | |||
7c4c00f1e6 | |||
fe6c7dc10a | |||
9bc24e3b12 | |||
833baca66e | |||
9fd92512a2 | |||
b692ca7896 | |||
52dc04a35a | |||
42b1287759 | |||
5a471aa1d0 | |||
a4b8d4a098 | |||
a3be6affa4 | |||
71b99edd48 | |||
64553ddcb7 | |||
2a42482ae9 | |||
11f345a8ae | |||
fec50d8cfe | |||
05e42381df | |||
b435075e09 | |||
430da53f0b | |||
2e6d836dd1 | |||
899d324a9c | |||
576ed6a906 | |||
d744cf8437 | |||
088e662285 | |||
f9b0b81eb2 | |||
c5485c6501 | |||
d8ed01400f | |||
ebc4694e05 | |||
a9441d670e | |||
495d2ebd70 | |||
ad26adc3e3 | |||
63a62e19f9 | |||
4f2ae34df9 | |||
a636f161a4 | |||
dfb1e22559 | |||
dff85a7f70 | |||
3be198d2f5 | |||
d19314fe3a | |||
d06f457b2a | |||
7d07881d96 | |||
3e6e3a207c | |||
481c6d4511 | |||
231a445809 | |||
93e8f6c05e | |||
9de2144fc4 | |||
363dc51ba0 | |||
99117ff2ef | |||
5356cb9fbd | |||
0e13d9fbaa | |||
2dcb16870b | |||
ac9909112f | |||
eb3c2c9e76 | |||
3d29e3efbf | |||
f410fb6689 | |||
eb62fd466e | |||
b50cdd6de8 | |||
f38e2b5c6d | |||
455915ec9e | |||
2333158256 | |||
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 | |||
ebba89ea31 | |||
8388afc9d9 | |||
b133724b38 | |||
09429d08aa | |||
9b577b8679 | |||
7a595827f1 | |||
332e12ded0 | |||
a508e15efe | |||
a5b6bb6209 | |||
1882a32b83 | |||
798766b4b5 | |||
193c4cc6d5 | |||
422b6ca871 | |||
2b13ac3856 | |||
4c10351579 | |||
dd27aaef1b | |||
6eb4a0e87b | |||
15f3a545f0 | |||
365f76ad19 | |||
df2845a9b4 | |||
8453261211 | |||
1dc8f3300e | |||
10d4edc7af | |||
50cbf91bc5 | |||
d05f9b3b1e | |||
f5fad393d0 | |||
d19a5f4c2f | |||
04451af776 | |||
232aca76a4 | |||
0178b53289 | |||
e05e6b42fe | |||
dd79afb503 | |||
599bb9797d | |||
c355585112 | |||
45f32c9541 | |||
7528094e12 | |||
dcfa135ab9 | |||
e9bb4f25eb | |||
0f7a9bbd31 | |||
73e65df5f6 | |||
a63a5adafa | |||
2eb4f8d28a | |||
d9ae66791a | |||
2c5939dc7d | |||
3150e70fc7 | |||
c9ffd6afc0 | |||
986b427038 | |||
c973850571 | |||
5a725f9651 | |||
79cc725aff | |||
e2cbc4e853 | |||
b5a27f0ccb | |||
bdb12f4bff | |||
c9c29f9e4c | |||
32951f1161 | |||
56f85b3108 | |||
16f85f32a2 | |||
2ae2f2ea9d | |||
4696c9069b | |||
1ffbb66e64 | |||
8dc7b8a7cd | |||
666e6a7b57 | |||
47c5346934 | |||
882cf74137 | |||
57a26bbd42 | |||
f9acb7a7a5 | |||
569345e1d4 | |||
adbbcafd30 | |||
860c2a606d | |||
6b5d96337e | |||
f54cf8a096 | |||
b5d591bb09 | |||
60ce497edc | |||
dd4351e2b7 | |||
3f443f40d0 | |||
8192360b20 | |||
889b67ca92 | |||
8d1cecf643 | |||
188d33b306 | |||
965e07d8cc | |||
d6b6b1df38 | |||
c897ac6e1e | |||
df691c6c91 | |||
abc05ece21 | |||
6f69ae8707 | |||
84a6010f71 | |||
634bb688c1 | |||
c14b209276 | |||
6535ae3d6e | |||
0390ec97f4 | |||
e3c4d82798 | |||
ee71a35786 | |||
11ea5e61fc | |||
02763b47f7 | |||
4828a7cd29 | |||
728852c750 | |||
26cec83b63 | |||
4724b3c570 | |||
303a9defd3 | |||
a64270829e | |||
8f5df89a78 | |||
39f402c8cc | |||
7702d683c7 | |||
766533aafb | |||
781e423a97 | |||
6e3a827e32 | |||
6685f74e03 | |||
034c33c2b5 | |||
08d1be79fc | |||
c563b7862e | |||
a64cfb6285 | |||
f078aacc25 | |||
d859bff877 | |||
de5cd4ec23 | |||
48850becd8 | |||
2ea42f296c | |||
eb2ba470c7 | |||
a951edd0d5 | |||
d65a38dd41 | |||
8f568f4fc5 | |||
ee26590011 | |||
11352f87f0 | |||
9f85b10fcb | |||
0dd1403a69 | |||
cb4527fc0d | |||
ad395944ef | |||
6126209f57 | |||
43e061f8c6 | |||
738541f727 | |||
1d5518a214 | |||
57101d5022 | |||
f6ff6ab6e4 | |||
a224cd38ab | |||
e292bb46bb | |||
05aca1c157 | |||
8d269f62dd | |||
b1a946f0dc | |||
c59f860b48 | |||
c6588c661a | |||
8fe269a3d8 | |||
8f00713ad2 | |||
d1d98a897a | |||
3dc95ef765 | |||
84da815b22 | |||
371a951668 | |||
baf84f05d9 | |||
da4d24d082 | |||
22519c9083 | |||
1601aa2f49 | |||
88555860f3 | |||
015d2ee050 | |||
dd7ee1808a | |||
0db4180cea | |||
8ff15c46c1 | |||
48cfc9b598 |
@ -9,6 +9,12 @@ strategy:
|
||||
linux-minimal:
|
||||
image: ubuntu-18.04
|
||||
style: 'minimal'
|
||||
linux-extra:
|
||||
image: ubuntu-18.04
|
||||
style: 'extra'
|
||||
linux-wasm:
|
||||
image: ubuntu-18.04
|
||||
style: 'wasm'
|
||||
macos-stable:
|
||||
image: macos-10.14
|
||||
style: 'unflagged'
|
||||
@ -37,6 +43,7 @@ steps:
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
sudo npm install -g wasm-pack
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
@ -49,21 +56,27 @@ steps:
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features stable
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- bash: cd samples/wasm && wasm-pack build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Check clippy lints
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
||||
|
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"]
|
@ -73,7 +73,7 @@ workflows:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v.*/
|
||||
only: /^\d+\.\d+\.\d+$/
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:latest
|
||||
- run: docker pull quay.io/nushell/nu-base:latest
|
||||
|
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -12,9 +12,9 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
@ -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.**
|
||||
|
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_stable_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -112,7 +112,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=stable
|
||||
args: --release --all --features=extra
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
@ -129,7 +129,7 @@ jobs:
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_stable_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
|
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
|
||||
|
@ -4,7 +4,7 @@ tasks:
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable,test-bins
|
||||
init: cargo test --all --features=stable
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
|
@ -25,38 +25,51 @@ cargo build
|
||||
|
||||
### Useful Commands
|
||||
|
||||
Build and run Nushell:
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
```
|
||||
|
||||
Run Clippy on Nushell:
|
||||
- Build and run with extra features:
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
Run all tests:
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
- Run all tests:
|
||||
|
||||
Run all tests for a specific command
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
- Run all tests for a specific command
|
||||
|
||||
Check to see if there are code formatting issues
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
- Check to see if there are code formatting issues
|
||||
|
||||
Format the code in the project
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
- Format the code in the project
|
||||
|
||||
```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
|
||||
```
|
||||
|
4512
Cargo.lock
generated
4512
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
212
Cargo.toml
212
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.18.1"
|
||||
version = "0.26.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,93 +18,111 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.18.1", path = "./crates/nu-cli"}
|
||||
nu-errors = {version = "0.18.1", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.18.1", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.18.1", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.18.1", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.18.1", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.18.1", path = "./crates/nu-value-ext"}
|
||||
nu-cli = {version = "0.26.0", path = "./crates/nu-cli"}
|
||||
nu-command = {version = "0.26.0", path = "./crates/nu-command"}
|
||||
nu-data = {version = "0.26.0", path = "./crates/nu-data"}
|
||||
nu-engine = {version = "0.26.0", path = "./crates/nu-engine"}
|
||||
nu-errors = {version = "0.26.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.26.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.26.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.26.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.26.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.26.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_plugin_binaryview = {version = "0.18.1", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.18.1", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.18.1", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.18.1", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.18.1", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.18.1", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.18.1", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_start = {version = "0.18.1", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.18.1", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.18.1", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.18.1", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.18.1", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.26.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.26.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.26.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.26.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.26.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.26.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.26.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.26.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.26.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_selector = {version = "0.26.0", path = "./crates/nu_plugin_selector", optional = true}
|
||||
nu_plugin_start = {version = "0.26.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.26.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.26.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.26.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.26.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.26.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.26.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
|
||||
crossterm = {version = "0.17.5", optional = true}
|
||||
semver = {version = "0.10.0", optional = true}
|
||||
syntect = {version = "4.2", default-features = false, features = ["default-fancy"], optional = true}
|
||||
url = {version = "2.1.1", optional = true}
|
||||
|
||||
clap = "2.33.1"
|
||||
ctrlc = "3.1.4"
|
||||
dunce = "1.0.1"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
quick-xml = "0.18.1"
|
||||
starship = "0.43.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = {version = "0.18.1", path = "./crates/nu-test-support"}
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = {version = "0.26.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-command/directories", "nu-command/dirs", "nu-data/directories", "nu-data/dirs", "nu-engine/dirs"]
|
||||
ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-cli/term", "nu-command/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which", "nu-command/ichwh", "nu-command/which"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
"textview",
|
||||
"inc",
|
||||
"git-support",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"zip-support",
|
||||
]
|
||||
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
|
||||
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", "syntect", "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", "nu-command/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", "nu-command/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"]
|
||||
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
starship-prompt = ["nu-cli/starship-prompt"]
|
||||
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"]
|
||||
[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
|
||||
@ -129,37 +147,83 @@ name = "nu_plugin_core_sys"
|
||||
path = "src/plugins/nu_plugin_core_sys.rs"
|
||||
required-features = ["sys"]
|
||||
|
||||
# Stable plugins
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_fetch"
|
||||
path = "src/plugins/nu_plugin_stable_fetch.rs"
|
||||
name = "nu_plugin_core_fetch"
|
||||
path = "src/plugins/nu_plugin_core_fetch.rs"
|
||||
required-features = ["fetch"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_binaryview"
|
||||
path = "src/plugins/nu_plugin_stable_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_match"
|
||||
path = "src/plugins/nu_plugin_stable_match.rs"
|
||||
name = "nu_plugin_core_match"
|
||||
path = "src/plugins/nu_plugin_core_match.rs"
|
||||
required-features = ["match"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_post"
|
||||
path = "src/plugins/nu_plugin_stable_post.rs"
|
||||
name = "nu_plugin_core_post"
|
||||
path = "src/plugins/nu_plugin_core_post.rs"
|
||||
required-features = ["post"]
|
||||
|
||||
# Extra plugins
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_tree"
|
||||
path = "src/plugins/nu_plugin_stable_tree.rs"
|
||||
name = "nu_plugin_extra_binaryview"
|
||||
path = "src/plugins/nu_plugin_extra_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_stable_start"
|
||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
[[bin]]
|
||||
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"
|
||||
|
28
README.md
28
README.md
@ -7,11 +7,11 @@
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||
|
||||
## Nu Shell
|
||||
## Nushell
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
## Status
|
||||
|
||||
@ -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,10 +64,10 @@ 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=stable
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
### Docker
|
||||
@ -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:
|
||||
|
||||
@ -300,14 +300,14 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
|
||||
| Functions| X | | | | | No functions, yet, only aliases
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Functions | | X | | | | No functions, yet, only aliases
|
||||
| Variables| | X | | | | Nu doesn't yet support variables
|
||||
| Completions | | X | | | | Completions are currently barebones, at best
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||
|
||||
## 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,105 +1,110 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.18.1"
|
||||
version = "0.26.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = {version = "0.18.1", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.18.1", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.18.1", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.18.1", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.18.1", path = "../nu-source"}
|
||||
nu-table = {version = "0.18.1", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.18.1", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.18.1", path = "../nu-value-ext"}
|
||||
nu-command = { version = "0.26.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.26.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.26.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.26.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.26.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.26.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.26.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.26.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.26.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.26.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.26.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.26.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.26.0", path = "../nu-value-ext" }
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = {version = "2", package = "app_dirs2"}
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.36"
|
||||
base64 = "0.12.3"
|
||||
bigdecimal = {version = "0.1.2", features = ["serde"]}
|
||||
byte-unit = "3.1.3"
|
||||
bytes = "0.5.5"
|
||||
calamine = "0.16"
|
||||
chrono = {version = "0.4.11", features = ["serde"]}
|
||||
clap = "2.33.1"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1"
|
||||
ctrlc = {version = "3.1.4", optional = true}
|
||||
async-trait = "0.1.40"
|
||||
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.11.0"
|
||||
csv = "1.1.3"
|
||||
ctrlc = { version = "3.1.6", optional = true }
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "2.0.2", optional = true}
|
||||
dirs = {version = "2.0.2", optional = true}
|
||||
dtparse = "1.1.0"
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.24"
|
||||
filesize = "0.2.0"
|
||||
futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
futures-util = "0.3.5"
|
||||
futures_codec = "0.4"
|
||||
fs_extra = "1.2.0"
|
||||
futures = { version = "0.3.5", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.8"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = {version = "0.13.6", default_features = false, optional = true}
|
||||
glob = "0.3.0"
|
||||
hex = "0.4"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.*"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.4.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
meval = "0.2"
|
||||
natural = "0.5.0"
|
||||
num-bigint = {version = "0.2.6", features = ["serde"]}
|
||||
num-format = {version = "0.4", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.11"
|
||||
ical = "0.7.0"
|
||||
ichwh = { version = "0.3.4", optional = true }
|
||||
indexmap = { version = "1.6.0", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
num-bigint = { version = "0.3.0", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-traits = "0.2.12"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.1.1"
|
||||
pretty_env_logger = "0.4.0"
|
||||
ptree = {version = "0.2", optional = true}
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = { version = "0.3.0", optional = true }
|
||||
query_interface = "0.3.5"
|
||||
rand = "0.7"
|
||||
regex = "1"
|
||||
roxmltree = "0.13.0"
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = "6.2.0"
|
||||
serde = {version = "1.0.114", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
quick-xml = "0.20.0"
|
||||
rand = "0.7.3"
|
||||
rayon = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.8.0"
|
||||
rustyline = { version = "6.3.0", optional = true }
|
||||
serde = { version = "1.0.115", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.55"
|
||||
serde_urlencoded = "0.6.1"
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0.57"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.5.2", optional = true}
|
||||
term = { version = "0.6.1", optional = true }
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
titlecase = "1.0"
|
||||
toml = "0.5.6"
|
||||
typetag = "0.1.5"
|
||||
umask = "1.0.0"
|
||||
unicode-xid = "0.2.1"
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.2", optional = true}
|
||||
zip = "0.5.6"
|
||||
|
||||
clipboard = {version = "0.5", optional = true}
|
||||
encoding_rs = "0.8.23"
|
||||
quick-xml = "0.18.1"
|
||||
rayon = "1.3.1"
|
||||
starship = {version = "0.43.0", optional = true}
|
||||
trash = {version = "1.0.1", optional = true}
|
||||
url = {version = "2.1.1"}
|
||||
trash = { version = "1.2.0", optional = true }
|
||||
unicode-segmentation = "1.6.0"
|
||||
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 }
|
||||
shadow-rs = { version = "0.5", default-features = false, optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
@ -111,16 +116,20 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.23.1"
|
||||
version = "0.24.2"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["clipboard"]
|
||||
default = ["shadow-rs"]
|
||||
clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
trash-support = ["trash"]
|
||||
dirs = ["dirs-next"]
|
||||
directories = ["directories-next"]
|
||||
|
3
crates/nu-cli/build.rs
Normal file
3
crates/nu-cli/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,151 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
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("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,
|
||||
save,
|
||||
},
|
||||
_ctx,
|
||||
) = args.process(®istry).await?;
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::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", "")
|
||||
.replace("-s", "");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Alias;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Alias {})
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[1;31mHello \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?;
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(color.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown color",
|
||||
"unknown color",
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(ansi_term::Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(ansi_term::Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(ansi_term::Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(ansi_term::Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(ansi_term::Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(ansi_term::Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(ansi_term::Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(ansi_term::Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(ansi_term::Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(ansi_term::Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(ansi_term::Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(ansi_term::Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(ansi_term::Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(ansi_term::Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(ansi_term::Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(ansi_term::Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(ansi_term::Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(ansi_term::Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(ansi_term::Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(ansi_term::Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(ansi_term::Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => {
|
||||
Some(ansi_term::Color::Yellow.underline().prefix().to_string())
|
||||
}
|
||||
"yi" | "yellow_italic" => Some(ansi_term::Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(ansi_term::Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(ansi_term::Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(ansi_term::Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => {
|
||||
Some(ansi_term::Color::Purple.underline().prefix().to_string())
|
||||
}
|
||||
"pi" | "purple_italic" => Some(ansi_term::Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(ansi_term::Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(ansi_term::Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(ansi_term::Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(ansi_term::Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(ansi_term::Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(ansi_term::Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(ansi_term::Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(ansi_term::Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(ansi_term::Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(ansi_term::Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(ansi_term::Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(ansi_term::Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AppendArgs {
|
||||
row: Value,
|
||||
}
|
||||
|
||||
pub struct Append;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Append {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("append").required(
|
||||
"row value",
|
||||
SyntaxShape::Any,
|
||||
"the value of the row to append to the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append the given row to the table"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AppendArgs { row }, input) = args.process(registry).await?;
|
||||
|
||||
let eos = futures::stream::iter(vec![row]);
|
||||
|
||||
Ok(input.chain(eos).to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Add something to the end of a list or table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Append;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Append {})
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
pub struct Benchmark;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BenchmarkArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Benchmark {
|
||||
fn name(&self) -> &str {
|
||||
"benchmark"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("benchmark").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and benchmark",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block and return the time it took to do execute it. Eg) benchmark { echo $nu.env.NAME }"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (BenchmarkArgs { block }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
let start_time: chrono::DateTime<_> = Utc::now();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
let _ = result?.drain_vec().await;
|
||||
let run_duration: chrono::Duration = Utc::now().signed_duration_since(start_time);
|
||||
|
||||
context.clear_errors();
|
||||
|
||||
let output = Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::from(run_duration)),
|
||||
tag: Tag::from(block.span),
|
||||
}));
|
||||
|
||||
Ok(OutputStream::from(vec![output]))
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::context::Context;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::InputStream;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(crate) async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &mut Context,
|
||||
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 {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => return Err(e),
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input, it, vars, env).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
async fn run_pipeline(
|
||||
commands: &Commands,
|
||||
ctx: &mut Context,
|
||||
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::Expr(expr) => {
|
||||
run_expression_block(*expr, ctx, it, vars, env).await?
|
||||
}
|
||||
|
||||
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||
|
||||
ClassifiedCommand::Internal(left) => {
|
||||
run_internal_command(left, ctx, input, it, vars, env).await?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
@ -1,440 +0,0 @@
|
||||
use crate::commands::help::get_help;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
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 parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
#[derive(Deserialize, Serialize, 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?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn switch_present(&self, switch: &str) -> bool {
|
||||
self.args.switch_preset(switch)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct CommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
pub input: InputStream,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
#[derive(Getters, Clone)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct RawCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
}
|
||||
|
||||
impl RawCommandArgs {
|
||||
pub fn with_input(self, input: impl Into<InputStream>) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host,
|
||||
ctrl_c: self.ctrl_c,
|
||||
current_errors: self.current_errors,
|
||||
shell_manager: self.shell_manager,
|
||||
call_info: self.call_info,
|
||||
input: input.into(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CommandArgs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.call_info.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub async fn evaluate_once(
|
||||
self,
|
||||
registry: &CommandRegistry,
|
||||
) -> 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 = self.call_info.evaluate(registry).await?;
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
))
|
||||
}
|
||||
|
||||
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?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
|
||||
Ok((T::deserialize(&mut deserializer)?, args.input))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
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 name: Tag,
|
||||
pub raw_input: String,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EvaluatedWholeStreamCommandArgs {
|
||||
pub args: EvaluatedCommandArgs,
|
||||
pub input: InputStream,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedWholeStreamCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedWholeStreamCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
input: impl Into<InputStream>,
|
||||
) -> EvaluatedWholeStreamCommandArgs {
|
||||
EvaluatedWholeStreamCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
},
|
||||
input: input.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_tag(&self) -> Tag {
|
||||
self.args.call_info.name_tag.clone()
|
||||
}
|
||||
|
||||
pub fn parts(self) -> (InputStream, EvaluatedArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args.call_info.args)
|
||||
}
|
||||
|
||||
pub fn split(self) -> (InputStream, EvaluatedCommandArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: CallInfo,
|
||||
}
|
||||
|
||||
impl EvaluatedCommandArgs {
|
||||
pub fn nth(&self, pos: usize) -> Option<&Value> {
|
||||
self.call_info.args.nth(pos)
|
||||
}
|
||||
|
||||
/// Get the nth positional argument, error if not possible
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||
self.call_info
|
||||
.args
|
||||
.nth(pos)
|
||||
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||
self.call_info.args.get(name)
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.call_info.args.has(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Example {
|
||||
pub example: &'static str,
|
||||
pub description: &'static str,
|
||||
pub result: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WholeStreamCommand: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name()).desc(self.usage()).filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||
|
||||
impl PrettyDebugWithSource for Command {
|
||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||
b::typed(
|
||||
"whole stream command",
|
||||
b::description(self.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ self.signature().pretty_debug(source),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Command {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Command({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn name(&self) -> &str {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> Signature {
|
||||
self.0.signature()
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> &str {
|
||||
self.0.usage()
|
||||
}
|
||||
|
||||
pub fn examples(&self) -> Vec<Example> {
|
||||
self.0.examples()
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> 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()),
|
||||
))))
|
||||
} else {
|
||||
self.0.run(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_binary(&self) -> bool {
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
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,67 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
key: Tagged<String>,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set")
|
||||
.required("key", SyntaxShape::String, "variable name to set")
|
||||
.required("value", SyntaxShape::Any, "value to use")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
set(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set completion_mode to circular",
|
||||
example: "config set [completion_mode circular]",
|
||||
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?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
config::write(&result, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(&value.tag),
|
||||
)))
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
"count"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("count")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show the total number of rows or items."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let rows: Vec<Value> = args.input.collect().await;
|
||||
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::int(rows.len()).into_value(name),
|
||||
))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Count {})
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date")
|
||||
.switch("utc", "use universal time (UTC)", Some('u'))
|
||||
.switch("local", "use the local time", Some('l'))
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"report datetime in supplied strftime format",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the current datetime."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
date(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current local time and date",
|
||||
example: "date",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current UTC time and date",
|
||||
example: "date --utc",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it based on format",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current time and date and report it without a table",
|
||||
example: "date --format '%Y-%m-%d %H:%M:%S.%f %z' --raw",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub async fn date(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let raw = args.has("raw");
|
||||
|
||||
let dt_fmt = if args.has("format") {
|
||||
if let Some(dt_fmt) = args.get("format") {
|
||||
dt_fmt.convert_to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let value = if args.has("utc") {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(utc, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(utc, tag, dt_fmt)
|
||||
}
|
||||
} else {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Date {})
|
||||
}
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::set::IndexSet;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
pub struct Get;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Get {
|
||||
fn name(&self) -> &str {
|
||||
"get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("get").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally return additional data by path",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Open given cells as text."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
get(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Extract the name of files as a list",
|
||||
example: "ls | get name",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Extract the cpu list from the sys information",
|
||||
example: "sys | get cpu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellError> {
|
||||
let fields = path.clone();
|
||||
|
||||
get_data_by_column_path(
|
||||
obj,
|
||||
path,
|
||||
Box::new(move |(obj_source, column_path_tried, error)| {
|
||||
let path_members_span = span_for_spanned_list(fields.members().iter().map(|p| p.span));
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
let suggestions: IndexSet<_> = rows
|
||||
.iter()
|
||||
.filter_map(|r| did_you_mean(&r, &column_path_tried))
|
||||
.map(|s| s[0].1.to_owned())
|
||||
.collect();
|
||||
let mut existing_columns: IndexSet<_> = IndexSet::default();
|
||||
let mut names: Vec<String> = vec![];
|
||||
|
||||
for row in rows {
|
||||
for field in row.data_descriptors() {
|
||||
if !existing_columns.contains(&field[..]) {
|
||||
existing_columns.insert(field.clone());
|
||||
names.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if names.is_empty() {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
"Appears to contain rows. Try indexing instead.",
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
} else {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions
|
||||
.iter()
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
names.join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
};
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
let total = rows.len();
|
||||
|
||||
let secondary_label = if total == 1 {
|
||||
"The table only has 1 row".to_owned()
|
||||
} else {
|
||||
format!("The table only has {} rows (0 to {})", total, total - 1)
|
||||
};
|
||||
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Row not found",
|
||||
format!("There isn't a row indexed at {}", idx),
|
||||
column_path_tried.span,
|
||||
secondary_label,
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
},
|
||||
UntaggedValue::Row(columns) => match column_path_tried {
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::String(column),
|
||||
..
|
||||
} => {
|
||||
let primary_label = format!("There isn't a column named '{}'", &column);
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"Unknown column",
|
||||
primary_label,
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Perhaps you meant '{}'? Columns available: {}",
|
||||
suggestions[0].1,
|
||||
&obj_source.data_descriptors().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
}
|
||||
PathMember {
|
||||
unspanned: UnspannedPathMember::Int(idx),
|
||||
..
|
||||
} => {
|
||||
return ShellError::labeled_error_with_secondary(
|
||||
"No rows available",
|
||||
format!("A row at '{}' can't be indexed.", &idx),
|
||||
column_path_tried.span,
|
||||
format!(
|
||||
"Appears to contain columns. Columns available: {}",
|
||||
columns.keys().join(",")
|
||||
),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(suggestions) = did_you_mean(&obj_source, column_path_tried) {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
column_path_tried.span.since(path_members_span),
|
||||
);
|
||||
}
|
||||
|
||||
error
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (GetArgs { rest: mut fields }, mut input) = args.process(®istry).await?;
|
||||
if fields.is_empty() {
|
||||
let vec = input.drain_vec().await;
|
||||
|
||||
let descs = nu_protocol::merge_descriptors(&vec);
|
||||
|
||||
Ok(futures::stream::iter(descs.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||
} else {
|
||||
let member = fields.remove(0);
|
||||
trace!("get {:?} {:?}", member, fields);
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let mut output = vec![];
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Value {
|
||||
value: UntaggedValue::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
output.push(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => {}
|
||||
other => output.push(ReturnSuccess::value(other.clone())),
|
||||
},
|
||||
Err(reason) => output.push(ReturnSuccess::value(
|
||||
UntaggedValue::Error(reason).into_untagged_value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output)
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Get;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Get {})
|
||||
}
|
||||
}
|
@ -1,169 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
pub struct Help;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HelpArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about commands."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
help(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (HelpArgs { rest }, ..) = args.process(®istry).await?;
|
||||
|
||||
if !rest.is_empty() {
|
||||
if rest[0].item == "commands" {
|
||||
let mut sorted_names = registry.names();
|
||||
sorted_names.sort();
|
||||
|
||||
Ok(
|
||||
futures::stream::iter(sorted_names.into_iter().filter_map(move |cmd| {
|
||||
// If it's a subcommand, don't list it during the commands list
|
||||
if cmd.contains(' ') {
|
||||
return None;
|
||||
}
|
||||
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
match registry.get_command(&cmd).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd),
|
||||
"could not load command",
|
||||
document_tag,
|
||||
)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
name.clone(),
|
||||
);
|
||||
|
||||
short_desc.insert_untagged("name", cmd);
|
||||
short_desc.insert_untagged(
|
||||
"description",
|
||||
match match get_data_by_key(&value, "usage".spanned_unknown()).ok_or_else(
|
||||
|| {
|
||||
ShellError::labeled_error(
|
||||
"Expected a usage key",
|
||||
"expected a 'usage' key",
|
||||
&value.tag,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
.as_string()
|
||||
{
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Some(Err(err)),
|
||||
},
|
||||
);
|
||||
|
||||
Some(ReturnSuccess::value(short_desc.into_value()))
|
||||
}))
|
||||
.to_output_stream(),
|
||||
)
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(generate_docs(
|
||||
®istry,
|
||||
))))
|
||||
} else if rest.len() == 2 {
|
||||
// Check for a subcommand
|
||||
let command_name = format!("{} {}", rest[0].item, rest[1].item);
|
||||
if let Some(command) = registry.get_command(&command_name) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
} else if let Some(command) = registry.get_command(&rest[0].item) {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(command.stream_command(), ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Can't find command (use 'help commands' for full list)",
|
||||
"can't find command",
|
||||
rest[0].tag.span,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help commands - list all available commands
|
||||
* help <command name> - display help about a particular command
|
||||
|
||||
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||
|
||||
[Examples]
|
||||
|
||||
List the files in the current directory, sorted by size:
|
||||
ls | sort-by size
|
||||
|
||||
Get information about the current system:
|
||||
sys | get host
|
||||
|
||||
Get the processes on your system actively using CPU:
|
||||
ps | where cpu > 0
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(msg).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> String {
|
||||
get_documentation(cmd, registry, &DocumentationConfig::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Help;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Help {})
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use crate::cli::History as HistoryFile;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub struct History;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for History {
|
||||
fn name(&self) -> &str {
|
||||
"history"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("history")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display command history."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
history(args, registry)
|
||||
}
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag;
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
Ok(line) => Some(ReturnSuccess::value(
|
||||
UntaggedValue::string(line).into_value(tag.clone()),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not open history",
|
||||
"history file could not be opened",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct If;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IfArgs {
|
||||
condition: Block,
|
||||
then_case: Block,
|
||||
else_case: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for If {
|
||||
fn name(&self) -> &str {
|
||||
"if"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("if")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must match",
|
||||
)
|
||||
.required(
|
||||
"then_case",
|
||||
SyntaxShape::Block,
|
||||
"block to run if condition is true",
|
||||
)
|
||||
.required(
|
||||
"else_case",
|
||||
SyntaxShape::Block,
|
||||
"block to run if condition is false",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run blocks if a condition is true or false."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
if_command(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run a block if a condition is true",
|
||||
example: "echo 10 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
|
||||
result: Some(vec![UntaggedValue::string("greater than 5").into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Run a block if a condition is false",
|
||||
example: "echo 1 | if $it > 5 { echo 'greater than 5' } { echo 'less than or equal to 5' }",
|
||||
result: Some(vec![UntaggedValue::string("less than or equal to 5").into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
async fn if_command(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = Arc::new(registry.clone());
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
|
||||
let (
|
||||
IfArgs {
|
||||
condition,
|
||||
then_case,
|
||||
else_case,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
let condition = {
|
||||
if condition.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match condition.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let condition = condition.clone();
|
||||
let then_case = then_case.clone();
|
||||
let else_case = else_case.clone();
|
||||
let registry = registry.clone();
|
||||
let scope = scope.clone();
|
||||
let mut context = context.clone();
|
||||
|
||||
async move {
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition =
|
||||
evaluate_baseline_expr(&condition, &*registry, &input, &scope.vars, &scope.env)
|
||||
.await;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => {
|
||||
if b {
|
||||
match run_block(
|
||||
&then_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => stream.to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
|
||||
.to_output_stream(),
|
||||
}
|
||||
} else {
|
||||
match run_block(
|
||||
&else_case,
|
||||
Arc::make_mut(&mut context),
|
||||
InputStream::empty(),
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => stream.to_output_stream(),
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter())
|
||||
.to_output_stream(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream()
|
||||
}
|
||||
},
|
||||
Err(e) => futures::stream::iter(vec![Err(e)].into_iter()).to_output_stream(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::If;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(If {})
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Insert;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct InsertArgs {
|
||||
column: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Insert {
|
||||
fn name(&self) -> &str {
|
||||
"insert"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("insert")
|
||||
.required(
|
||||
"column",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the column name to insert",
|
||||
)
|
||||
.required("value", SyntaxShape::Any, "the value to give the cell(s)")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Insert a new column with a given value."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
insert(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<Context>,
|
||||
input: Value,
|
||||
mut value: Arc<Value>,
|
||||
column: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value = Arc::make_mut(&mut value);
|
||||
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
..
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
match input {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if let Some(result) = stream.next().await {
|
||||
match obj.insert_data_at_column_path(&column, result) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
} else {
|
||||
OutputStream::empty()
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
_ => match input {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.insert_data_at_column_path(&column, value.clone()) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let (InsertArgs { column, value }, input) = raw_args.process(®istry).await?;
|
||||
let value = Arc::new(value);
|
||||
let column = Arc::new(column);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
let value = value.clone();
|
||||
let column = column.clone();
|
||||
|
||||
async {
|
||||
match process_row(scope, context, input, value, column).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Insert;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Insert {})
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
enum IsEmptyFor {
|
||||
Value,
|
||||
RowWithFieldsAndFallback(Vec<Tagged<ColumnPath>>, Value),
|
||||
RowWithField(Tagged<ColumnPath>),
|
||||
RowWithFieldAndFallback(Box<Tagged<ColumnPath>>, Value),
|
||||
}
|
||||
|
||||
pub struct IsEmpty;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IsEmptyArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IsEmpty {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("empty?").rest(
|
||||
SyntaxShape::Any,
|
||||
"the names of the columns to check emptiness followed by the replacement value.",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Checks emptiness. The last value is the replacement value for any empty column(s) given to check against the table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (IsEmptyArgs { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| {
|
||||
let value_tag = value.tag();
|
||||
|
||||
let action = if rest.len() <= 2 {
|
||||
let field = rest.get(0);
|
||||
let replacement_if_true = rest.get(1);
|
||||
|
||||
match (field, replacement_if_true) {
|
||||
(Some(field), Some(replacement_if_true)) => {
|
||||
IsEmptyFor::RowWithFieldAndFallback(
|
||||
Box::new(field.as_column_path()?),
|
||||
replacement_if_true.clone(),
|
||||
)
|
||||
}
|
||||
(Some(field), None) => IsEmptyFor::RowWithField(field.as_column_path()?),
|
||||
(_, _) => IsEmptyFor::Value,
|
||||
}
|
||||
} else {
|
||||
// let no_args = vec![];
|
||||
let mut arguments = rest.iter().rev();
|
||||
let replacement_if_true = match arguments.next() {
|
||||
Some(arg) => arg.clone(),
|
||||
None => UntaggedValue::boolean(value.is_empty()).into_value(&value_tag),
|
||||
};
|
||||
|
||||
IsEmptyFor::RowWithFieldsAndFallback(
|
||||
arguments
|
||||
.map(|a| a.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
replacement_if_true,
|
||||
)
|
||||
};
|
||||
|
||||
match action {
|
||||
IsEmptyFor::Value => Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::boolean(value.is_empty()).into_value(value_tag),
|
||||
)),
|
||||
IsEmptyFor::RowWithFieldsAndFallback(fields, default) => {
|
||||
let mut out = value;
|
||||
|
||||
for field in fields.iter() {
|
||||
let val = crate::commands::get::get_column_path(&field, &out)?;
|
||||
|
||||
let emptiness_value = match out {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
obj.replace_data_at_column_path(&field, default.clone())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
};
|
||||
|
||||
out = emptiness_value?;
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(out))
|
||||
}
|
||||
IsEmptyFor::RowWithField(field) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(
|
||||
&field,
|
||||
UntaggedValue::boolean(true).into_value(&value_tag),
|
||||
) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
IsEmptyFor::RowWithFieldAndFallback(field, default) => {
|
||||
let val = crate::commands::get::get_column_path(&field, &value)?;
|
||||
|
||||
match &value {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if val.is_empty() {
|
||||
match obj.replace_data_at_column_path(&field, default) {
|
||||
Some(v) => Ok(ReturnSuccess::Value(v)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"empty? could not find place to check emptiness",
|
||||
"column name",
|
||||
&field.tag,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(ReturnSuccess::Value(value))
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
&value_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IsEmpty;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IsEmpty {})
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
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 SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubCommandArgs {
|
||||
expression: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math eval"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Evaluate a math expression into a number"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math eval").desc(self.usage()).optional(
|
||||
"math expression",
|
||||
SyntaxShape::String,
|
||||
"the math expression to evaluate",
|
||||
)
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
eval(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Evalulate math in the pipeline",
|
||||
example: "echo '10 / 4' | math eval",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn eval(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.span;
|
||||
let (SubCommandArgs { expression }, input) = args.process(registry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |x| {
|
||||
if let Some(Tagged {
|
||||
tag,
|
||||
item: expression,
|
||||
}) = &expression
|
||||
{
|
||||
UntaggedValue::string(expression).into_value(tag)
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
.map(move |input| {
|
||||
if let Ok(string) = input.as_string() {
|
||||
match parse(&string, &input.tag) {
|
||||
Ok(value) => ReturnSuccess::value(value),
|
||||
Err(err) => Err(ShellError::labeled_error(
|
||||
"Math evaluation error",
|
||||
err,
|
||||
&input.tag.span,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected a string from pipeline",
|
||||
"requires string input",
|
||||
name,
|
||||
))
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
|
||||
match meval::eval_str(math_expression) {
|
||||
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
||||
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
|
||||
Err(error) => Err(error.to_string().to_lowercase()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"move"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("move")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"moves across desired subcommand."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathBasename;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathBasename {
|
||||
fn name(&self) -> &str {
|
||||
"path basename"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path basename")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the filename of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get basename of a path",
|
||||
example: "echo '/home/joe/test.txt' | path basename",
|
||||
result: Some(vec![Value::from("test.txt")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.file_name() {
|
||||
Some(filename) => filename.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathBasename;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(PathBasename {})
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExists;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExists {
|
||||
fn name(&self) -> &str {
|
||||
"path exists"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"checks whether the path exists"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Check if file exists",
|
||||
example: "echo '/home/joe/todo.txt' | path exists",
|
||||
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::boolean(path.exists())
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExpand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExpand {
|
||||
fn name(&self) -> &str {
|
||||
"path expand"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"expands the path to its absolute form"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Expand relative directories",
|
||||
example: "echo '/home/joe/foo/../bar' | path expand",
|
||||
result: Some(vec![Value::from("/home/joe/bar")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
let ps = path.to_string_lossy();
|
||||
let expanded = shellexpand::tilde(&ps);
|
||||
let path: &Path = expanded.as_ref().as_ref();
|
||||
UntaggedValue::string(match path.canonicalize() {
|
||||
Ok(p) => p.to_string_lossy().to_string(),
|
||||
Err(_) => ps.to_string(),
|
||||
})
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExtension;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExtension {
|
||||
fn name(&self) -> &str {
|
||||
"path extension"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path extension")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the extension of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get extension of a path",
|
||||
example: "echo 'test.txt' | path extension",
|
||||
result: Some(vec![Value::from("txt")]),
|
||||
},
|
||||
Example {
|
||||
description: "You get an empty string if there is no extension",
|
||||
example: "echo 'test' | path extension",
|
||||
result: Some(vec![Value::from("")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.extension() {
|
||||
Some(ext) => ext.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathExtension;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(PathExtension {})
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::files::get_file_type;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathType;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathType {
|
||||
fn name(&self) -> &str {
|
||||
"path type"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gives the type of the object the path refers to (eg file, dir, symlink)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show type of a filepath",
|
||||
example: "echo '.' | path type",
|
||||
result: Some(vec![Value::from("Dir")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
let meta = std::fs::symlink_metadata(path);
|
||||
UntaggedValue::string(match &meta {
|
||||
Ok(md) => get_file_type(md),
|
||||
Err(_) => "",
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathType;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(PathType {})
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::deserializer::NumericRange;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RangeArgs {
|
||||
area: Tagged<NumericRange>,
|
||||
}
|
||||
|
||||
pub struct Range;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Range {
|
||||
fn name(&self) -> &str {
|
||||
"range"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("range").required(
|
||||
"rows ",
|
||||
SyntaxShape::Range,
|
||||
"range of rows to return: Eg) 4..7 (=> from 4 to 7)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return only the selected rows"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
range(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn range(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (RangeArgs { area }, input) = args.process(®istry).await?;
|
||||
let range = area.item;
|
||||
let (from, _) = range.from;
|
||||
let (to, _) = range.to;
|
||||
|
||||
let from = *from as usize;
|
||||
let to = *to as usize;
|
||||
|
||||
Ok(input
|
||||
.skip(from)
|
||||
.take(to - from + 1)
|
||||
.map(ReturnSuccess::value)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Range;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Range {})
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Signature, SyntaxShape};
|
||||
|
||||
#[derive(new, Clone)]
|
||||
pub struct AliasCommand {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AliasCommand {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
let mut alias = Signature::build(&self.name);
|
||||
|
||||
for arg in &self.args {
|
||||
alias = alias.optional(arg, SyntaxShape::Any, "");
|
||||
}
|
||||
|
||||
alias
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let call_info = args.call_info.clone();
|
||||
let registry = registry.clone();
|
||||
let mut block = self.block.clone();
|
||||
block.set_redirect(call_info.args.external_redirection);
|
||||
|
||||
let alias_command = self.clone();
|
||||
let mut context = Context::from_args(&args, ®istry);
|
||||
let input = args.input;
|
||||
|
||||
let mut scope = call_info.scope.clone();
|
||||
let evaluated = call_info.evaluate(®istry).await?;
|
||||
if let Some(positional) = &evaluated.args.positional {
|
||||
for (pos, arg) in positional.iter().enumerate() {
|
||||
scope
|
||||
.vars
|
||||
.insert(alias_command.args[pos].to_string(), arg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we need to patch up the spans to point at the top-level error
|
||||
Ok(run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await?
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubCommandArgs {
|
||||
separator: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str collect").desc(self.usage()).optional(
|
||||
"separator",
|
||||
SyntaxShape::String,
|
||||
"the separator to put between the different values",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"collects a list of strings into a string"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
collect(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Collect a list of string",
|
||||
example: "echo ['a' 'b' 'c'] | str collect",
|
||||
result: Some(vec![Value::from("abc")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn collect(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (SubCommandArgs { separator }, input) = args.process(registry).await?;
|
||||
let separator = separator.map(|tagged| tagged.item).unwrap_or_default();
|
||||
|
||||
let strings: Vec<Result<String, ShellError>> =
|
||||
input.map(|value| value.as_string()).collect().await;
|
||||
let strings: Vec<String> = strings.into_iter().collect::<Result<_, _>>()?;
|
||||
let output = strings.join(&separator);
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(tag),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str length"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str length")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"outputs the lengths of the strings in the pipeline"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |x| match x.as_string() {
|
||||
Ok(s) => ReturnSuccess::value(UntaggedValue::int(s.len()).into_untagged_value()),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "echo 'hello' | str length",
|
||||
result: Some(vec![UntaggedValue::int(5).into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "echo 'hi' 'there' | str length",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(2).into_untagged_value(),
|
||||
UntaggedValue::int(5).into_untagged_value(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str reverse"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str reverse")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"outputs the reversals of the strings in the pipeline"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |x| match x.as_string() {
|
||||
Ok(s) => ReturnSuccess::value(
|
||||
UntaggedValue::string(s.chars().rev().collect::<String>())
|
||||
.into_untagged_value(),
|
||||
),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Return the reversals of multiple strings",
|
||||
example: "echo 'Nushell' | str reverse",
|
||||
result: Some(vec![UntaggedValue::string("llehsuN").into_untagged_value()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
replace: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str set")
|
||||
.required("set", SyntaxShape::String, "the new string to set")
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally set text by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"sets text"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Set contents with preferred string",
|
||||
example: "echo 'good day' | str set 'good bye'",
|
||||
result: Some(vec![Value::from("good bye")]),
|
||||
},
|
||||
Example {
|
||||
description: "Set the contents on preferred column paths",
|
||||
example: "open Cargo.toml | str set '255' package.version",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Replace(String);
|
||||
|
||||
async fn operate(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (Arguments { replace, rest }, input) = args.process(®istry).await?;
|
||||
let options = Replace(replace.item);
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &options, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &options, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let replacement = &options.0;
|
||||
Ok(UntaggedValue::string(replacement.as_str()).into_value(tag))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, Replace, SubCommand};
|
||||
use nu_plugin::test_helpers::value::string;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets() {
|
||||
let word = string("andres");
|
||||
let expected = string("robalino");
|
||||
|
||||
let set_options = Replace(String::from("robalino"));
|
||||
|
||||
let actual = action(&word, &set_options, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
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;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str to-int"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str to-int").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into integer by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts text into integer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert to an integer",
|
||||
example: "echo '255' | str to-int",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn operate(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (Arguments { rest }, input) = args.process(®istry).await?;
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let other = s.trim();
|
||||
let out = match BigInt::from_str(other) {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as an integer",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
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, SubCommand};
|
||||
use nu_plugin::test_helpers::value::{int, string};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_to_integer() {
|
||||
let word = string("10");
|
||||
let expected = int(10);
|
||||
|
||||
let actual = action(&word, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||
let integer_str = string("36anra");
|
||||
|
||||
let actual = action(&integer_str, Tag::unknown());
|
||||
|
||||
assert!(actual.is_err());
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct ToMarkdown;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToMarkdown {
|
||||
fn name(&self) -> &str {
|
||||
"to md"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to md")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into simple Markdown"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_html(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_html(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let name_tag = args.name_tag();
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
let mut output_string = String::new();
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
output_string.push_str(&htmlescape::encode_minimal(&header));
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n|");
|
||||
for _ in &headers {
|
||||
output_string.push_str("-");
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
|
||||
for row in input {
|
||||
match row.value {
|
||||
UntaggedValue::Row(row) => {
|
||||
output_string.push_str("|");
|
||||
for header in &headers {
|
||||
let data = row.get_data(header);
|
||||
output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000));
|
||||
output_string.push_str("|");
|
||||
}
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
p => {
|
||||
output_string.push_str(
|
||||
&(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000))),
|
||||
);
|
||||
output_string.push_str("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ToMarkdown;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(ToMarkdown {})
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Touch;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TouchArgs {
|
||||
pub target: Tagged<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Touch {
|
||||
fn name(&self) -> &str {
|
||||
"touch"
|
||||
}
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("touch").required(
|
||||
"filename",
|
||||
SyntaxShape::Path,
|
||||
"the path of the file you want to create",
|
||||
)
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
"creates a file"
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
touch(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates \"fixture.json\"",
|
||||
example: "touch fixture.json",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn touch(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (TouchArgs { target }, _) = args.process(®istry).await?;
|
||||
|
||||
match OpenOptions::new().write(true).create(true).open(&target) {
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(err) => Err(ShellError::labeled_error(
|
||||
"File Error",
|
||||
err.to_string(),
|
||||
&target.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Touch;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Touch {})
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Trim;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Trim {
|
||||
fn name(&self) -> &str {
|
||||
"trim"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("trim")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Trim leading and following whitespace from text data."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
trim(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Trims surrounding whitespace and outputs \"Hello world\"",
|
||||
example: "echo \" Hello world\" | trim",
|
||||
result: Some(vec![Value::from("Hello world")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_primitive(p: &mut Primitive) {
|
||||
match p {
|
||||
Primitive::String(s) | Primitive::Line(s) => *p = Primitive::String(s.trim().to_string()),
|
||||
Primitive::Nothing
|
||||
| Primitive::Int(_)
|
||||
| Primitive::Decimal(_)
|
||||
| Primitive::Filesize(_)
|
||||
| Primitive::ColumnPath(_)
|
||||
| Primitive::Pattern(_)
|
||||
| Primitive::Boolean(_)
|
||||
| Primitive::Date(_)
|
||||
| Primitive::Duration(_)
|
||||
| Primitive::Range(_)
|
||||
| Primitive::Path(_)
|
||||
| Primitive::Binary(_)
|
||||
| Primitive::BeginningOfStream
|
||||
| Primitive::EndOfStream => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_row(d: &mut Dictionary) {
|
||||
for (_, mut value) in d.entries.iter_mut() {
|
||||
trim_value(&mut value);
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_value(v: &mut Value) {
|
||||
match &mut v.value {
|
||||
UntaggedValue::Primitive(p) => trim_primitive(p),
|
||||
UntaggedValue::Row(row) => trim_row(row),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
fn trim(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(|v| {
|
||||
let mut trimmed = v;
|
||||
trim_value(&mut trimmed);
|
||||
ReturnSuccess::value(trimmed)
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Trim;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Trim {})
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Update;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateArgs {
|
||||
field: ColumnPath,
|
||||
replacement: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Update {
|
||||
fn name(&self) -> &str {
|
||||
"update"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("update")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"replacement value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Update an existing column to have a new value."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
update(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
scope: Arc<Scope>,
|
||||
mut context: Arc<Context>,
|
||||
input: Value,
|
||||
mut replacement: Arc<Value>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let replacement = Arc::make_mut(&mut replacement);
|
||||
|
||||
Ok(match replacement {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
..
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
Arc::make_mut(&mut context),
|
||||
input_stream,
|
||||
&input,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
match input {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if let Some(result) = stream.next().await {
|
||||
match obj.replace_data_at_column_path(&field, result) {
|
||||
Some(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
None => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
OutputStream::empty()
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
_ => match input {
|
||||
obj
|
||||
@
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.replace_data_at_column_path(&field, replacement.clone()) {
|
||||
Some(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
None => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))),
|
||||
},
|
||||
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn update(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = Arc::new(raw_args.call_info.scope.clone());
|
||||
let context = Arc::new(Context::from_raw(&raw_args, ®istry));
|
||||
let (UpdateArgs { field, replacement }, input) = raw_args.process(®istry).await?;
|
||||
let replacement = Arc::new(replacement);
|
||||
let field = Arc::new(field);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let scope = scope.clone();
|
||||
let context = context.clone();
|
||||
let replacement = replacement.clone();
|
||||
let field = field.clone();
|
||||
|
||||
async {
|
||||
match process_row(scope, context, input, replacement, field).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Update;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Update {})
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, UntaggedValue};
|
||||
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Version {
|
||||
fn name(&self) -> &str {
|
||||
"version"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("version")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display Nu version"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
version(args, registry)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Display Nu version",
|
||||
example: "version",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.args.span;
|
||||
|
||||
let mut indexmap = IndexMap::with_capacity(4);
|
||||
|
||||
indexmap.insert(
|
||||
"version".to_string(),
|
||||
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
|
||||
);
|
||||
|
||||
indexmap.insert("features".to_string(), features_enabled(&tag).into_value());
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
fn features_enabled(tag: impl Into<Tag>) -> TaggedListBuilder {
|
||||
let mut names = TaggedListBuilder::new(tag);
|
||||
|
||||
names.push_untagged(UntaggedValue::string("default"));
|
||||
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
{
|
||||
names.push_untagged(UntaggedValue::string("clipboard"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
names.push_untagged(UntaggedValue::string("trash"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "starship-prompt")]
|
||||
{
|
||||
names.push_untagged(UntaggedValue::string("starship"));
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Version;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Version {})
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Which;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Which {
|
||||
fn name(&self) -> &str {
|
||||
"which"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("which")
|
||||
.required("application", SyntaxShape::String, "application")
|
||||
.switch("all", "list all executables", Some('a'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds a program file."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
which(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcuts for creating an entry to the output table
|
||||
fn entry(arg: impl Into<String>, path: Value, builtin: bool, tag: Tag) -> Value {
|
||||
let mut map = IndexMap::new();
|
||||
map.insert(
|
||||
"arg".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(arg.into())).into_value(tag.clone()),
|
||||
);
|
||||
map.insert("path".to_string(), path);
|
||||
map.insert(
|
||||
"builtin".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::Boolean(builtin)).into_value(tag.clone()),
|
||||
);
|
||||
|
||||
UntaggedValue::row(map).into_value(tag)
|
||||
}
|
||||
|
||||
macro_rules! entry_builtin {
|
||||
($arg:expr, $tag:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::String("nushell built-in command".to_string()))
|
||||
.into_value($tag.clone()),
|
||||
true,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! entry_path {
|
||||
($arg:expr, $path:expr, $tag:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::Path($path)).into_value($tag.clone()),
|
||||
false,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WhichArgs {
|
||||
application: Tagged<String>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
async fn which(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
let (WhichArgs { application, all }, _) = args.process(®istry).await?;
|
||||
let external = application.starts_with('^');
|
||||
let item = if external {
|
||||
application.item[1..].to_string()
|
||||
} else {
|
||||
application.item.clone()
|
||||
};
|
||||
if !external {
|
||||
let builtin = registry.has(&item);
|
||||
if builtin {
|
||||
output.push(ReturnSuccess::value(entry_builtin!(
|
||||
item,
|
||||
application.tag.clone()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ichwh")]
|
||||
{
|
||||
if let Ok(paths) = ichwh::which_all(&item).await {
|
||||
for path in paths {
|
||||
output.push(ReturnSuccess::value(entry_path!(
|
||||
item,
|
||||
path.into(),
|
||||
application.tag.clone()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if all {
|
||||
Ok(futures::stream::iter(output.into_iter()).to_output_stream())
|
||||
} else {
|
||||
Ok(futures::stream::iter(output.into_iter().take(1)).to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Which;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Which {})
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct WithEnv;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WithEnvArgs {
|
||||
variable: (Tagged<String>, Tagged<String>),
|
||||
block: Block,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for WithEnv {
|
||||
fn name(&self) -> &str {
|
||||
"with-env"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("with-env")
|
||||
.required(
|
||||
"variable",
|
||||
SyntaxShape::Any,
|
||||
"the environment variable to temporarily set",
|
||||
)
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run once the variable is set",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block with an environment set. Eg) with-env [NAME 'foo'] { echo $nu.env.NAME }"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
with_env(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set the MYENV environment variable",
|
||||
example: r#"with-env [MYENV "my env value"] { echo $nu.env.MYENV }"#,
|
||||
result: Some(vec![Value::from("my env value")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn with_env(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let mut scope = raw_args.call_info.scope.clone();
|
||||
let (WithEnvArgs { variable, block }, input) = raw_args.process(®istry).await?;
|
||||
|
||||
scope.env.insert(variable.0.item, variable.1.item);
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::WithEnv;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(WithEnv {})
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::context;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Suggestion {
|
||||
pub display: String,
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
pub struct Context<'a>(&'a context::Context, &'a rustyline::Context<'a>);
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(a: &'a context::Context, b: &'a rustyline::Context<'a>) -> Context<'a> {
|
||||
Context(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<context::Context> for Context<'a> {
|
||||
fn as_ref(&self) -> &context::Context {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<rustyline::Context<'a>> for Context<'a> {
|
||||
fn as_ref(&self) -> &rustyline::Context<'a> {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Completer {
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &Context<'_>,
|
||||
) -> Result<(usize, Vec<Suggestion>), ShellError>;
|
||||
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String>;
|
||||
}
|
141
crates/nu-cli/src/completion/command.rs
Normal file
141
crates/nu-cli/src/completion/command.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use std::iter::FromIterator;
|
||||
use std::path::Path;
|
||||
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
pub struct CommandCompleter;
|
||||
|
||||
impl Completer for CommandCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(context.scope.get_command_names());
|
||||
|
||||
// Command suggestions can come from three possible sets:
|
||||
// 1. internal command names,
|
||||
// 2. external command names relative to PATH env var, and
|
||||
// 3. any other executable (that matches what's been typed so far).
|
||||
|
||||
let path_executables = find_path_executables().unwrap_or_default();
|
||||
|
||||
// TODO quote these, if necessary
|
||||
commands.extend(path_executables.into_iter());
|
||||
|
||||
let mut suggestions: Vec<_> = commands
|
||||
.into_iter()
|
||||
.filter(|v| matcher.matches(partial, v))
|
||||
.map(|v| Suggestion {
|
||||
replacement: v.clone(),
|
||||
display: v,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !partial.is_empty() {
|
||||
let path_completer = crate::completion::path::PathCompleter;
|
||||
let path_results = path_completer.path_suggestions(partial, matcher);
|
||||
let iter = path_results.into_iter().filter_map(|path_suggestion| {
|
||||
let path = path_suggestion.path;
|
||||
if path.is_dir() || is_executable(&path) {
|
||||
Some(path_suggestion.suggestion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
suggestions.extend(iter);
|
||||
}
|
||||
|
||||
suggestions
|
||||
}
|
||||
}
|
||||
|
||||
// TODO create a struct for "is executable" and store this information in it so we don't recompute
|
||||
// on every dir entry
|
||||
|
||||
#[cfg(windows)]
|
||||
fn pathext() -> Option<Vec<String>> {
|
||||
std::env::var_os("PATHEXT").map(|v| {
|
||||
v.to_string_lossy()
|
||||
.split(';')
|
||||
// Filter out empty tokens and ';' at the end
|
||||
.filter(|f| f.len() > 1)
|
||||
// Cut off the leading '.' character
|
||||
.map(|ext| ext[1..].to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
// If the entry isn't a file, it cannot be executable
|
||||
if !(file_type.is_file() || file_type.is_symlink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(extension) = path.extension() {
|
||||
if let Some(exts) = pathext() {
|
||||
exts.iter()
|
||||
.any(|ext| extension.to_string_lossy().eq_ignore_ascii_case(ext))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn is_executable(_path: &Path) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
let filetype = metadata.file_type();
|
||||
let permissions = metadata.permissions();
|
||||
|
||||
// The file is executable if it is a directory or a symlink and the permissions are set for
|
||||
// owner, group, or other
|
||||
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO cache these, but watch for changes to PATH
|
||||
fn find_path_executables() -> Option<IndexSet<String>> {
|
||||
let path_var = std::env::var_os("PATH")?;
|
||||
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
|
||||
|
||||
let mut executables: IndexSet<String> = IndexSet::new();
|
||||
for path in paths {
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if is_executable(&item.path()) {
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(executables)
|
||||
}
|
434
crates/nu-cli/src/completion/engine.rs
Normal file
434
crates/nu-cli/src/completion/engine.rs
Normal file
@ -0,0 +1,434 @@
|
||||
use nu_protocol::hir::*;
|
||||
use nu_source::{Span, Spanned, SpannedItem};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum LocationType {
|
||||
Command,
|
||||
Flag(String), // command name
|
||||
Argument(Option<String>, Option<String>), // command name, argument name
|
||||
Variable,
|
||||
}
|
||||
|
||||
pub type CompletionLocation = Spanned<LocationType>;
|
||||
|
||||
// TODO The below is very similar to shapes / expression_to_flat_shape. Check back October 2020
|
||||
// to see if we're close enough to just make use of those.
|
||||
|
||||
struct Flatten<'s> {
|
||||
line: &'s str,
|
||||
command: Option<String>,
|
||||
flag: Option<String>,
|
||||
}
|
||||
|
||||
impl<'s> Flatten<'s> {
|
||||
/// Converts a SpannedExpression into a completion location for use in NuCompleter
|
||||
fn expression(&self, e: &SpannedExpression) -> Vec<CompletionLocation> {
|
||||
match &e.expr {
|
||||
Expression::Block(block) => self.completion_locations(block),
|
||||
Expression::Invocation(block) => self.completion_locations(block),
|
||||
Expression::List(exprs) => exprs.iter().flat_map(|v| self.expression(v)).collect(),
|
||||
Expression::Table(headers, cells) => headers
|
||||
.iter()
|
||||
.flat_map(|v| self.expression(v))
|
||||
.chain(
|
||||
cells
|
||||
.iter()
|
||||
.flat_map(|v| v.iter().flat_map(|v| self.expression(v))),
|
||||
)
|
||||
.collect(),
|
||||
Expression::Command => vec![LocationType::Command.spanned(e.span)],
|
||||
Expression::Path(path) => self.expression(&path.head),
|
||||
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
|
||||
|
||||
Expression::Boolean(_)
|
||||
| Expression::FilePath(_)
|
||||
| Expression::Literal(Literal::ColumnPath(_))
|
||||
| Expression::Literal(Literal::GlobPattern(_))
|
||||
| Expression::Literal(Literal::Number(_))
|
||||
| Expression::Literal(Literal::Size(_, _))
|
||||
| Expression::Literal(Literal::String(_)) => {
|
||||
vec![
|
||||
LocationType::Argument(self.command.clone(), self.flag.clone()).spanned(e.span),
|
||||
]
|
||||
}
|
||||
|
||||
Expression::Binary(binary) => {
|
||||
let mut result = Vec::new();
|
||||
result.append(&mut self.expression(&binary.left));
|
||||
result.append(&mut self.expression(&binary.right));
|
||||
result
|
||||
}
|
||||
Expression::Range(range) => {
|
||||
let mut result = Vec::new();
|
||||
if let Some(left) = &range.left {
|
||||
result.append(&mut self.expression(left));
|
||||
}
|
||||
if let Some(right) = &range.right {
|
||||
result.append(&mut self.expression(right));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
Expression::ExternalWord
|
||||
| Expression::ExternalCommand(_)
|
||||
| Expression::Synthetic(_)
|
||||
| Expression::Literal(Literal::Operator(_))
|
||||
| Expression::Literal(Literal::Bare(_))
|
||||
| Expression::Garbage => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_command(&self, internal: &InternalCommand) -> Vec<CompletionLocation> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
match internal.args.head.expr {
|
||||
Expression::Command => {
|
||||
result.push(LocationType::Command.spanned(internal.name_span));
|
||||
}
|
||||
Expression::Literal(Literal::String(_)) => {
|
||||
result.push(LocationType::Command.spanned(internal.name_span));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(positionals) = &internal.args.positional {
|
||||
let mut positionals = positionals.iter();
|
||||
|
||||
if internal.name == "run_external" {
|
||||
if let Some(external_command) = positionals.next() {
|
||||
result.push(LocationType::Command.spanned(external_command.span));
|
||||
}
|
||||
}
|
||||
|
||||
result.extend(positionals.flat_map(|positional| match positional.expr {
|
||||
Expression::Garbage => {
|
||||
let garbage = positional.span.slice(self.line);
|
||||
let location = if garbage.starts_with('-') {
|
||||
LocationType::Flag(internal.name.clone())
|
||||
} else {
|
||||
// TODO we may be able to map this to the name of a positional,
|
||||
// but we'll need a signature
|
||||
LocationType::Argument(Some(internal.name.clone()), None)
|
||||
};
|
||||
|
||||
vec![location.spanned(positional.span)]
|
||||
}
|
||||
|
||||
_ => self.expression(positional),
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, kind) in &named.named {
|
||||
match kind {
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
|
||||
}
|
||||
|
||||
NamedValue::Value(span, expr) => {
|
||||
result.push(LocationType::Flag(internal.name.clone()).spanned(*span));
|
||||
result.append(&mut self.with_flag(name.clone()).expression(expr));
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn pipeline(&self, pipeline: &Pipeline) -> Vec<CompletionLocation> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for command in &pipeline.list {
|
||||
match command {
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
let engine = self.with_command(internal.name.clone());
|
||||
result.append(&mut engine.internal_command(internal));
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => result.append(&mut self.expression(expr)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Flattens the block into a Vec of completion locations
|
||||
pub fn completion_locations(&self, block: &Block) -> Vec<CompletionLocation> {
|
||||
block
|
||||
.block
|
||||
.iter()
|
||||
.flat_map(|g| g.pipelines.iter().flat_map(|v| self.pipeline(v)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new(line: &'s str) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line,
|
||||
command: None,
|
||||
flag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_command(&self, command: String) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line: self.line,
|
||||
command: Some(command),
|
||||
flag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_flag(&self, flag: String) -> Flatten<'s> {
|
||||
Flatten {
|
||||
line: self.line,
|
||||
command: self.command.clone(),
|
||||
flag: Some(flag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Characters that precede a command name
|
||||
const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';'];
|
||||
|
||||
/// Determines the completion location for a given block at the given cursor position
|
||||
pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec<CompletionLocation> {
|
||||
let completion_engine = Flatten::new(line);
|
||||
let locations = completion_engine.completion_locations(block);
|
||||
|
||||
if locations.is_empty() {
|
||||
vec![LocationType::Command.spanned(Span::unknown())]
|
||||
} else {
|
||||
let mut command = None;
|
||||
let mut prev = None;
|
||||
for loc in locations {
|
||||
// We don't use span.contains because we want to include the end. This handles the case
|
||||
// where the cursor is just after the text (i.e., no space between cursor and text)
|
||||
if loc.span.start() <= pos && pos <= loc.span.end() {
|
||||
// The parser sees the "-" in `cmd -` as an argument, but the user is likely
|
||||
// expecting a flag.
|
||||
return match loc.item {
|
||||
LocationType::Argument(ref cmd, _) => {
|
||||
if loc.span.slice(line) == "-" {
|
||||
let cmd = cmd.clone();
|
||||
let span = loc.span;
|
||||
vec![
|
||||
loc,
|
||||
LocationType::Flag(cmd.unwrap_or_default()).spanned(span),
|
||||
]
|
||||
} else {
|
||||
vec![loc]
|
||||
}
|
||||
}
|
||||
_ => vec![loc],
|
||||
};
|
||||
} else if pos < loc.span.start() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let LocationType::Command = loc.item {
|
||||
command = Some(String::from(loc.span.slice(line)));
|
||||
}
|
||||
|
||||
prev = Some(loc);
|
||||
}
|
||||
|
||||
if let Some(prev) = prev {
|
||||
// Cursor is between locations (or at the end). Look at the line to see if the cursor
|
||||
// is after some character that would imply we're in the command position.
|
||||
let start = prev.span.end();
|
||||
if line[start..pos].contains(BEFORE_COMMAND_CHARS) {
|
||||
vec![LocationType::Command.spanned(Span::new(pos, pos))]
|
||||
} else {
|
||||
// TODO this should be able to be mapped to a command
|
||||
vec![LocationType::Argument(command, None).spanned(Span::new(pos, pos))]
|
||||
}
|
||||
} else {
|
||||
// Cursor is before any possible completion location, so must be a command
|
||||
vec![LocationType::Command.spanned(Span::unknown())]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::{block, classify_block, lex, ParserScope};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VecRegistry(Vec<Signature>);
|
||||
|
||||
impl From<Vec<Signature>> for VecRegistry {
|
||||
fn from(v: Vec<Signature>) -> Self {
|
||||
VecRegistry(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParserScope for VecRegistry {
|
||||
fn has_signature(&self, name: &str) -> bool {
|
||||
self.0.iter().any(|v| v.name == name)
|
||||
}
|
||||
|
||||
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> {
|
||||
self.0.iter().find(|v| v.name == name).map(Clone::clone)
|
||||
}
|
||||
|
||||
fn get_alias(&self, _name: &str) -> Option<Vec<Spanned<String>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_alias(&self, _name: &str, _replacement: Vec<Spanned<String>>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn add_definition(&self, _block: Block) {}
|
||||
|
||||
fn get_definitions(&self) -> Vec<Block> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn enter_scope(&self) {}
|
||||
|
||||
fn exit_scope(&self) {}
|
||||
}
|
||||
|
||||
mod completion_location {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::ParserScope;
|
||||
|
||||
fn completion_location(
|
||||
line: &str,
|
||||
scope: &dyn ParserScope,
|
||||
pos: usize,
|
||||
) -> Vec<LocationType> {
|
||||
let (tokens, _) = lex(line, 0);
|
||||
let (lite_block, _) = block(tokens);
|
||||
|
||||
scope.enter_scope();
|
||||
let (block, _) = classify_block(&lite_block, scope);
|
||||
scope.exit_scope();
|
||||
|
||||
super::completion_location(line, &block, pos)
|
||||
.into_iter()
|
||||
.map(|v| v.item)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_internal_command_names() {
|
||||
let registry: VecRegistry =
|
||||
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 10),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_external_command_names() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 10),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_command_names_when_cursor_immediately_after_command_name() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 4),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_variables() {
|
||||
let registry: VecRegistry = Vec::new().into();
|
||||
let line = "echo $nu.env.";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 13),
|
||||
vec![LocationType::Variable],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_flags() {
|
||||
let registry: VecRegistry = vec![Signature::build("du")
|
||||
.switch("recursive", "the values to echo", None)
|
||||
.rest(SyntaxShape::Any, "blah")]
|
||||
.into();
|
||||
|
||||
let line = "du --recurs";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 7),
|
||||
vec![LocationType::Flag("du".to_string())],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_incomplete_nested_structure() {
|
||||
let registry: VecRegistry = vec![Signature::build("sys")].into();
|
||||
let line = "echo $(sy";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 8),
|
||||
vec![LocationType::Command],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_command_name_for_argument() {
|
||||
let registry: VecRegistry = vec![Signature::build("cd")].into();
|
||||
let line = "cd ";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 3),
|
||||
vec![LocationType::Argument(Some("cd".to_string()), None)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_flags_with_just_a_single_hyphen() {
|
||||
let registry: VecRegistry = vec![Signature::build("du")
|
||||
.switch("recursive", "the values to echo", None)
|
||||
.rest(SyntaxShape::Any, "blah")]
|
||||
.into();
|
||||
|
||||
let line = "du -";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 3),
|
||||
vec![
|
||||
LocationType::Argument(Some("du".to_string()), None),
|
||||
LocationType::Flag("du".to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_arguments() {
|
||||
let registry: VecRegistry =
|
||||
vec![Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")].into();
|
||||
let line = "echo 1 | echo 2";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 6),
|
||||
vec![LocationType::Argument(Some("echo".to_string()), None)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
41
crates/nu-cli/src/completion/flag.rs
Normal file
41
crates/nu-cli/src/completion/flag.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
pub struct FlagCompleter {
|
||||
pub(crate) cmd: String,
|
||||
}
|
||||
|
||||
impl Completer for FlagCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
|
||||
if let Some(cmd) = context.scope.get_command(&self.cmd) {
|
||||
let sig = cmd.signature();
|
||||
let mut suggestions = Vec::new();
|
||||
for (name, (named_type, _desc)) in sig.named.iter() {
|
||||
suggestions.push(format!("--{}", name));
|
||||
|
||||
if let Some(c) = named_type.get_short() {
|
||||
suggestions.push(format!("-{}", c));
|
||||
}
|
||||
}
|
||||
|
||||
suggestions
|
||||
.into_iter()
|
||||
.filter(|v| matcher.matches(partial, v))
|
||||
.map(|v| Suggestion {
|
||||
replacement: format!("{} ", v),
|
||||
display: v,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
45
crates/nu-cli/src/completion/matchers/case_insensitive.rs
Normal file
45
crates/nu-cli/src/completion/matchers/case_insensitive.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::completion::matchers;
|
||||
pub struct Matcher;
|
||||
|
||||
impl matchers::Matcher for Matcher {
|
||||
fn matches(&self, partial: &str, from: &str) -> bool {
|
||||
from.to_ascii_lowercase()
|
||||
.starts_with(partial.to_ascii_lowercase().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: check some unicode matches if this becomes relevant
|
||||
|
||||
// FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test
|
||||
#[test]
|
||||
fn completes_exact_matches() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(matcher.matches("shouldmatch", "shouldmatch"));
|
||||
assert!(matcher.matches("shouldm", "shouldmatch"));
|
||||
assert!(matcher.matches("--also-should-m", "--also-should-match"));
|
||||
assert!(matcher.matches("-also-should-m", "-also-should-match"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_case_insensitive_matches() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(matcher.matches("thisshould", "Thisshouldmatch"));
|
||||
assert!(matcher.matches("--Shouldm", "--shouldmatch"));
|
||||
assert!(matcher.matches("-Shouldm", "-shouldmatch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_match_when_unequal() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
assert!(!matcher.matches("ashouldmatch", "Shouldnotmatch"));
|
||||
assert!(!matcher.matches("--ashouldnotmatch", "--Shouldnotmatch"));
|
||||
assert!(!matcher.matches("-ashouldnotmatch", "-Shouldnotmatch"));
|
||||
}
|
||||
}
|
28
crates/nu-cli/src/completion/matchers/case_sensitive.rs
Normal file
28
crates/nu-cli/src/completion/matchers/case_sensitive.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use crate::completion::matchers;
|
||||
|
||||
pub struct Matcher;
|
||||
|
||||
impl matchers::Matcher for Matcher {
|
||||
fn matches(&self, partial: &str, from: &str) -> bool {
|
||||
from.starts_with(partial)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn completes_case_sensitive() {
|
||||
let matcher: Box<dyn matchers::Matcher> = Box::new(Matcher);
|
||||
|
||||
//Should match
|
||||
assert!(matcher.matches("shouldmatch", "shouldmatch"));
|
||||
assert!(matcher.matches("shouldm", "shouldmatch"));
|
||||
assert!(matcher.matches("--also-should-m", "--also-should-match"));
|
||||
assert!(matcher.matches("-also-should-m", "-also-should-match"));
|
||||
|
||||
// Should not match
|
||||
assert!(!matcher.matches("--Shouldnot", "--shouldnotmatch"));
|
||||
}
|
||||
}
|
6
crates/nu-cli/src/completion/matchers/mod.rs
Normal file
6
crates/nu-cli/src/completion/matchers/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub(crate) mod case_insensitive;
|
||||
pub(crate) mod case_sensitive;
|
||||
|
||||
pub trait Matcher {
|
||||
fn matches(&self, partial: &str, from: &str) -> bool;
|
||||
}
|
37
crates/nu-cli/src/completion/mod.rs
Normal file
37
crates/nu-cli/src/completion/mod.rs
Normal file
@ -0,0 +1,37 @@
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod engine;
|
||||
pub(crate) mod flag;
|
||||
pub(crate) mod matchers;
|
||||
pub(crate) mod path;
|
||||
|
||||
use matchers::Matcher;
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Suggestion {
|
||||
pub display: String,
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
pub struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> CompletionContext<'a> {
|
||||
pub fn new(a: &'a EvaluationContext) -> CompletionContext<'a> {
|
||||
CompletionContext(a)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
|
||||
fn as_ref(&self) -> &EvaluationContext {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Completer {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion>;
|
||||
}
|
88
crates/nu-cli/src/completion/path.rs
Normal file
88
crates/nu-cli/src/completion/path.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub struct PathCompleter;
|
||||
|
||||
pub struct PathSuggestion {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) suggestion: Suggestion,
|
||||
}
|
||||
|
||||
impl PathCompleter {
|
||||
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
||||
let expanded = nu_parser::expand_ndots(partial);
|
||||
let expanded = expanded.as_ref();
|
||||
|
||||
let (base_dir_name, partial) = match expanded.rfind(SEP) {
|
||||
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
|
||||
None => ("", expanded),
|
||||
};
|
||||
|
||||
let base_dir = if base_dir_name.is_empty() {
|
||||
PathBuf::from(".")
|
||||
} else {
|
||||
#[cfg(feature = "directories")]
|
||||
{
|
||||
let home_prefix = format!("~{}", SEP);
|
||||
if base_dir_name.starts_with(&home_prefix) {
|
||||
let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~"));
|
||||
home_dir.push(&base_dir_name[2..]);
|
||||
home_dir
|
||||
} else {
|
||||
PathBuf::from(base_dir_name)
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "directories"))]
|
||||
{
|
||||
PathBuf::from(base_dir_name)
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matcher.matches(partial, file_name.as_str()) {
|
||||
let mut path = format!("{}{}", base_dir_name, file_name);
|
||||
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
||||
path.push(std::path::MAIN_SEPARATOR);
|
||||
file_name.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
|
||||
Some(PathSuggestion {
|
||||
path: entry.path(),
|
||||
suggestion: Suggestion {
|
||||
replacement: path,
|
||||
display: file_name,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for PathCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
_ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
self.path_suggestions(partial, matcher)
|
||||
.into_iter()
|
||||
.map(|ps| ps.suggestion)
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
|
||||
use crate::env::host::Host;
|
||||
use crate::shell::shell_manager::ShellManager;
|
||||
use crate::stream::{InputStream, OutputStream};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::{hir, Scope, Signature};
|
||||
use nu_source::{Tag, Text};
|
||||
use parking_lot::Mutex;
|
||||
use std::error::Error;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
pub registry: CommandRegistry,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub raw_input: String,
|
||||
pub user_recently_used_autoenv_untrust: bool,
|
||||
pub(crate) shell_manager: ShellManager,
|
||||
|
||||
#[cfg(windows)]
|
||||
pub windows_drives_previous_cwd: Arc<Mutex<std::collections::HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn registry(&self) -> &CommandRegistry {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub(crate) fn from_raw(raw_args: &CommandArgs, registry: &CommandRegistry) -> Context {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: raw_args.host.clone(),
|
||||
current_errors: raw_args.current_errors.clone(),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: raw_args.host.clone(),
|
||||
current_errors: raw_args.current_errors.clone(),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_args(args: &CommandArgs, registry: &CommandRegistry) -> Context {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
current_errors: args.current_errors.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
current_errors: args.current_errors.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
raw_input: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic() -> Result<Context, Box<dyn Error>> {
|
||||
let registry = CommandRegistry::new();
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Ok(Context {
|
||||
registry,
|
||||
host: Arc::new(parking_lot::Mutex::new(Box::new(
|
||||
crate::env::host::BasicHost,
|
||||
))),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: ShellManager::basic()?,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
raw_input: String::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Ok(Context {
|
||||
registry,
|
||||
host: Arc::new(parking_lot::Mutex::new(Box::new(
|
||||
crate::env::host::BasicHost,
|
||||
))),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
user_recently_used_autoenv_untrust: false,
|
||||
shell_manager: ShellManager::basic()?,
|
||||
raw_input: String::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn error(&mut self, error: ShellError) {
|
||||
self.with_errors(|errors| errors.push(error))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_errors(&mut self) {
|
||||
self.current_errors.lock().clear()
|
||||
}
|
||||
|
||||
pub(crate) fn get_errors(&self) -> Vec<ShellError> {
|
||||
self.current_errors.lock().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn add_error(&self, err: ShellError) {
|
||||
self.current_errors.lock().push(err);
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
|
||||
let errors = self.current_errors.clone();
|
||||
let mut errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
let error = errors[0].clone();
|
||||
*errors = vec![];
|
||||
|
||||
crate::cli::print_err(error, &source);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
|
||||
let mut host = self.host.lock();
|
||||
|
||||
block(&mut *host)
|
||||
}
|
||||
|
||||
pub(crate) fn with_errors<T>(&mut self, block: impl FnOnce(&mut Vec<ShellError>) -> T) -> T {
|
||||
let mut errors = self.current_errors.lock();
|
||||
|
||||
block(&mut *errors)
|
||||
}
|
||||
|
||||
pub fn add_commands(&mut self, commands: Vec<Command>) {
|
||||
for command in commands {
|
||||
self.registry.insert(command.name().to_string(), command);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
|
||||
pub(crate) fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
||||
self.registry.expect_command(name)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_command(
|
||||
&mut self,
|
||||
command: Command,
|
||||
name_tag: Tag,
|
||||
args: hir::Call,
|
||||
scope: &Scope,
|
||||
input: InputStream,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let command_args = self.command_args(args, input, name_tag, scope);
|
||||
command.run(command_args, self.registry()).await
|
||||
}
|
||||
|
||||
fn call_info(&self, args: hir::Call, name_tag: Tag, scope: &Scope) -> UnevaluatedCallInfo {
|
||||
UnevaluatedCallInfo {
|
||||
args,
|
||||
name_tag,
|
||||
scope: scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn command_args(
|
||||
&self,
|
||||
args: hir::Call,
|
||||
input: InputStream,
|
||||
name_tag: Tag,
|
||||
scope: &Scope,
|
||||
) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host.clone(),
|
||||
ctrl_c: self.ctrl_c.clone(),
|
||||
current_errors: self.current_errors.clone(),
|
||||
shell_manager: self.shell_manager.clone(),
|
||||
call_info: self.call_info(args, name_tag, scope),
|
||||
input,
|
||||
raw_input: self.raw_input.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_env(&self) -> IndexMap<String, String> {
|
||||
let mut output = IndexMap::new();
|
||||
for (var, value) in self.host.lock().vars() {
|
||||
output.insert(var, value);
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
pub(crate) mod base;
|
||||
pub(crate) mod command;
|
||||
pub mod config;
|
||||
pub(crate) mod dict;
|
||||
pub(crate) mod files;
|
||||
pub mod primitive;
|
||||
pub(crate) mod types;
|
||||
pub mod value;
|
||||
|
||||
pub(crate) use command::command_dict;
|
||||
pub(crate) use dict::TaggedListBuilder;
|
||||
pub(crate) use files::dir_entry_dict;
|
@ -1,163 +0,0 @@
|
||||
mod conf;
|
||||
mod nuconfig;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
pub(crate) use conf::Conf;
|
||||
pub(crate) use nuconfig::NuConfig;
|
||||
|
||||
use crate::commands::from_toml::convert_toml_value_to_nu_value;
|
||||
use crate::commands::to_toml::value_to_toml_value;
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(feature = "directories")]
|
||||
pub fn config_path() -> Result<PathBuf, ShellError> {
|
||||
use directories::ProjectDirs;
|
||||
|
||||
let dir = ProjectDirs::from("org", "nushell", "nu")
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
|
||||
let path = ProjectDirs::config_dir(&dir).to_owned();
|
||||
std::fs::create_dir_all(&path).map_err(|err| {
|
||||
ShellError::untagged_runtime_error(&format!("Couldn't create {} path:\n{}", "config", err))
|
||||
})?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "directories"))]
|
||||
pub fn config_path() -> Result<PathBuf, ShellError> {
|
||||
// FIXME: unsure if this should be error or a simple default
|
||||
|
||||
Ok(std::path::PathBuf::from("/"))
|
||||
}
|
||||
|
||||
pub fn default_path() -> Result<PathBuf, ShellError> {
|
||||
default_path_for(&None)
|
||||
}
|
||||
|
||||
pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
|
||||
let mut filename = config_path()?;
|
||||
let file: &Path = file
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
.unwrap_or_else(|| "config.toml".as_ref());
|
||||
filename.push(file);
|
||||
|
||||
Ok(filename)
|
||||
}
|
||||
|
||||
#[cfg(feature = "directories")]
|
||||
pub fn user_data() -> Result<PathBuf, ShellError> {
|
||||
use directories::ProjectDirs;
|
||||
|
||||
let dir = ProjectDirs::from("org", "nushell", "nu")
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
|
||||
let path = ProjectDirs::data_local_dir(&dir).to_owned();
|
||||
std::fs::create_dir_all(&path).map_err(|err| {
|
||||
ShellError::untagged_runtime_error(&format!(
|
||||
"Couldn't create {} path:\n{}",
|
||||
"user data", err
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "directories"))]
|
||||
pub fn user_data() -> Result<PathBuf, ShellError> {
|
||||
// FIXME: unsure if this should be error or a simple default
|
||||
|
||||
Ok(std::path::PathBuf::from("/"))
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
tag: impl Into<Tag>,
|
||||
at: &Option<PathBuf>,
|
||||
) -> Result<IndexMap<String, Value>, ShellError> {
|
||||
let filename = default_path()?;
|
||||
|
||||
let filename = match at {
|
||||
None => filename,
|
||||
Some(ref file) => file.clone(),
|
||||
};
|
||||
|
||||
if !filename.exists() && touch(&filename).is_err() {
|
||||
// If we can't create configs, let's just return an empty indexmap instead as we may be in
|
||||
// a readonly environment
|
||||
return Ok(IndexMap::new());
|
||||
}
|
||||
|
||||
trace!("config file = {}", filename.display());
|
||||
|
||||
let tag = tag.into();
|
||||
let contents = fs::read_to_string(filename)
|
||||
.map(|v| v.tagged(&tag))
|
||||
.map_err(|err| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Couldn't read config file:\n{}", err),
|
||||
"file name",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let parsed: toml::Value = toml::from_str(&contents).map_err(|err| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Couldn't parse config file:\n{}", err),
|
||||
"file name",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let value = convert_toml_value_to_nu_value(&parsed, tag);
|
||||
let tag = value.tag();
|
||||
match value.value {
|
||||
UntaggedValue::Row(Dictionary { entries }) => Ok(entries),
|
||||
other => Err(ShellError::type_error(
|
||||
"Dictionary",
|
||||
other.type_name().spanned(tag.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
|
||||
read(tag, &None)
|
||||
}
|
||||
|
||||
pub fn write(config: &IndexMap<String, Value>, at: &Option<PathBuf>) -> Result<(), ShellError> {
|
||||
let filename = &mut default_path()?;
|
||||
let filename = match at {
|
||||
None => filename,
|
||||
Some(file) => {
|
||||
filename.pop();
|
||||
filename.push(file);
|
||||
filename
|
||||
}
|
||||
};
|
||||
|
||||
let contents = value_to_toml_value(
|
||||
&UntaggedValue::Row(Dictionary::new(config.clone())).into_untagged_value(),
|
||||
)?;
|
||||
|
||||
let contents = toml::to_string(&contents)?;
|
||||
|
||||
fs::write(&filename, &contents)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A simple implementation of `% touch path` (ignores existing files)
|
||||
fn touch(path: &Path) -> io::Result<()> {
|
||||
match OpenOptions::new().create(true).write(true).open(path) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
use nu_protocol::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Conf: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
fn reload(&self);
|
||||
}
|
||||
|
||||
impl Conf for Box<dyn Conf> {
|
||||
fn env(&self) -> Option<Value> {
|
||||
(**self).env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
(**self).reload();
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
use crate::data::config::{read, Conf};
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NuConfig {
|
||||
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
|
||||
}
|
||||
|
||||
impl Conf for NuConfig {
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
let mut vars = self.vars.lock();
|
||||
|
||||
if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
vars.extend(variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NuConfig {
|
||||
pub fn new() -> NuConfig {
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
|
||||
NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn env(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
|
||||
if let Some(env_vars) = vars.get("env") {
|
||||
return Some(env_vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
|
||||
if let Some(env_vars) = vars.get("path") {
|
||||
return Some(env_vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
use crate::data::config::{read, Conf, NuConfig};
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FakeConfig {
|
||||
pub config: NuConfig,
|
||||
}
|
||||
|
||||
impl Conf for FakeConfig {
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.config.env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
self.config.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeConfig {
|
||||
pub fn new(config_file: &Path) -> FakeConfig {
|
||||
let config_file = PathBuf::from(config_file);
|
||||
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &Some(config_file)) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
|
||||
FakeConfig {
|
||||
config: NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
use crate::commands::du::{DirBuilder, DirInfo};
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
pub(crate) fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
let ft = md.file_type();
|
||||
let mut file_type = "Unknown";
|
||||
if ft.is_dir() {
|
||||
file_type = "Dir";
|
||||
} else if ft.is_file() {
|
||||
file_type = "File";
|
||||
} else if ft.is_symlink() {
|
||||
file_type = "Symlink";
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if ft.is_block_device() {
|
||||
file_type = "Block device";
|
||||
} else if ft.is_char_device() {
|
||||
file_type = "Char device";
|
||||
} else if ft.is_fifo() {
|
||||
file_type = "Pipe";
|
||||
} else if ft.is_socket() {
|
||||
file_type = "Socket";
|
||||
}
|
||||
}
|
||||
}
|
||||
file_type
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dir_entry_dict(
|
||||
filename: &std::path::Path,
|
||||
metadata: Option<&std::fs::Metadata>,
|
||||
tag: impl Into<Tag>,
|
||||
long: bool,
|
||||
short_name: bool,
|
||||
with_symlink_targets: bool,
|
||||
du: bool,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
// Insert all columns first to maintain proper table alignment if we can't find (or are not allowed to view) any information
|
||||
if long {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "size", "created", "accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "mode", "uid", "group", "size", "created",
|
||||
"accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(&(*column.to_owned()), UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for column in ["name", "type", "target", "size", "modified"].iter() {
|
||||
if *column == "target" && !with_symlink_targets {
|
||||
continue;
|
||||
}
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
let name = if short_name {
|
||||
filename.file_name().and_then(|s| s.to_str())
|
||||
} else {
|
||||
filename.to_str()
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Invalid file name: {:}", filename.to_string_lossy()),
|
||||
"invalid file name",
|
||||
tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
dict.insert_untagged("name", UntaggedValue::string(name));
|
||||
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged("type", get_file_type(md));
|
||||
}
|
||||
|
||||
if long || with_symlink_targets {
|
||||
if let Some(md) = metadata {
|
||||
if md.file_type().is_symlink() {
|
||||
let symlink_target_untagged_value: UntaggedValue;
|
||||
if let Ok(path_to_link) = filename.read_link() {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string(path_to_link.to_string_lossy());
|
||||
} else {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string("Could not obtain target file's path");
|
||||
}
|
||||
dict.insert_untagged("target", symlink_target_untagged_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged(
|
||||
"readonly",
|
||||
UntaggedValue::boolean(md.permissions().readonly()),
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = md.permissions().mode();
|
||||
dict.insert_untagged(
|
||||
"mode",
|
||||
UntaggedValue::string(umask::Mode::from(mode).to_string()),
|
||||
);
|
||||
|
||||
if let Some(user) = users::get_user_by_uid(md.uid()) {
|
||||
dict.insert_untagged(
|
||||
"uid",
|
||||
UntaggedValue::string(user.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(group) = users::get_group_by_gid(md.gid()) {
|
||||
dict.insert_untagged(
|
||||
"group",
|
||||
UntaggedValue::string(group.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
let mut size_untagged_value: UntaggedValue = UntaggedValue::nothing();
|
||||
|
||||
if md.is_dir() {
|
||||
let dir_size: u64 = if du {
|
||||
let params = DirBuilder::new(
|
||||
Tag {
|
||||
anchor: None,
|
||||
span: Span::new(0, 2),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
DirInfo::new(filename, ¶ms, None, ctrl_c).get_size()
|
||||
} else {
|
||||
md.len()
|
||||
};
|
||||
|
||||
size_untagged_value = UntaggedValue::filesize(dir_size);
|
||||
} else if md.is_file() {
|
||||
size_untagged_value = UntaggedValue::filesize(md.len());
|
||||
} else if md.file_type().is_symlink() {
|
||||
if let Ok(symlink_md) = filename.symlink_metadata() {
|
||||
size_untagged_value = UntaggedValue::filesize(symlink_md.len() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
dict.insert_untagged("size", size_untagged_value);
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
if long {
|
||||
if let Ok(c) = md.created() {
|
||||
dict.insert_untagged("created", UntaggedValue::system_date(c));
|
||||
}
|
||||
|
||||
if let Ok(a) = md.accessed() {
|
||||
dict.insert_untagged("accessed", UntaggedValue::system_date(a));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(m) = md.modified() {
|
||||
dict.insert_untagged("modified", UntaggedValue::system_date(m));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dict.into_value())
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use nu_protocol::{hir::Number, Primitive};
|
||||
use nu_table::TextStyle;
|
||||
|
||||
pub fn number(number: impl Into<Number>) -> Primitive {
|
||||
let number = number.into();
|
||||
|
||||
match number {
|
||||
Number::Int(int) => Primitive::Int(int),
|
||||
Number::Decimal(decimal) => Primitive::Decimal(decimal),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style_primitive(primitive: &Primitive) -> TextStyle {
|
||||
match primitive {
|
||||
Primitive::Int(_) | Primitive::Filesize(_) | Primitive::Decimal(_) => {
|
||||
TextStyle::basic_right()
|
||||
}
|
||||
_ => TextStyle::basic(),
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
use crate::data::{TaggedDictBuilder, Value};
|
||||
use crate::prelude::*;
|
||||
use itertools::join;
|
||||
use sysinfo::ProcessExt;
|
||||
|
||||
pub(crate) fn process_dict(proc: &sysinfo::Process, tag: impl Into<Tag>) -> Value {
|
||||
let mut dict = TaggedDictBuilder::new(tag);
|
||||
|
||||
let cmd = proc.cmd();
|
||||
|
||||
let cmd_value = if cmd.len() == 0 {
|
||||
value::nothing()
|
||||
} else {
|
||||
value::string(join(cmd, ""))
|
||||
};
|
||||
|
||||
dict.insert("pid", value::int(proc.pid() as i64));
|
||||
dict.insert("status", value::string(proc.status().to_string()));
|
||||
dict.insert("cpu", value::number(proc.cpu_usage()));
|
||||
|
||||
match cmd_value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => {
|
||||
dict.insert("name", value::string(proc.name()));
|
||||
}
|
||||
_ => dict.insert("name", cmd_value),
|
||||
}
|
||||
|
||||
dict.into_value()
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
pub(crate) mod directory_specific_environment;
|
||||
pub(crate) mod environment;
|
||||
pub(crate) mod environment_syncer;
|
||||
pub(crate) mod host;
|
||||
|
||||
pub(crate) use self::host::Host;
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::commands;
|
||||
use commands::autoenv;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use nu_command::commands::autoenv;
|
||||
use nu_errors::ShellError;
|
||||
use serde::Deserialize;
|
||||
use std::env::*;
|
||||
@ -112,7 +111,7 @@ impl DirectorySpecificEnvironment {
|
||||
|
||||
if let Some(es) = nu_env_doc.entryscripts {
|
||||
for s in es {
|
||||
run(s.as_str())?;
|
||||
run(s.as_str(), None)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +146,7 @@ impl DirectorySpecificEnvironment {
|
||||
new_exitscripts.insert(dir, scripts);
|
||||
} else {
|
||||
for s in scripts {
|
||||
run(s.as_str())?;
|
||||
run(s.as_str(), Some(&dir))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,9 +214,23 @@ impl DirectorySpecificEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cmd: &str) -> Result<(), ShellError> {
|
||||
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
|
||||
if cfg!(target_os = "windows") {
|
||||
Command::new("cmd").args(&["/C", cmd]).output()?
|
||||
if let Some(dir) = dir {
|
||||
let command = format!("cd {} & {}", dir.to_string_lossy(), cmd);
|
||||
Command::new("cmd")
|
||||
.args(&["/C", command.as_str()])
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("cmd").args(&["/C", cmd]).output()?
|
||||
}
|
||||
} else if let Some(dir) = dir {
|
||||
// FIXME: When nu scripting is added, cding like might not be a good idea. If nu decides to execute entryscripts when entering the dir this way, it will cause troubles.
|
||||
// For now only standard shell scripts are used, so this is an issue for the future.
|
||||
Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("cd {:?}; {}", dir, cmd))
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(&cmd).output()?
|
||||
};
|
||||
|
37
crates/nu-cli/src/env/environment.rs
vendored
37
crates/nu-cli/src/env/environment.rs
vendored
@ -1,38 +1,9 @@
|
||||
use crate::data::config::Conf;
|
||||
use crate::env::directory_specific_environment::*;
|
||||
use indexmap::{indexmap, IndexSet};
|
||||
use nu_data::config::Conf;
|
||||
use nu_engine::Env;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::env::*;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Env: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str);
|
||||
fn add_path(&mut self, new_path: OsString);
|
||||
}
|
||||
|
||||
impl Env for Box<dyn Env> {
|
||||
fn env(&self) -> Option<Value> {
|
||||
(**self).env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str) {
|
||||
(**self).add_env(key, value);
|
||||
}
|
||||
|
||||
fn add_path(&mut self, new_path: OsString) {
|
||||
(**self).add_path(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Environment {
|
||||
@ -128,7 +99,7 @@ impl Env for Environment {
|
||||
{
|
||||
let mut new_paths = current_paths.clone();
|
||||
|
||||
let new_path_candidates = split_paths(&paths).map(|path| {
|
||||
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
|
||||
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
|
||||
});
|
||||
|
||||
@ -154,7 +125,7 @@ impl Env for Environment {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Env, Environment};
|
||||
use crate::data::config::{tests::FakeConfig, Conf};
|
||||
use nu_data::config::{tests::FakeConfig, Conf};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
201
crates/nu-cli/src/env/environment_syncer.rs
vendored
201
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,14 +1,14 @@
|
||||
use crate::context::Context;
|
||||
use crate::data::config::{Conf, NuConfig};
|
||||
|
||||
use crate::env::environment::{Env, Environment};
|
||||
use nu_source::Text;
|
||||
use crate::env::environment::Environment;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_engine::Env;
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_errors::ShellError;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
|
||||
pub struct EnvironmentSyncer {
|
||||
pub env: Arc<Mutex<Box<Environment>>>,
|
||||
pub config: Arc<Box<dyn Conf>>,
|
||||
pub config: Arc<Mutex<Box<dyn Conf>>>,
|
||||
}
|
||||
|
||||
impl Default for EnvironmentSyncer {
|
||||
@ -18,38 +18,64 @@ impl Default for EnvironmentSyncer {
|
||||
}
|
||||
|
||||
impl EnvironmentSyncer {
|
||||
pub fn with_config(config: Box<dyn Conf>) -> Self {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> EnvironmentSyncer {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Box::new(NuConfig::new())),
|
||||
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_config(&mut self, config: Box<dyn Conf>) {
|
||||
self.config = Arc::new(config);
|
||||
self.config = Arc::new(Mutex::new(config));
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Box<dyn Conf> {
|
||||
let config = self.config.lock();
|
||||
|
||||
config.clone_box()
|
||||
}
|
||||
|
||||
pub fn load_environment(&mut self) {
|
||||
let config = self.config.clone();
|
||||
let config = self.config.lock();
|
||||
|
||||
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
self.config.reload();
|
||||
|
||||
let mut environment = self.env.lock();
|
||||
environment.morph(&*self.config);
|
||||
pub fn did_config_change(&mut self) -> bool {
|
||||
let config = self.config.lock();
|
||||
config.is_modified().unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
||||
pub fn reload(&mut self) {
|
||||
let mut config = self.config.lock();
|
||||
config.reload();
|
||||
|
||||
let mut environment = self.env.lock();
|
||||
environment.morph(&*config);
|
||||
}
|
||||
|
||||
pub fn autoenv(&self, ctx: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||
let mut environment = self.env.lock();
|
||||
let recently_used = ctx
|
||||
.user_recently_used_autoenv_untrust
|
||||
.load(Ordering::SeqCst);
|
||||
let auto = environment.autoenv(recently_used);
|
||||
ctx.user_recently_used_autoenv_untrust
|
||||
.store(false, Ordering::SeqCst);
|
||||
auto
|
||||
}
|
||||
|
||||
pub fn sync_env_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
|
||||
crate::cli::print_err(e, &Text::from(""));
|
||||
}
|
||||
ctx.user_recently_used_autoenv_untrust = false;
|
||||
if environment.env().is_some() {
|
||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||
if name != "path" && name != "PATH" {
|
||||
@ -78,7 +104,7 @@ impl EnvironmentSyncer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_path_vars(&mut self, ctx: &mut Context) {
|
||||
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
if environment.path().is_some() {
|
||||
@ -110,7 +136,7 @@ impl EnvironmentSyncer {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn clear_env_vars(&mut self, ctx: &mut Context) {
|
||||
pub fn clear_env_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
for (key, _value) in ctx.with_host(|host| host.vars()) {
|
||||
if key != "path" && key != "PATH" {
|
||||
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(key)));
|
||||
@ -119,7 +145,7 @@ impl EnvironmentSyncer {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn clear_path_var(&mut self, ctx: &mut Context) {
|
||||
pub fn clear_path_var(&mut self, ctx: &mut EvaluationContext) {
|
||||
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from("PATH")));
|
||||
}
|
||||
}
|
||||
@ -127,10 +153,10 @@ impl EnvironmentSyncer {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EnvironmentSyncer;
|
||||
use crate::context::Context;
|
||||
use crate::data::config::tests::FakeConfig;
|
||||
use crate::env::environment::Env;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::config::tests::FakeConfig;
|
||||
use nu_engine::basic_evaluation_context;
|
||||
use nu_engine::Env;
|
||||
use nu_errors::ShellError;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
@ -138,11 +164,118 @@ mod tests {
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
// This test fails on Linux.
|
||||
// It's possible it has something to do with the fake configuration
|
||||
// TODO: More tests.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
);
|
||||
|
||||
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
),
|
||||
FileWithContent(
|
||||
"updated_configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
USER = "NUNO"
|
||||
"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let file = dirs.test().join("configuration.toml");
|
||||
let new_file = dirs.test().join("updated_configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
// Nu loads the environment variables from the configuration file
|
||||
actual.load_environment();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!actual.did_config_change());
|
||||
|
||||
// Replacing the newer configuration file to the existing one.
|
||||
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
|
||||
std::fs::write(&file, &new_config_contents).expect("Failed");
|
||||
|
||||
// A change has happened
|
||||
assert!(actual.did_config_change());
|
||||
|
||||
// Syncer should reload and add new envs
|
||||
actual.reload();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
@ -240,8 +373,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
@ -316,8 +449,8 @@ mod tests {
|
||||
#[test]
|
||||
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
|
||||
) -> Result<(), ShellError> {
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let expected = std::env::join_paths(vec![
|
||||
PathBuf::from("/Users/andresrobalino/.volta/bin"),
|
||||
@ -403,8 +536,8 @@ mod tests {
|
||||
#[test]
|
||||
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
|
||||
) -> Result<(), ShellError> {
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let expected = std::env::join_paths(vec![
|
||||
PathBuf::from("/Users/andresrobalino/.volta/bin"),
|
||||
|
@ -1,215 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::evaluate::operator::apply_operator;
|
||||
use crate::prelude::*;
|
||||
use async_recursion::async_recursion;
|
||||
use log::trace;
|
||||
use nu_errors::{ArgumentError, ShellError};
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, SpannedExpression};
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
#[async_recursion]
|
||||
pub(crate) async fn evaluate_baseline_expr(
|
||||
expr: &SpannedExpression,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = Tag {
|
||||
span: expr.span,
|
||||
anchor: None,
|
||||
};
|
||||
let span = expr.span;
|
||||
match &expr.expr {
|
||||
Expression::Literal(literal) => Ok(evaluate_literal(&literal, span)),
|
||||
Expression::ExternalWord => Err(ShellError::argument_error(
|
||||
"Invalid external word".spanned(tag.span),
|
||||
ArgumentError::InvalidExternalWord,
|
||||
)),
|
||||
Expression::FilePath(path) => Ok(UntaggedValue::path(path.clone()).into_value(tag)),
|
||||
Expression::Synthetic(hir::Synthetic::String(s)) => {
|
||||
Ok(UntaggedValue::string(s).into_untagged_value())
|
||||
}
|
||||
Expression::Variable(var) => evaluate_reference(&var, it, vars, env, tag),
|
||||
Expression::Command(_) => unimplemented!(),
|
||||
Expression::Invocation(block) => evaluate_invocation(block, registry, it, vars, env).await,
|
||||
Expression::ExternalCommand(_) => unimplemented!(),
|
||||
Expression::Binary(binary) => {
|
||||
// TODO: If we want to add short-circuiting, we'll need to move these down
|
||||
let left = evaluate_baseline_expr(&binary.left, registry, it, vars, env).await?;
|
||||
let right = evaluate_baseline_expr(&binary.right, registry, it, vars, env).await?;
|
||||
|
||||
trace!("left={:?} right={:?}", left.value, right.value);
|
||||
|
||||
match binary.op.expr {
|
||||
Expression::Literal(hir::Literal::Operator(op)) => {
|
||||
match apply_operator(op, &left, &right) {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Error(shell_err) => Err(shell_err),
|
||||
_ => Ok(result.into_value(tag)),
|
||||
},
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(binary.left.span),
|
||||
right_type.spanned(binary.right.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Expression::Range(range) => {
|
||||
let left = &range.left;
|
||||
let right = &range.right;
|
||||
|
||||
let left = evaluate_baseline_expr(&left, registry, it, vars, env).await?;
|
||||
let right = evaluate_baseline_expr(&right, registry, it, vars, env).await?;
|
||||
let left_span = left.tag.span;
|
||||
let right_span = right.tag.span;
|
||||
|
||||
let left = (
|
||||
left.as_primitive()?.spanned(left_span),
|
||||
RangeInclusion::Inclusive,
|
||||
);
|
||||
let right = (
|
||||
right.as_primitive()?.spanned(right_span),
|
||||
RangeInclusion::Inclusive,
|
||||
);
|
||||
|
||||
Ok(UntaggedValue::range(left, right).into_value(tag))
|
||||
}
|
||||
Expression::List(list) => {
|
||||
let mut exprs = vec![];
|
||||
|
||||
for expr in list {
|
||||
let expr = evaluate_baseline_expr(&expr, registry, it, vars, env).await?;
|
||||
exprs.push(expr);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::Table(exprs).into_value(tag))
|
||||
}
|
||||
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
|
||||
Expression::Path(path) => {
|
||||
let value = evaluate_baseline_expr(&path.head, registry, it, vars, env).await?;
|
||||
let mut item = value;
|
||||
|
||||
for member in &path.tail {
|
||||
let next = item.get_data_by_member(member);
|
||||
|
||||
match next {
|
||||
Err(err) => {
|
||||
let possibilities = item.data_descriptors();
|
||||
|
||||
if let UnspannedPathMember::String(name) = &member.unspanned {
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &name), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if !possible_matches.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
&member.span,
|
||||
));
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(next) => {
|
||||
item = next.clone().value.into_value(&tag);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(item.value.into_value(tag))
|
||||
}
|
||||
Expression::Boolean(_boolean) => unimplemented!(),
|
||||
Expression::Garbage => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
|
||||
match &literal {
|
||||
hir::Literal::ColumnPath(path) => {
|
||||
let members = path.iter().map(|member| member.to_path_member()).collect();
|
||||
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
|
||||
.into_value(span)
|
||||
}
|
||||
hir::Literal::Number(int) => match int {
|
||||
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
|
||||
nu_protocol::hir::Number::Decimal(d) => {
|
||||
UntaggedValue::decimal(d.clone()).into_value(span)
|
||||
}
|
||||
},
|
||||
hir::Literal::Size(int, unit) => unit.compute(&int).into_value(span),
|
||||
hir::Literal::String(string) => UntaggedValue::string(string).into_value(span),
|
||||
hir::Literal::GlobPattern(pattern) => UntaggedValue::pattern(pattern).into_value(span),
|
||||
hir::Literal::Bare(bare) => UntaggedValue::string(bare.clone()).into_value(span),
|
||||
hir::Literal::Operator(_) => unimplemented!("Not sure what to do with operator yet"),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_reference(
|
||||
name: &hir::Variable,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
tag: Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
match name {
|
||||
hir::Variable::It(_) => Ok(it.clone()),
|
||||
hir::Variable::Other(name, _) => match name {
|
||||
x if x == "$nu" => crate::evaluate::variables::nu(env, tag),
|
||||
x if x == "$true" => Ok(Value {
|
||||
value: UntaggedValue::boolean(true),
|
||||
tag,
|
||||
}),
|
||||
x if x == "$false" => Ok(Value {
|
||||
value: UntaggedValue::boolean(false),
|
||||
tag,
|
||||
}),
|
||||
x => Ok(vars
|
||||
.get(x)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| UntaggedValue::nothing().into_value(tag))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn evaluate_invocation(
|
||||
block: &hir::Block,
|
||||
registry: &CommandRegistry,
|
||||
it: &Value,
|
||||
vars: &IndexMap<String, Value>,
|
||||
env: &IndexMap<String, String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
// FIXME: we should use a real context here
|
||||
let mut context = Context::basic()?;
|
||||
context.registry = registry.clone();
|
||||
|
||||
let input = InputStream::one(it.clone());
|
||||
|
||||
let mut block = block.clone();
|
||||
block.set_redirect(ExternalRedirection::Stdout);
|
||||
|
||||
let result = run_block(&block, &mut context, input, it, vars, env).await?;
|
||||
|
||||
let output = result.into_vec().await;
|
||||
|
||||
if let Some(e) = context.get_errors().get(0) {
|
||||
return Err(e.clone());
|
||||
}
|
||||
|
||||
match output.len() {
|
||||
x if x > 1 => Ok(UntaggedValue::Table(output).into_value(Tag::unknown())),
|
||||
1 => Ok(output[0].clone()),
|
||||
_ => Ok(UntaggedValue::nothing().into_value(Tag::unknown())),
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
use crate::cli::History;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
for v in env.iter() {
|
||||
if v.0 != "PATH" && v.0 != "Path" {
|
||||
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
|
||||
}
|
||||
}
|
||||
nu_dict.insert_value("env", dict.into_value());
|
||||
|
||||
let config = crate::data::config::read(&tag, &None)?;
|
||||
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
||||
|
||||
let mut table = vec![];
|
||||
let path = std::env::var_os("PATH");
|
||||
if let Some(paths) = path {
|
||||
for path in std::env::split_paths(&paths) {
|
||||
table.push(UntaggedValue::path(path).into_value(&tag));
|
||||
}
|
||||
}
|
||||
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
|
||||
|
||||
let path = std::env::current_dir()?;
|
||||
nu_dict.insert_value("cwd", UntaggedValue::path(path).into_value(&tag));
|
||||
|
||||
if let Some(home) = crate::shell::filesystem_shell::homedir_if_possible() {
|
||||
nu_dict.insert_value("home-dir", UntaggedValue::path(home).into_value(&tag));
|
||||
}
|
||||
|
||||
let temp = std::env::temp_dir();
|
||||
nu_dict.insert_value("temp-dir", UntaggedValue::path(temp).into_value(&tag));
|
||||
|
||||
let config = crate::data::config::default_path()?;
|
||||
nu_dict.insert_value("config-path", UntaggedValue::path(config).into_value(&tag));
|
||||
|
||||
let keybinding_path = crate::keybinding::keybinding_path()?;
|
||||
nu_dict.insert_value(
|
||||
"keybinding-path",
|
||||
UntaggedValue::path(keybinding_path).into_value(&tag),
|
||||
);
|
||||
|
||||
let history = History::path();
|
||||
nu_dict.insert_value(
|
||||
"history-path",
|
||||
UntaggedValue::path(history).into_value(&tag),
|
||||
);
|
||||
|
||||
Ok(nu_dict.into_value())
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::ClassifiedBlock;
|
||||
use nu_protocol::{ShellTypeName, Value};
|
||||
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::{whole_stream_command, BuildString, Echo, StrCollect};
|
||||
use crate::context::Context;
|
||||
use crate::stream::InputStream;
|
||||
use crate::WholeStreamCommand;
|
||||
|
||||
pub fn test(cmd: impl WholeStreamCommand + 'static) {
|
||||
let examples = cmd.examples();
|
||||
let mut base_context = Context::basic().expect("could not create basic context");
|
||||
|
||||
base_context.add_commands(vec![
|
||||
whole_stream_command(Echo {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(cmd),
|
||||
whole_stream_command(StrCollect),
|
||||
]);
|
||||
|
||||
for example in examples {
|
||||
let mut ctx = base_context.clone();
|
||||
let block = parse_line(example.example, &mut ctx).expect("failed to parse example");
|
||||
if let Some(expected) = example.result {
|
||||
let result = block_on(evaluate_block(block, &mut ctx)).expect("failed to run example");
|
||||
|
||||
let errors = ctx.get_errors();
|
||||
|
||||
assert!(
|
||||
errors.is_empty(),
|
||||
"errors while running command.\ncommand: {}\nerrors: {:?}",
|
||||
example.example,
|
||||
errors
|
||||
);
|
||||
|
||||
assert!(expected.len() == result.len(), "example command produced unexpected number of results.\ncommand: {}\nexpected number: {}\nactual: {}",
|
||||
example.example,
|
||||
expected.len(),
|
||||
result.len(),);
|
||||
|
||||
assert!(
|
||||
expected
|
||||
.iter()
|
||||
.zip(result.iter())
|
||||
.all(|(e, a)| values_equal(e, a)),
|
||||
"example command produced unexpected result.\ncommand: {}\nexpected: {:?}\nactual: {:?}",
|
||||
example.example,
|
||||
expected,
|
||||
result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and run a nushell pipeline
|
||||
fn parse_line(line: &'static str, ctx: &mut Context) -> Result<ClassifiedBlock, 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();
|
||||
Ok(classified_block)
|
||||
}
|
||||
|
||||
async fn evaluate_block(
|
||||
block: ClassifiedBlock,
|
||||
ctx: &mut Context,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
|
||||
Ok(run_block(
|
||||
&block.block,
|
||||
ctx,
|
||||
input_stream,
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await?
|
||||
.into_vec()
|
||||
.await)
|
||||
}
|
||||
|
||||
// TODO probably something already available to do this
|
||||
// TODO perhaps better panic messages when things don't compare
|
||||
|
||||
// Deep value comparisons that ignore tags
|
||||
fn values_equal(expected: &Value, actual: &Value) -> bool {
|
||||
use nu_protocol::UntaggedValue::*;
|
||||
|
||||
match (&expected.value, &actual.value) {
|
||||
(Primitive(e), Primitive(a)) => e == a,
|
||||
(Row(e), Row(a)) => {
|
||||
if e.entries.len() != a.entries.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
e.entries
|
||||
.iter()
|
||||
.zip(a.entries.iter())
|
||||
.all(|((ek, ev), (ak, av))| ek == ak && values_equal(ev, av))
|
||||
}
|
||||
(Table(e), Table(a)) => e.iter().zip(a.iter()).all(|(e, a)| values_equal(e, a)),
|
||||
(e, a) => unimplemented!("{} {}", e.type_name(), a.type_name()),
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn current_branch() -> Option<String> {
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
let use_starship = config
|
||||
.get("use_starship")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !use_starship {
|
||||
#[cfg(feature = "git2")]
|
||||
{
|
||||
use git2::{Repository, RepositoryOpenFlags};
|
||||
use std::ffi::OsString;
|
||||
|
||||
let v: Vec<OsString> = vec![];
|
||||
match Repository::open_ext(".", RepositoryOpenFlags::empty(), v) {
|
||||
Ok(repo) => {
|
||||
let r = repo.head();
|
||||
match r {
|
||||
Ok(r) => match r.shorthand() {
|
||||
Some(s) => Some(s.to_string()),
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "git2"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@ -405,14 +405,10 @@ pub struct Keybinding {
|
||||
|
||||
type Keybindings = Vec<Keybinding>;
|
||||
|
||||
pub(crate) fn keybinding_path() -> Result<std::path::PathBuf, nu_errors::ShellError> {
|
||||
crate::data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml")))
|
||||
}
|
||||
|
||||
pub(crate) fn load_keybindings(
|
||||
rl: &mut rustyline::Editor<crate::shell::Helper>,
|
||||
) -> Result<(), nu_errors::ShellError> {
|
||||
let filename = keybinding_path()?;
|
||||
let filename = nu_data::keybinding::keybinding_path()?;
|
||||
let contents = std::fs::read_to_string(filename);
|
||||
|
||||
// Silently fail if there is no file there
|
||||
|
@ -14,43 +14,28 @@ extern crate quickcheck;
|
||||
extern crate quickcheck_macros;
|
||||
|
||||
mod cli;
|
||||
mod commands;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod completion;
|
||||
mod context;
|
||||
pub mod data;
|
||||
mod deserializer;
|
||||
mod documentation;
|
||||
mod env;
|
||||
mod evaluate;
|
||||
mod format;
|
||||
mod futures;
|
||||
mod git;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
mod path;
|
||||
mod line_editor;
|
||||
mod shell;
|
||||
mod stream;
|
||||
pub mod utils;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod examples;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub use crate::cli::cli;
|
||||
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
|
||||
pub use crate::cli::{
|
||||
cli, create_default_context, load_plugins, parse_and_eval, process_line,
|
||||
run_pipeline_standalone, run_vec_of_pipelines, LineResult,
|
||||
};
|
||||
pub use crate::commands::command::{
|
||||
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,
|
||||
};
|
||||
pub use crate::commands::help::get_help;
|
||||
pub use crate::context::{CommandRegistry, Context};
|
||||
pub use crate::data::config;
|
||||
pub use crate::data::dict::TaggedListBuilder;
|
||||
pub use crate::data::primitive;
|
||||
pub use crate::data::value;
|
||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||
pub use crate::env::host::BasicHost;
|
||||
pub use crate::prelude::ToOutputStream;
|
||||
pub use crate::stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub use nu_command::commands::default_context::create_default_context;
|
||||
pub use nu_data::config;
|
||||
pub use nu_data::dict::TaggedListBuilder;
|
||||
pub use nu_data::primitive;
|
||||
pub use nu_data::value;
|
||||
pub use nu_stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub use nu_value_ext::ValueExt;
|
||||
pub use num_traits::cast::ToPrimitive;
|
||||
|
||||
|
234
crates/nu-cli/src/line_editor.rs
Normal file
234
crates/nu-cli/src/line_editor.rs
Normal file
@ -0,0 +1,234 @@
|
||||
use nu_engine::EvaluationContext;
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::prelude::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_command::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub 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,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// 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);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn nu_line_editor_helper(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn rustyline_hinter(
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -21,28 +21,6 @@ macro_rules! stream {
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_stream {
|
||||
(target: $target:tt, $desc:tt = $expr:expr) => {{
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
"{} = {}",
|
||||
$desc,
|
||||
nu_source::PrettyDebug::plain_string(o, 70)
|
||||
);
|
||||
});
|
||||
|
||||
$crate::stream::InputStream::from_stream(objects.boxed())
|
||||
} else {
|
||||
$expr
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_out_stream {
|
||||
(target: $target:tt, $desc:tt = $expr:expr) => {{
|
||||
@ -61,49 +39,24 @@ macro_rules! trace_out_stream {
|
||||
);
|
||||
});
|
||||
|
||||
$crate::stream::OutputStream::new(objects)
|
||||
nu_stream::OutputStream::new(objects)
|
||||
} else {
|
||||
$expr
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use nu_protocol::{errln, out, outln};
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
pub(crate) use crate::commands::command::{CommandArgs, RawCommandArgs, RunnableContext};
|
||||
pub(crate) use crate::commands::Example;
|
||||
pub(crate) use crate::context::CommandRegistry;
|
||||
pub(crate) use crate::context::Context;
|
||||
pub(crate) use crate::data::config;
|
||||
pub(crate) use crate::data::value;
|
||||
// pub(crate) use crate::env::host::handle_unexpected;
|
||||
pub(crate) use crate::env::Host;
|
||||
pub(crate) use crate::shell::filesystem_shell::FilesystemShell;
|
||||
pub(crate) use crate::shell::help_shell::HelpShell;
|
||||
pub(crate) use crate::shell::shell_manager::ShellManager;
|
||||
pub(crate) use crate::shell::value_shell::ValueShell;
|
||||
pub(crate) use crate::stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub(crate) use bigdecimal::BigDecimal;
|
||||
pub(crate) use futures::stream::BoxStream;
|
||||
pub(crate) use futures::{Stream, StreamExt};
|
||||
pub(crate) use nu_protocol::MaybeOwned;
|
||||
pub(crate) use nu_source::{
|
||||
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
|
||||
TaggedItem, Text,
|
||||
};
|
||||
pub(crate) use nu_engine::Host;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_errors::ShellError;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_protocol::outln;
|
||||
pub(crate) use nu_stream::OutputStream;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_value_ext::ValueExt;
|
||||
pub(crate) use num_bigint::BigInt;
|
||||
pub(crate) use num_traits::cast::ToPrimitive;
|
||||
pub(crate) use serde::Deserialize;
|
||||
pub(crate) use std::collections::VecDeque;
|
||||
pub(crate) use std::future::Future;
|
||||
pub(crate) use std::sync::atomic::AtomicBool;
|
||||
pub(crate) use std::sync::Arc;
|
||||
|
||||
pub(crate) use async_trait::async_trait;
|
||||
pub(crate) use indexmap::IndexMap;
|
||||
pub(crate) use itertools::Itertools;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use std::sync::atomic::Ordering;
|
||||
|
||||
pub trait FromInputStream {
|
||||
fn from_input_stream(self) -> OutputStream;
|
||||
@ -120,26 +73,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToInputStream {
|
||||
fn to_input_stream(self) -> InputStream;
|
||||
}
|
||||
|
||||
impl<T, U> ToInputStream for T
|
||||
where
|
||||
T: Stream<Item = U> + Send + 'static,
|
||||
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
|
||||
{
|
||||
fn to_input_stream(self) -> InputStream {
|
||||
InputStream::from_stream(self.map(|item| match item.into() {
|
||||
Ok(result) => result,
|
||||
Err(err) => match HasFallibleSpan::maybe_span(&err) {
|
||||
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
|
||||
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToOutputStream {
|
||||
fn to_output_stream(self) -> OutputStream;
|
||||
}
|
||||
@ -155,16 +88,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Interruptible<V> {
|
||||
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V>;
|
||||
}
|
||||
|
||||
impl<S, V> Interruptible<V> for S
|
||||
where
|
||||
S: Stream<Item = V> + Send + 'static,
|
||||
{
|
||||
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V> {
|
||||
InterruptibleStream::new(self, ctrl_c)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod completer;
|
||||
pub(crate) mod filesystem_shell;
|
||||
pub(crate) mod help_shell;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod helper;
|
||||
pub(crate) mod palette;
|
||||
pub(crate) mod shell;
|
||||
pub(crate) mod shell_manager;
|
||||
pub(crate) mod value_shell;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) use helper::Helper;
|
||||
|
@ -1,364 +1,157 @@
|
||||
use std::fs::{read_dir, DirEntry};
|
||||
use crate::completion::command::CommandCompleter;
|
||||
use crate::completion::flag::FlagCompleter;
|
||||
use crate::completion::matchers;
|
||||
use crate::completion::matchers::Matcher;
|
||||
use crate::completion::path::{PathCompleter, PathSuggestion};
|
||||
use crate::completion::{self, Completer, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use indexmap::set::IndexSet;
|
||||
use nu_errors::ShellError;
|
||||
use rustyline::completion::{Completer as _, FilenameCompleter};
|
||||
use rustyline::hint::{Hinter as _, HistoryHinter};
|
||||
pub(crate) struct NuCompleter {}
|
||||
|
||||
#[cfg(all(windows, feature = "ichwh"))]
|
||||
use ichwh::{IchwhError, IchwhResult};
|
||||
|
||||
use crate::completion::{self, Completer};
|
||||
use crate::context;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct NuCompleter {
|
||||
file_completer: FilenameCompleter,
|
||||
hinter: HistoryHinter,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum ReplacementLocation {
|
||||
Command,
|
||||
Other,
|
||||
}
|
||||
impl NuCompleter {}
|
||||
|
||||
impl NuCompleter {
|
||||
fn complete_internal(
|
||||
pub fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
context: &completion::Context,
|
||||
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
|
||||
let line_chars: Vec<_> = line[..pos].chars().collect();
|
||||
context: &completion::CompletionContext,
|
||||
) -> (usize, Vec<Suggestion>) {
|
||||
use completion::engine::LocationType;
|
||||
|
||||
let (replace_pos, replace_loc) = get_replace_pos(line, pos);
|
||||
let nu_context: &EvaluationContext = context.as_ref();
|
||||
|
||||
// See if we're a flag
|
||||
let mut completions;
|
||||
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
|
||||
completions = get_matching_arguments(
|
||||
context.as_ref(),
|
||||
&lite_block,
|
||||
&line_chars,
|
||||
line,
|
||||
replace_pos,
|
||||
pos,
|
||||
);
|
||||
} else {
|
||||
completions = self.file_completer.complete(line, pos, context.as_ref())?.1;
|
||||
}
|
||||
} else {
|
||||
completions = self.file_completer.complete(line, pos, context.as_ref())?.1;
|
||||
}
|
||||
nu_context.scope.enter_scope();
|
||||
let (block, _) = nu_parser::parse(line, 0, &nu_context.scope);
|
||||
nu_context.scope.exit_scope();
|
||||
|
||||
// Only complete executables or commands if the thing we're completing
|
||||
// is syntactically a command
|
||||
if replace_loc == ReplacementLocation::Command {
|
||||
let context: &context::Context = context.as_ref();
|
||||
let commands: Vec<String> = context.registry.names();
|
||||
let mut all_executables: IndexSet<_> = commands.iter().map(|x| x.to_string()).collect();
|
||||
let locations = completion::engine::completion_location(line, &block, pos);
|
||||
|
||||
let complete_from_path = config::config(Tag::unknown())
|
||||
.map(|conf| {
|
||||
conf.get("complete_from_path")
|
||||
.map(|v| v.is_true())
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.unwrap_or(true);
|
||||
|
||||
if complete_from_path {
|
||||
let path_executables = find_path_executables().unwrap_or_default();
|
||||
for path_exe in path_executables {
|
||||
all_executables.insert(path_exe);
|
||||
}
|
||||
};
|
||||
|
||||
for exe in all_executables.iter() {
|
||||
let mut pos = replace_pos;
|
||||
let mut matched = false;
|
||||
if pos < line_chars.len() {
|
||||
for chr in exe.chars() {
|
||||
if line_chars[pos] != chr {
|
||||
break;
|
||||
}
|
||||
|
||||
pos += 1;
|
||||
if pos == line_chars.len() {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
completions.push(rustyline::completion::Pair {
|
||||
display: exe.to_string(),
|
||||
replacement: exe.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust replacement to deal with a quote already at the cursor. Specifically, if there's
|
||||
// already a quote at the cursor, but the replacement doesn't have one, we need to ensure
|
||||
// one exists (to be safe, even if the completion doesn't need it).
|
||||
for completion in &mut completions {
|
||||
let cursor_char = line.chars().nth(replace_pos);
|
||||
if cursor_char.unwrap_or(' ') == '"' && !completion.replacement.starts_with('"') {
|
||||
completion.replacement.insert(0, '"');
|
||||
}
|
||||
}
|
||||
|
||||
Ok((replace_pos, completions))
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
context: &completion::Context,
|
||||
) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
|
||||
let expanded = nu_parser::expand_ndots(&line);
|
||||
|
||||
// Find the first not-matching char position, if there is one
|
||||
let differ_pos = line
|
||||
.chars()
|
||||
.zip(expanded.chars())
|
||||
.enumerate()
|
||||
.find(|(_index, (a, b))| a != b)
|
||||
.map(|(differ_pos, _)| differ_pos);
|
||||
|
||||
let pos = if let Some(differ_pos) = differ_pos {
|
||||
if differ_pos < pos {
|
||||
pos + (expanded.len() - line.len())
|
||||
} else {
|
||||
pos
|
||||
}
|
||||
} else {
|
||||
pos
|
||||
};
|
||||
|
||||
self.complete_internal(&expanded, pos, context)
|
||||
.map_err(|e| ShellError::untagged_runtime_error(format!("{}", e)))
|
||||
.map(requote)
|
||||
.map(|(pos, completions)| {
|
||||
(
|
||||
pos,
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|pair| completion::Suggestion {
|
||||
display: pair.display,
|
||||
replacement: pair.replacement,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
let matcher = nu_data::config::config(Tag::unknown())
|
||||
.ok()
|
||||
.and_then(|cfg| cfg.get("line_editor").cloned())
|
||||
.and_then(|le| {
|
||||
le.row_entries()
|
||||
.find(|(idx, _value)| idx.as_str() == "completion_match_method")
|
||||
.and_then(|(_idx, value)| value.as_string().ok())
|
||||
})
|
||||
}
|
||||
.unwrap_or_else(String::new);
|
||||
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &completion::Context<'_>) -> Option<String> {
|
||||
self.hinter.hint(line, pos, &ctx.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NuCompleter {
|
||||
fn default() -> NuCompleter {
|
||||
NuCompleter {
|
||||
file_completer: FilenameCompleter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_matching_arguments(
|
||||
context: &context::Context,
|
||||
lite_block: &nu_parser::LiteBlock,
|
||||
line_chars: &[char],
|
||||
line: &str,
|
||||
replace_pos: usize,
|
||||
pos: usize,
|
||||
) -> Vec<rustyline::completion::Pair> {
|
||||
let mut matching_arguments = vec![];
|
||||
|
||||
let mut line_copy = line.to_string();
|
||||
let substring = line_chars[replace_pos..pos].iter().collect::<String>();
|
||||
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
|
||||
line_copy.replace_range(replace_pos..pos, &replace_string);
|
||||
|
||||
let result = nu_parser::classify_block(&lite_block, &context.registry);
|
||||
|
||||
for pipeline in &result.block.block {
|
||||
for command in &pipeline.list {
|
||||
if let nu_protocol::hir::ClassifiedCommand::Internal(
|
||||
nu_protocol::hir::InternalCommand { args, .. },
|
||||
) = command
|
||||
{
|
||||
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
|
||||
if let Some(named) = &args.named {
|
||||
for (name, _) in named.iter() {
|
||||
let full_flag = format!("--{}", name);
|
||||
|
||||
if full_flag.starts_with(&substring) {
|
||||
matching_arguments.push(rustyline::completion::Pair {
|
||||
display: full_flag.clone(),
|
||||
replacement: full_flag,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matching_arguments
|
||||
}
|
||||
|
||||
// These is_executable/pathext implementations are copied from ichwh and modified
|
||||
// to not be async
|
||||
|
||||
#[cfg(windows)]
|
||||
fn pathext() -> IchwhResult<Vec<String>> {
|
||||
Ok(std::env::var_os("PATHEXT")
|
||||
.ok_or(IchwhError::PathextNotDefined)?
|
||||
.to_string_lossy()
|
||||
.split(';')
|
||||
// Cut off the leading '.' character
|
||||
.map(|ext| ext[1..].to_string())
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_executable(file: &DirEntry) -> bool {
|
||||
if let Ok(metadata) = file.metadata() {
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
// If the entry isn't a file, it cannot be executable
|
||||
if !(file_type.is_file() || file_type.is_symlink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(extension) = file.path().extension() {
|
||||
if let Ok(exts) = pathext() {
|
||||
exts.iter()
|
||||
.any(|ext| extension.to_string_lossy().eq_ignore_ascii_case(ext))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn is_executable(file: &DirEntry) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_executable(file: &DirEntry) -> bool {
|
||||
let metadata = file.metadata();
|
||||
|
||||
if let Ok(metadata) = metadata {
|
||||
let filetype = metadata.file_type();
|
||||
let permissions = metadata.permissions();
|
||||
|
||||
// The file is executable if it is a directory or a symlink and the permissions are set for
|
||||
// owner, group, or other
|
||||
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn find_path_executables() -> Option<IndexSet<String>> {
|
||||
let path_var = std::env::var_os("PATH")?;
|
||||
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
|
||||
|
||||
let mut executables: IndexSet<String> = IndexSet::new();
|
||||
for path in paths {
|
||||
if let Ok(mut contents) = read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if is_executable(&item) {
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(executables)
|
||||
}
|
||||
|
||||
fn get_replace_pos(line: &str, pos: usize) -> (usize, ReplacementLocation) {
|
||||
let line_chars: Vec<_> = line[..pos].chars().collect();
|
||||
let mut replace_pos = line_chars.len();
|
||||
let mut parsed_pos = false;
|
||||
let mut loc = ReplacementLocation::Other;
|
||||
if let Ok(lite_block) = nu_parser::lite_parse(line, 0) {
|
||||
'outer: for pipeline in lite_block.block.iter() {
|
||||
for command in pipeline.commands.iter() {
|
||||
let name_span = command.name.span;
|
||||
if name_span.start() <= pos && name_span.end() >= pos {
|
||||
replace_pos = name_span.start();
|
||||
parsed_pos = true;
|
||||
loc = ReplacementLocation::Command;
|
||||
break 'outer;
|
||||
}
|
||||
|
||||
for arg in command.args.iter() {
|
||||
if arg.span.start() <= pos && arg.span.end() >= pos {
|
||||
replace_pos = arg.span.start();
|
||||
parsed_pos = true;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !parsed_pos {
|
||||
// If the command won't parse, naively detect the completion start point
|
||||
while replace_pos > 0 {
|
||||
if line_chars[replace_pos - 1] == ' ' {
|
||||
break;
|
||||
}
|
||||
replace_pos -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
(replace_pos, loc)
|
||||
}
|
||||
|
||||
fn requote(
|
||||
items: (usize, Vec<rustyline::completion::Pair>),
|
||||
) -> (usize, Vec<rustyline::completion::Pair>) {
|
||||
let mut new_items = Vec::with_capacity(items.1.len());
|
||||
|
||||
for item in items.1 {
|
||||
let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\'));
|
||||
let maybe_quote = if unescaped != item.replacement {
|
||||
"\""
|
||||
} else {
|
||||
""
|
||||
let matcher = matcher.as_str();
|
||||
let matcher: &dyn Matcher = match matcher {
|
||||
"case-insensitive" => &matchers::case_insensitive::Matcher,
|
||||
_ => &matchers::case_sensitive::Matcher,
|
||||
};
|
||||
|
||||
new_items.push(rustyline::completion::Pair {
|
||||
display: item.display,
|
||||
replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote),
|
||||
});
|
||||
if locations.is_empty() {
|
||||
(pos, Vec::new())
|
||||
} else {
|
||||
let pos = locations[0].span.start();
|
||||
let suggestions = locations
|
||||
.into_iter()
|
||||
.flat_map(|location| {
|
||||
let partial = location.span.slice(line);
|
||||
match location.item {
|
||||
LocationType::Command => {
|
||||
let command_completer = CommandCompleter;
|
||||
command_completer.complete(context, partial, matcher.to_owned())
|
||||
}
|
||||
|
||||
LocationType::Flag(cmd) => {
|
||||
let flag_completer = FlagCompleter { cmd };
|
||||
flag_completer.complete(context, partial, matcher.to_owned())
|
||||
}
|
||||
|
||||
LocationType::Argument(cmd, _arg_name) => {
|
||||
let path_completer = PathCompleter;
|
||||
|
||||
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
|
||||
|
||||
// TODO Find a better way to deal with quote chars. Can the completion
|
||||
// engine relay this back to us? Maybe have two spans: inner and
|
||||
// outer. The former is what we want to complete, the latter what
|
||||
// we'd need to replace.
|
||||
let (quote_char, partial) = if partial.starts_with(QUOTE_CHARS) {
|
||||
let (head, tail) = partial.split_at(1);
|
||||
(Some(head), tail)
|
||||
} else {
|
||||
(None, partial)
|
||||
};
|
||||
|
||||
let partial = if let Some(quote_char) = quote_char {
|
||||
if partial.ends_with(quote_char) {
|
||||
&partial[..partial.len() - 1]
|
||||
} else {
|
||||
partial
|
||||
}
|
||||
} else {
|
||||
partial
|
||||
};
|
||||
|
||||
let completed_paths = path_completer.path_suggestions(partial, matcher);
|
||||
match cmd.as_deref().unwrap_or("") {
|
||||
"cd" => select_directory_suggestions(completed_paths),
|
||||
_ => completed_paths,
|
||||
}
|
||||
.into_iter()
|
||||
.map(|s| Suggestion {
|
||||
replacement: requote(s.suggestion.replacement),
|
||||
display: s.suggestion.display,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
LocationType::Variable => Vec::new(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
(pos, suggestions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_directory_suggestions(completed_paths: Vec<PathSuggestion>) -> Vec<PathSuggestion> {
|
||||
completed_paths
|
||||
.into_iter()
|
||||
.filter(|suggestion| {
|
||||
suggestion
|
||||
.path
|
||||
.metadata()
|
||||
.map(|md| md.is_dir())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn requote(orig_value: String) -> String {
|
||||
let value: Cow<str> = rustyline::completion::unescape(&orig_value, Some('\\'));
|
||||
|
||||
let mut quotes = vec!['"', '\'', '`'];
|
||||
let mut should_quote = false;
|
||||
for c in value.chars() {
|
||||
if c.is_whitespace() {
|
||||
should_quote = true;
|
||||
} else if let Some(index) = quotes.iter().position(|q| *q == c) {
|
||||
should_quote = true;
|
||||
quotes.swap_remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
(items.0, new_items)
|
||||
if should_quote {
|
||||
if quotes.is_empty() {
|
||||
// TODO we don't really have an escape character, so there isn't a great option right
|
||||
// now. One possibility is `{{$(char backtick)}}`
|
||||
value.to_string()
|
||||
} else {
|
||||
let quote = quotes[0];
|
||||
format!("{}{}{}", quote, value, quote)
|
||||
}
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,28 @@
|
||||
use crate::completion::{self, Completer};
|
||||
use crate::context::Context;
|
||||
use crate::shell::palette::{DefaultPalette, Palette};
|
||||
|
||||
use ansi_term::{Color, Style};
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::FlatShape;
|
||||
use nu_source::{Spanned, Tag, Tagged};
|
||||
use rustyline::hint::Hinter;
|
||||
use crate::completion;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub struct Helper {
|
||||
completer: Box<dyn Completer>,
|
||||
context: Context,
|
||||
completer: NuCompleter,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
context: EvaluationContext,
|
||||
pub colored_prompt: String,
|
||||
validator: NuValidator,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub(crate) fn new(completer: Box<dyn Completer>, context: Context) -> Helper {
|
||||
pub(crate) fn new(
|
||||
context: EvaluationContext,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
) -> Helper {
|
||||
Helper {
|
||||
completer,
|
||||
completer: NuCompleter {},
|
||||
hinter,
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
validator: NuValidator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,19 +44,21 @@ impl rustyline::completion::Completer for Helper {
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &rustyline::Context<'_>,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = completion::Context::new(&self.context, ctx);
|
||||
self.completer
|
||||
.complete(line, pos, &ctx)
|
||||
.map_err(|_| rustyline::error::ReadlineError::Eof)
|
||||
let ctx = completion::CompletionContext::new(&self.context);
|
||||
Ok(self.completer.complete(line, pos, &ctx))
|
||||
}
|
||||
|
||||
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
||||
let end = line.pos();
|
||||
line.replace(start..end, elected)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hinter for Helper {
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
let ctx = completion::Context::new(&self.context, ctx);
|
||||
self.completer.hint(line, pos, &ctx)
|
||||
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,11 +82,7 @@ impl rustyline::highlight::Highlighter for Helper {
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
Painter::paint_string(
|
||||
line,
|
||||
&self.context.registry().clone_box(),
|
||||
&DefaultPalette {},
|
||||
)
|
||||
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
@ -90,6 +90,47 @@ impl rustyline::highlight::Highlighter for Helper {
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::validate::Validator for Helper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct NuValidator {}
|
||||
|
||||
impl rustyline::validate::Validator for NuValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let src = ctx.input();
|
||||
|
||||
let (tokens, err) = nu_parser::lex(src, 0);
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustyline::validate::ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
@ -102,94 +143,52 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Painter {
|
||||
original: Vec<u8>,
|
||||
styles: Vec<Style>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
fn new(original: &str) -> Painter {
|
||||
let bytes: Vec<u8> = original.bytes().collect();
|
||||
let bytes_count = bytes.len();
|
||||
Painter {
|
||||
original: bytes,
|
||||
styles: vec![Color::White.normal(); bytes_count],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint_string<'l, P: Palette>(
|
||||
line: &'l str,
|
||||
registry: &dyn SignatureRegistry,
|
||||
palette: &P,
|
||||
) -> Cow<'l, str> {
|
||||
let lite_block = nu_parser::lite_parse(line, 0);
|
||||
|
||||
match lite_block {
|
||||
Err(_) => Cow::Borrowed(line),
|
||||
Ok(lb) => {
|
||||
let classified = nu_parser::classify_block(&lb, registry);
|
||||
|
||||
let shapes = nu_parser::shapes(&classified.block);
|
||||
let mut painter = Painter::new(line);
|
||||
|
||||
for shape in shapes {
|
||||
painter.paint_shape(&shape, palette);
|
||||
}
|
||||
|
||||
Cow::Owned(painter.into_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_shape<P: Palette>(&mut self, shape: &Spanned<FlatShape>, palette: &P) {
|
||||
palette
|
||||
.styles_for_shape(shape)
|
||||
.iter()
|
||||
.for_each(|x| self.paint(x));
|
||||
}
|
||||
|
||||
fn paint(&mut self, styled_span: &Spanned<Style>) {
|
||||
for pos in styled_span.span.start()..styled_span.span.end() {
|
||||
self.styles[pos] = styled_span.item;
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self) -> String {
|
||||
let mut idx_start = 0;
|
||||
let mut idx_end = 1;
|
||||
|
||||
if self.original.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let mut builder = String::new();
|
||||
|
||||
let mut current_style = self.styles[0];
|
||||
|
||||
while idx_end < self.styles.len() {
|
||||
if self.styles[idx_end] != current_style {
|
||||
// Emit, as we changed styles
|
||||
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
|
||||
|
||||
builder.push_str(&format!("{}", current_style.paint(intermediate)));
|
||||
|
||||
current_style = self.styles[idx_end];
|
||||
idx_start = idx_end;
|
||||
idx_end += 1;
|
||||
} else {
|
||||
idx_end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let intermediate = String::from_utf8_lossy(&self.original[idx_start..idx_end]);
|
||||
builder.push_str(&format!("{}", current_style.paint(intermediate)));
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
// Use default validator for normal single line behaviour
|
||||
// In the future we can implement this for custom multi-line support
|
||||
impl rustyline::validate::Validator for Helper {}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::basic_evaluation_context;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
1
crates/nu-cli/src/types.rs
Normal file
1
crates/nu-cli/src/types.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod deduction;
|
1095
crates/nu-cli/src/types/deduction.rs
Normal file
1095
crates/nu-cli/src/types/deduction.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
||||
#[test]
|
||||
fn alias_args_work() {
|
||||
Playground::setup("append_test_1", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.root(),
|
||||
r#"
|
||||
alias double_echo [a b] {echo $a $b}
|
||||
double_echo 1 2 | to json
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "[1,2]");
|
||||
})
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn each_works_separately() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[11,12,13]");
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn echo_range_is_lazy() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo 1..10000000000 | first 3 | echo $it | to json
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[1,2,3]");
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn insert_plugin() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| insert dev-dependencies.newdep "1"
|
||||
| get dev-dependencies.newdep
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn downcase_upcase() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo abcd | wrap downcase | insert upcase { echo $it.downcase | str upcase } | format "{downcase}{upcase}"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "abcdABCD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_and_its_negative_equal_zero() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo 1..10 | wrap num | insert neg { = $it.num * -1 } | math sum | = $it.num + $it.neg
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0");
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn adds_value_provided_if_column_is_empty() {
|
||||
Playground::setup("is_empty_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"likes.csv",
|
||||
r#"
|
||||
first_name,last_name,rusty_at,likes
|
||||
Andrés,Robalino,10/11/2013,1
|
||||
Jonathan,Turner,10/12/2013,1
|
||||
Jason,Gedge,10/11/2013,1
|
||||
Yehuda,Katz,10/11/2013,
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open likes.csv
|
||||
| empty? likes 1
|
||||
| get likes
|
||||
| math sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "4");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_value_provided_for_columns_that_are_empty() {
|
||||
Playground::setup("is_empty_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"checks.json",
|
||||
r#"
|
||||
[
|
||||
{"boost": 1, "check": []},
|
||||
{"boost": 1, "check": ""},
|
||||
{"boost": 1, "check": {}},
|
||||
{"boost": null, "check": ["" {} [] ""]}
|
||||
]
|
||||
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open checks.json
|
||||
| empty? boost check 1
|
||||
| get boost check
|
||||
| math sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "8");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_emptiness_check() {
|
||||
Playground::setup("is_empty_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"checks.json",
|
||||
r#"
|
||||
{
|
||||
"are_empty": [
|
||||
{"check": []},
|
||||
{"check": ""},
|
||||
{"check": {}},
|
||||
{"check": ["" {} [] ""]}
|
||||
]
|
||||
}
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open checks.json
|
||||
| get are_empty.check
|
||||
| empty?
|
||||
| where $it
|
||||
| count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "4");
|
||||
})
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn string() {
|
||||
Playground::setup("trim_test_1", |dirs, _sandbox| {
|
||||
let test_strings = ["\n", " \n ", "\thi\n\n", "\u{2003}a"];
|
||||
assert!(test_strings[3].chars().count() == 2);
|
||||
|
||||
for test_string in &test_strings {
|
||||
let commandline = format!(
|
||||
r#"
|
||||
echo {}
|
||||
| trim
|
||||
"#,
|
||||
test_string
|
||||
);
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(&commandline
|
||||
));
|
||||
assert_eq!(actual.out, test_string.trim())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row() {
|
||||
Playground::setup("trim_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContent("lines.csv", "lines\n l0\n\tl1\n l2\t \n\n"),
|
||||
FileWithContent("lines_trimmed.csv", "lines\nl0\nl1\nl2\n"),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open lines.csv
|
||||
| trim
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open lines_trimmed.csv
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, expected.out)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
Playground::setup("trim_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContent(
|
||||
"nested.json",
|
||||
r#"{ "l0" : {"l1": {"l2" : {"a" : " s0", "b" : "\t\ts1\n"} } } }"#,
|
||||
),
|
||||
FileWithContent(
|
||||
"nested_trimmed.json",
|
||||
r#"{ "l0" : {"l1": {"l2" : {"a" : "s0", "b" : "s1"} } } }"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open nested.json
|
||||
| trim
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open nested_trimmed.json
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, expected.out)
|
||||
})
|
||||
}
|
132
crates/nu-command/Cargo.toml
Normal file
132
crates/nu-command/Cargo.toml
Normal file
@ -0,0 +1,132 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
build = "build.rs"
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.26.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = { version = "0.26.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.26.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.26.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.26.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.26.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.26.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.26.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.26.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.26.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.26.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.26.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.26.0", 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.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.11.0"
|
||||
csv = "1.1.3"
|
||||
ctrlc = { version = "3.1.6", optional = true }
|
||||
derive-new = "0.5.8"
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
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"] }
|
||||
futures-util = "0.3.8"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
glob = "0.3.0"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
ichwh = { version = "0.3.4", optional = true }
|
||||
indexmap = { version = "1.6.0", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.11"
|
||||
meval = "0.2.0"
|
||||
num-bigint = { version = "0.3.0", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-traits = "0.2.12"
|
||||
parking_lot = "0.11.0"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = { version = "0.3.0", optional = true }
|
||||
query_interface = "0.3.5"
|
||||
quick-xml = "0.20.0"
|
||||
rand = "0.7.3"
|
||||
rayon = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.8.0"
|
||||
rustyline = { version = "6.3.0", optional = true }
|
||||
serde = { version = "1.0.115", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.57"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
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"
|
||||
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 }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
# (currently on nightly behind -Zfeatures=itarget):
|
||||
# https://github.com/rust-lang/cargo/issues/7914
|
||||
#[target.'cfg(not(windows))'.dependencies]
|
||||
#num-format = {version = "0.4", features = ["with-system-locale"]}
|
||||
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.2"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
directories = ["directories-next"]
|
||||
dirs = ["dirs-next"]
|
BIN
crates/nu-command/assets/228_themes.zip
Normal file
BIN
crates/nu-command/assets/228_themes.zip
Normal file
Binary file not shown.
3
crates/nu-command/build.rs
Normal file
3
crates/nu-command/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
@ -4,11 +4,10 @@ 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;
|
||||
pub(crate) mod autoenv;
|
||||
pub mod autoenv;
|
||||
pub(crate) mod autoenv_trust;
|
||||
pub(crate) mod autoenv_untrust;
|
||||
pub(crate) mod autoview;
|
||||
@ -17,10 +16,11 @@ 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")]
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod command;
|
||||
pub mod command;
|
||||
pub(crate) mod compact;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod constants;
|
||||
@ -28,16 +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 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;
|
||||
@ -57,15 +63,18 @@ 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;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod into_int;
|
||||
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;
|
||||
@ -74,11 +83,11 @@ pub(crate) mod mkdir;
|
||||
pub(crate) mod move_;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod nu;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod pivot;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
@ -89,15 +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_;
|
||||
@ -113,12 +125,10 @@ pub(crate) mod to_tsv;
|
||||
pub(crate) mod to_url;
|
||||
pub(crate) mod to_xml;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod trim;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod url_;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod what;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
@ -126,13 +136,9 @@ pub(crate) mod wrap;
|
||||
|
||||
pub(crate) use autoview::Autoview;
|
||||
pub(crate) use cd::Cd;
|
||||
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;
|
||||
@ -140,23 +146,29 @@ 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;
|
||||
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;
|
||||
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 update::Update;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
@ -164,9 +176,11 @@ pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use enter::Enter;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use format::Format;
|
||||
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;
|
||||
@ -184,36 +198,45 @@ 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, 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;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use path::{PathBasename, PathCommand, PathExists, PathExpand, PathExtension, PathType};
|
||||
pub(crate) use path::{
|
||||
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
|
||||
PathType,
|
||||
};
|
||||
pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{Random, RandomBool, RandomDice};
|
||||
pub(crate) use random::{
|
||||
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
|
||||
};
|
||||
pub(crate) use range::Range;
|
||||
pub(crate) use reduce::Reduce;
|
||||
pub(crate) use reject::Reject;
|
||||
@ -223,16 +246,21 @@ 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, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith, StrFindReplace, StrFrom,
|
||||
StrIndexOf, StrLength, StrReverse, StrSet, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||
StrReverse, StrScreamingSnakeCase, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
@ -248,12 +276,81 @@ pub(crate) use to_url::ToURL;
|
||||
pub(crate) use to_xml::ToXML;
|
||||
pub(crate) use to_yaml::ToYAML;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use trim::Trim;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
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::examples::{test_anchors, test_examples};
|
||||
use nu_engine::{whole_stream_command, Command};
|
||||
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),
|
||||
// Str Command Suite
|
||||
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(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),
|
||||
]
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
291
crates/nu-command/src/commands/ansi.rs
Normal file
291
crates/nu-command/src/commands/ansi.rs
Normal file
@ -0,0 +1,291 @@
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
code: Value,
|
||||
escape: Option<Tagged<String>>,
|
||||
osc: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi")
|
||||
.optional(
|
||||
"code",
|
||||
SyntaxShape::Any,
|
||||
"the name of the code to use like 'green' or 'reset' to reset the color",
|
||||
)
|
||||
.named(
|
||||
"escape", // \x1b[
|
||||
SyntaxShape::Any,
|
||||
"escape sequence without the escape character(s)",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"osc", // \x1b]
|
||||
SyntaxShape::Any,
|
||||
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
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> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\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) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { code, 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 code_string = code.as_string()?;
|
||||
let ansi_code = str_to_ansi(code_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(code.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown ansi code",
|
||||
"unknown ansi code",
|
||||
code.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_ansi(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
|
||||
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
|
||||
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
|
||||
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
// Ansi Erase Sequences
|
||||
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
|
||||
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
|
||||
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
|
||||
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
|
||||
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||
|
||||
// Turn on/off cursor
|
||||
"cursor_off" => Some("\x1b[?25l".to_string()),
|
||||
"cursor_on" => Some("\x1b[?25h".to_string()),
|
||||
|
||||
// Turn on/off blinking
|
||||
"cursor_blink_off" => Some("\x1b[?12l".to_string()),
|
||||
"cursor_blink_on" => Some("\x1b[?12h".to_string()),
|
||||
|
||||
// Cursor position in ESC [ <r>;<c>R where r = row and c = column
|
||||
"cursor_position" => Some("\x1b[6n".to_string()),
|
||||
|
||||
// Report Terminal Identity
|
||||
"identity" => Some("\x1b[0c".to_string()),
|
||||
|
||||
// Ansi escape only - CSI command
|
||||
"csi" | "escape" | "escape_left" => Some("\x1b[".to_string()),
|
||||
// OSC escape (Operating system command)
|
||||
"osc" | "escape_right" => Some("\x1b]".to_string()),
|
||||
|
||||
// Ansi RGB - Needs to be 32;2;r;g;b or 48;2;r;g;b
|
||||
// assuming the rgb will be passed via command and no here
|
||||
"rgb_fg" => Some("\x1b[32;2;".to_string()),
|
||||
"rgb_bg" => Some("\x1b[48;2;".to_string()),
|
||||
|
||||
// Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255
|
||||
"idx_fg" | "color_idx_fg" => Some("\x1b[38;5;".to_string()),
|
||||
"idx_bg" | "color_idx_bg" => Some("\x1b[48;5;".to_string()),
|
||||
|
||||
// Returns terminal size like "[<r>;<c>R" where r is rows and c is columns
|
||||
// This should work assuming your terminal is not greater than 999x999
|
||||
"size" => Some("\x1b[s\x1b[999;999H\x1b[6n\x1b[u".to_string()),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Ansi {})?)
|
||||
}
|
||||
}
|
85
crates/nu-command/src/commands/append.rs
Normal file
85
crates/nu-command/src/commands/append.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
value: Value,
|
||||
}
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("append").required(
|
||||
"row value",
|
||||
SyntaxShape::Any,
|
||||
"the value of the row to append to the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append a row to the table"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { mut value }, input) = args.process().await?;
|
||||
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
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> {
|
||||
use nu_protocol::row;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Add values to the end of the table",
|
||||
example: "echo [1 2 3] | append 4",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(1).into(),
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Add row value to the end of the table",
|
||||
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||
result: Some(vec![
|
||||
row! { "country".into() => Value::from("Ecuador")},
|
||||
row! { "country".into() => Value::from("New Zealand")},
|
||||
row! { "country".into() => Value::from("USA")},
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use serde::Deserialize;
|
||||
@ -55,21 +55,15 @@ impl WholeStreamCommand for Autoenv {
|
||||
The file can contain several optional sections:
|
||||
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
|
||||
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
|
||||
scripts: scripts to run when entering the directory or leaving it. Note that exitscripts are not run in the directory they are declared in."#
|
||||
scripts: scripts to run when entering the directory or leaving it."#
|
||||
}
|
||||
|
||||
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))
|
||||
.into_value(Tag::unknown()),
|
||||
UntaggedValue::string(get_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
||||
@ -77,15 +71,15 @@ The file can contain several optional sections:
|
||||
vec![Example {
|
||||
description: "Example .nu-env file",
|
||||
example: r#"cat .nu-env
|
||||
[env]
|
||||
mykey = "myvalue"
|
||||
[env]
|
||||
mykey = "myvalue"
|
||||
|
||||
[scriptvars]
|
||||
myscript = "echo myval"
|
||||
[scriptvars]
|
||||
myscript = "echo myval"
|
||||
|
||||
[scripts]
|
||||
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||
exitscripts = ["touch bye.txt"]"#,
|
||||
[scripts]
|
||||
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||
exitscripts = ["touch bye.txt"]"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user