mirror of
https://github.com/nushell/nushell.git
synced 2025-07-04 00:21:35 +02:00
Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
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
|
||||
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
|
||||
|
@ -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
|
||||
|
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\
|
||||
|
@ -25,38 +25,38 @@ 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:
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
```
|
||||
|
||||
Run all tests for a specific command
|
||||
- Run all tests for a specific command
|
||||
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
```shell
|
||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||
```
|
||||
|
||||
Check to see if there are code formatting issues
|
||||
- Check to see if there are code formatting issues
|
||||
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
```shell
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
Format the code in the project
|
||||
- Format the code in the project
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
2023
Cargo.lock
generated
2023
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
100
Cargo.toml
100
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.19.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,32 +18,33 @@ 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.19.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.19.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.19.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.19.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.19.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.19.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.19.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.19.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_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.19.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.19.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.19.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.19.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.19.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.19.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.19.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.19.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.19.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.19.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.19.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.19.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.19.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.19.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
|
||||
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"
|
||||
@ -53,10 +54,9 @@ futures = {version = "0.3", features = ["compat", "io-compat"]}
|
||||
log = "0.4.8"
|
||||
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"}
|
||||
nu-test-support = {version = "0.19.0", path = "./crates/nu-test-support"}
|
||||
|
||||
[build-dependencies]
|
||||
serde = {version = "1.0.110", features = ["derive"]}
|
||||
@ -75,14 +75,18 @@ default = [
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
]
|
||||
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"]
|
||||
stable = ["default"]
|
||||
|
||||
# Default
|
||||
inc = ["semver", "nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["crossterm", "syntect", "url", "nu_plugin_textview"]
|
||||
textview = ["crossterm", "url", "nu_plugin_textview"]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
@ -90,6 +94,7 @@ bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
@ -97,10 +102,9 @@ tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/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"]
|
||||
@ -129,37 +133,43 @@ 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"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
@ -67,7 +67,7 @@ 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:
|
||||
|
||||
```bash
|
||||
cargo build --workspace --features=stable
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
@ -4,20 +4,21 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.18.1"
|
||||
version = "0.19.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-data = {version = "0.19.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.19.0", path = "../nu-errors"}
|
||||
nu-parser = {version = "0.19.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.19.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.19.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.19.0", path = "../nu-source"}
|
||||
nu-table = {version = "0.19.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.19.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.19.0", path = "../nu-value-ext"}
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
app_dirs = {version = "2", package = "app_dirs2"}
|
||||
@ -89,15 +90,15 @@ 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"
|
||||
zip = {version = "0.5.6", optional = true}
|
||||
|
||||
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"}
|
||||
Inflector = "0.11"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
@ -114,6 +115,8 @@ optional = true
|
||||
version = "0.23.1"
|
||||
|
||||
[build-dependencies]
|
||||
git2 = {version = "0.13", optional = true}
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
||||
@ -122,5 +125,4 @@ quickcheck_macros = "0.9"
|
||||
[features]
|
||||
clipboard-cli = ["clipboard"]
|
||||
stable = []
|
||||
starship-prompt = ["starship"]
|
||||
trash-support = ["trash"]
|
||||
|
36
crates/nu-cli/build.rs
Normal file
36
crates/nu-cli/build.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::path::Path;
|
||||
use std::{env, fs, io};
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
let out_dir = env::var_os("OUT_DIR").expect(
|
||||
"\
|
||||
OUT_DIR environment variable not found. \
|
||||
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
|
||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
||||
");
|
||||
|
||||
let latest_commit_hash = latest_commit_hash(env::current_dir()?).unwrap_or_default();
|
||||
|
||||
let commit_hash_path = Path::new(&out_dir).join("git_commit_hash");
|
||||
fs::write(commit_hash_path, latest_commit_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn latest_commit_hash<P: AsRef<Path>>(dir: P) -> Result<String, Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "git2")]
|
||||
{
|
||||
use git2::Repository;
|
||||
let dir = dir.as_ref();
|
||||
Ok(Repository::discover(dir)?
|
||||
.head()?
|
||||
.peel_to_commit()?
|
||||
.id()
|
||||
.to_string())
|
||||
}
|
||||
#[cfg(not(feature = "git2"))]
|
||||
{
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
@ -1,105 +1,39 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::commands::plugin::JsonRpc;
|
||||
use crate::commands::plugin::{PluginCommand, PluginSink};
|
||||
use crate::commands::whole_stream_command;
|
||||
use crate::context::Context;
|
||||
use crate::git::current_branch;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
#[allow(unused)]
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
||||
use std::error::Error;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use rayon::prelude::*;
|
||||
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan() {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request = JsonRpc::new("config", Vec::<Value>::new());
|
||||
let request_raw = serde_json::to_string(&request)?;
|
||||
stdin.write_all(format!("{}\n", request_raw).as_bytes())?;
|
||||
let path = dunce::canonicalize(path)?;
|
||||
|
||||
let mut input = String::new();
|
||||
let result = match reader.read_line(&mut input) {
|
||||
Ok(count) => {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> config response");
|
||||
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
|
||||
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
|
||||
|
||||
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
|
||||
match response {
|
||||
Ok(jrpc) => match jrpc.params {
|
||||
Ok(params) => {
|
||||
let fname = path.to_string_lossy();
|
||||
|
||||
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
|
||||
|
||||
let name = params.name.clone();
|
||||
let fname = fname.to_string();
|
||||
|
||||
if context.get_command(&name).is_some() {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
|
||||
} else if params.is_filter {
|
||||
context.add_commands(vec![whole_stream_command(PluginCommand::new(
|
||||
name, fname, params,
|
||||
))]);
|
||||
} else {
|
||||
context.add_commands(vec![whole_stream_command(PluginSink::new(
|
||||
name, fname, params,
|
||||
))]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
|
||||
Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error: {:?}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Err(ShellError::untagged_runtime_error(format!(
|
||||
"Error: {:?}",
|
||||
e
|
||||
))),
|
||||
};
|
||||
|
||||
let _ = child.wait();
|
||||
|
||||
result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
let mut search_paths = Vec::new();
|
||||
@ -111,7 +45,7 @@ fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(plugin_dirs) = config.get("plugin_dirs") {
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
@ -130,120 +64,6 @@ fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
let opts = glob::MatchOptions {
|
||||
case_sensitive: false,
|
||||
require_literal_separator: false,
|
||||
require_literal_leading_dot: false,
|
||||
};
|
||||
|
||||
for path in search_paths() {
|
||||
let mut pattern = path.to_path_buf();
|
||||
|
||||
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
|
||||
|
||||
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
|
||||
.filter_map(|x| x.ok())
|
||||
.collect();
|
||||
|
||||
let _failures: Vec<_> = plugs
|
||||
.par_iter()
|
||||
.map(|path| {
|
||||
let bin_name = {
|
||||
if let Some(name) = path.file_name() {
|
||||
name.to_str().unwrap_or("")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
};
|
||||
|
||||
let is_valid_name = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
bin_name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
}
|
||||
};
|
||||
|
||||
let is_executable = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bin_name.ends_with(".exe") || bin_name.ends_with(".bat")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if is_valid_name && is_executable {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
|
||||
|
||||
// we are ok if this plugin load fails
|
||||
let _ = load_plugin(&path, &mut context.clone());
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct History;
|
||||
|
||||
impl History {
|
||||
pub fn path() -> PathBuf {
|
||||
const FNAME: &str = "history.txt";
|
||||
let default = config::user_data()
|
||||
.map(|mut p| {
|
||||
p.push(FNAME);
|
||||
p
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(FNAME));
|
||||
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
if let Ok(c) = cfg {
|
||||
match &c.get("history-path") {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(p),
|
||||
..
|
||||
}) => match p {
|
||||
Primitive::String(path) => PathBuf::from(path),
|
||||
_ => default,
|
||||
},
|
||||
_ => default,
|
||||
}
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn create_default_starship_config() -> Option<toml::Value> {
|
||||
let mut map = toml::value::Table::new();
|
||||
map.insert("add_newline".into(), toml::Value::Boolean(false));
|
||||
|
||||
let mut git_branch = toml::value::Table::new();
|
||||
git_branch.insert("symbol".into(), toml::Value::String("📙 ".into()));
|
||||
map.insert("git_branch".into(), toml::Value::Table(git_branch));
|
||||
|
||||
let mut git_status = toml::value::Table::new();
|
||||
git_status.insert("disabled".into(), toml::Value::Boolean(true));
|
||||
map.insert("git_status".into(), toml::Value::Table(git_status));
|
||||
|
||||
Some(toml::Value::Table(map))
|
||||
}
|
||||
|
||||
pub fn create_default_context(
|
||||
syncer: &mut crate::EnvironmentSyncer,
|
||||
interactive: bool,
|
||||
@ -279,6 +99,9 @@ pub fn create_default_context(
|
||||
whole_stream_command(Touch),
|
||||
whole_stream_command(Cpy),
|
||||
whole_stream_command(Date),
|
||||
whole_stream_command(DateNow),
|
||||
whole_stream_command(DateUTC),
|
||||
whole_stream_command(DateFormat),
|
||||
whole_stream_command(Cal),
|
||||
whole_stream_command(Mkdir),
|
||||
whole_stream_command(Mv),
|
||||
@ -291,6 +114,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Alias),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
whole_stream_command(Sleep),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
@ -336,6 +160,11 @@ pub fn create_default_context(
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(StrLength),
|
||||
whole_stream_command(StrReverse),
|
||||
whole_stream_command(StrCamelCase),
|
||||
whole_stream_command(StrPascalCase),
|
||||
whole_stream_command(StrKebabCase),
|
||||
whole_stream_command(StrSnakeCase),
|
||||
whole_stream_command(StrScreamingSnakeCase),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(Char),
|
||||
@ -346,6 +175,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Get),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(IntoInt),
|
||||
whole_stream_command(SplitBy),
|
||||
// Row manipulation
|
||||
whole_stream_command(Reverse),
|
||||
@ -398,6 +228,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(MathStddev),
|
||||
whole_stream_command(MathSummation),
|
||||
whole_stream_command(MathVariance),
|
||||
whole_stream_command(MathProduct),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
@ -435,11 +266,13 @@ pub fn create_default_context(
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
whole_stream_command(RandomUUID),
|
||||
// Path
|
||||
whole_stream_command(PathCommand),
|
||||
whole_stream_command(PathExtension),
|
||||
whole_stream_command(PathBasename),
|
||||
whole_stream_command(PathExpand),
|
||||
whole_stream_command(PathCommand),
|
||||
whole_stream_command(PathDirname),
|
||||
whole_stream_command(PathExists),
|
||||
whole_stream_command(PathExpand),
|
||||
whole_stream_command(PathExtension),
|
||||
whole_stream_command(PathFilestem),
|
||||
whole_stream_command(PathType),
|
||||
// Url
|
||||
whole_stream_command(UrlCommand),
|
||||
@ -465,7 +298,7 @@ pub async fn run_vec_of_pipelines(
|
||||
let mut syncer = crate::EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(&mut syncer, false)?;
|
||||
|
||||
let _ = crate::load_plugins(&mut context);
|
||||
let _ = register_plugins(&mut context);
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
@ -482,7 +315,7 @@ pub async fn run_vec_of_pipelines(
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
@ -558,7 +391,7 @@ pub async fn run_pipeline_standalone(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
@ -720,9 +553,6 @@ pub fn set_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>
|
||||
}
|
||||
}
|
||||
|
||||
// we are ok if history does not exist
|
||||
let _ = rl.load_history(&History::path());
|
||||
|
||||
(rl, config)
|
||||
}
|
||||
|
||||
@ -731,9 +561,15 @@ pub async fn cli(
|
||||
mut syncer: EnvironmentSyncer,
|
||||
mut context: Context,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = load_plugins(&mut context);
|
||||
let configuration = nu_data::config::NuConfig::new();
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
|
||||
let (mut rl, config) = set_rustyline_configuration();
|
||||
let _ = register_plugins(&mut context);
|
||||
|
||||
let (mut rl, config) = create_rustyline_configuration();
|
||||
|
||||
// we are ok if history does not exist
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let skip_welcome_message = config
|
||||
.get("skip_welcome_message")
|
||||
@ -746,11 +582,6 @@ pub async fn cli(
|
||||
);
|
||||
}
|
||||
|
||||
let use_starship = config
|
||||
.get("use_starship")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
@ -768,7 +599,7 @@ pub async fn cli(
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
@ -802,43 +633,12 @@ pub async fn cli(
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(
|
||||
Box::new(<NuCompleter as Default>::default()),
|
||||
context.clone(),
|
||||
)));
|
||||
let hinter = init_hinter(&config);
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
|
||||
|
||||
let colored_prompt = {
|
||||
if use_starship {
|
||||
#[cfg(feature = "starship")]
|
||||
{
|
||||
std::env::set_var("STARSHIP_SHELL", "");
|
||||
std::env::set_var("PWD", &cwd);
|
||||
let mut starship_context =
|
||||
starship::context::Context::new_with_dir(clap::ArgMatches::default(), cwd);
|
||||
|
||||
match starship_context.config.config {
|
||||
None => {
|
||||
starship_context.config.config = create_default_starship_config();
|
||||
}
|
||||
Some(toml::Value::Table(t)) if t.is_empty() => {
|
||||
starship_context.config.config = create_default_starship_config();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
starship::print::get_prompt(starship_context)
|
||||
}
|
||||
#[cfg(not(feature = "starship"))]
|
||||
{
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
} else if let Some(prompt) = config.get("prompt") {
|
||||
if let Some(prompt) = config.get("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
@ -934,13 +734,13 @@ pub async fn cli(
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&History::path());
|
||||
let _ = rl.save_history(&history_path);
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&History::path());
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
@ -960,7 +760,7 @@ pub async fn cli(
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
let _ = rl.save_history(&History::path());
|
||||
let _ = rl.save_history(&history_path);
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
@ -977,11 +777,23 @@ pub async fn cli(
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
let _ = rl.save_history(&History::path());
|
||||
let _ = rl.save_history(&history_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
||||
// Show hints unless explicitly disabled in config
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
fn chomp_newline(s: &str) -> &str {
|
||||
if s.ends_with('\n') {
|
||||
&s[..s.len() - 1]
|
||||
@ -1202,7 +1014,7 @@ pub async fn process_line(
|
||||
};
|
||||
|
||||
if let Ok(mut output_stream) =
|
||||
crate::commands::autoview::autoview(context).await
|
||||
crate::commands::autoview::command::autoview(context).await
|
||||
{
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
|
@ -63,6 +63,7 @@ pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
@ -78,7 +79,6 @@ 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;
|
||||
@ -97,6 +97,7 @@ 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 split;
|
||||
pub(crate) mod split_by;
|
||||
@ -146,7 +147,7 @@ pub(crate) use config::{
|
||||
};
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::Date;
|
||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use do_::Do;
|
||||
@ -191,13 +192,14 @@ pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use into_int::IntoInt;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
|
||||
MathSummation, MathVariance,
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||
MathStddev, MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
@ -206,7 +208,10 @@ 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;
|
||||
@ -227,12 +232,14 @@ 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 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, StrLength, StrPascalCase, StrReverse,
|
||||
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
|
@ -1,12 +1,16 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::SignatureRegistry;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
@ -15,6 +19,7 @@ pub struct AliasArgs {
|
||||
pub name: Tagged<String>,
|
||||
pub args: Vec<Value>,
|
||||
pub block: Block,
|
||||
pub infer: Option<bool>,
|
||||
pub save: Option<bool>,
|
||||
}
|
||||
|
||||
@ -33,6 +38,7 @@ impl WholeStreamCommand for Alias {
|
||||
SyntaxShape::Block,
|
||||
"the block to run as the body of the alias",
|
||||
)
|
||||
.switch("infer", "infer argument types (experimental)", Some('i'))
|
||||
.switch("save", "save the alias to your config", Some('s'))
|
||||
}
|
||||
|
||||
@ -75,6 +81,7 @@ pub async fn alias(
|
||||
name,
|
||||
args: list,
|
||||
block,
|
||||
infer,
|
||||
save,
|
||||
},
|
||||
_ctx,
|
||||
@ -82,17 +89,21 @@ pub async fn alias(
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
||||
let left = raw_input[..left_brace]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
.replace("--save", "") // TODO using regex (or reconstruct string from AST?)
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
let right = raw_input[right_brace..]
|
||||
.replace("--save", "")
|
||||
.replace("-s", "");
|
||||
.replace("-si", "-i")
|
||||
.replace("-s ", "")
|
||||
.replace("-is", "-i");
|
||||
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
||||
|
||||
// create a value from raw_input alias
|
||||
@ -133,9 +144,197 @@ pub async fn alias(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(name.to_string(), processed_args, block),
|
||||
)))
|
||||
if let Some(true) = infer {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
||||
block,
|
||||
),
|
||||
)))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::AddAlias(
|
||||
name.to_string(),
|
||||
processed_args
|
||||
.into_iter()
|
||||
.map(|arg| (arg, SyntaxShape::Any))
|
||||
.collect(),
|
||||
block,
|
||||
),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_arg_shapes(
|
||||
args: Vec<String>,
|
||||
block: &Block,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
|
||||
match find_block_shapes(block, registry) {
|
||||
Ok(found) => Ok(args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
(
|
||||
arg.clone(),
|
||||
match found.get(arg) {
|
||||
None | Some((_, None)) => SyntaxShape::Any,
|
||||
Some((_, Some(shape))) => *shape,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
|
||||
|
||||
fn check_insert(
|
||||
existing: &mut ShapeMap,
|
||||
to_add: (String, (Span, Option<SyntaxShape>)),
|
||||
) -> Result<(), ShellError> {
|
||||
match (to_add.1).1 {
|
||||
None => match existing.get(&to_add.0) {
|
||||
None => {
|
||||
existing.insert(to_add.0, to_add.1);
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Ok(()),
|
||||
},
|
||||
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
|
||||
None => Ok(()),
|
||||
Some(exist) => match exist.1 {
|
||||
None => Ok(()),
|
||||
Some(shape) => match shape {
|
||||
SyntaxShape::Any => Ok(()),
|
||||
shape if shape == new => Ok(()),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Type conflict in alias variable use",
|
||||
format!("{:?}", new),
|
||||
(to_add.1).0,
|
||||
format!("{:?}", shape),
|
||||
exist.0,
|
||||
)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
|
||||
for (k, v) in new.iter() {
|
||||
check_insert(existing, (k.clone(), *v))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_expr_shapes(
|
||||
spanned_expr: &SpannedExpression,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<ShapeMap, ShellError> {
|
||||
match &spanned_expr.expr {
|
||||
// TODO range will need similar if/when invocations can be parsed within range expression
|
||||
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
|
||||
find_expr_shapes(&bin.right, registry)
|
||||
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
|
||||
}),
|
||||
Expression::Block(b) => find_block_shapes(&b, registry),
|
||||
Expression::Path(path) => match &path.head.expr {
|
||||
Expression::Invocation(b) => find_block_shapes(&b, registry),
|
||||
Expression::Variable(Variable::Other(var, _)) => {
|
||||
let mut result = HashMap::new();
|
||||
result.insert(var.to_string(), (spanned_expr.span, None));
|
||||
Ok(result)
|
||||
}
|
||||
_ => Ok(HashMap::new()),
|
||||
},
|
||||
_ => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
|
||||
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
|
||||
found
|
||||
.iter()
|
||||
.map(|(v, sh)| match sh.1 {
|
||||
None => (v.clone(), (sh.0, Some(sig_shape))),
|
||||
Some(shape) => (v.clone(), (sh.0, Some(shape))),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut arg_shapes = HashMap::new();
|
||||
for pipeline in &block.block {
|
||||
for classified in &pipeline.list {
|
||||
match classified {
|
||||
ClassifiedCommand::Expr(spanned_expr) => {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
check_merge(&mut arg_shapes, &found)?
|
||||
}
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
if let Some(signature) = registry.get(&internal.name) {
|
||||
if let Some(positional) = &internal.args.positional {
|
||||
for (i, spanned_expr) in positional.iter().enumerate() {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
if i >= signature.positional.len() {
|
||||
if let Some((sig_shape, _)) = &signature.rest_positional {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
} else {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
} else {
|
||||
let (pos_type, _) = &signature.positional[i];
|
||||
match pos_type {
|
||||
// TODO pass on mandatory/optional?
|
||||
PositionalType::Mandatory(_, sig_shape)
|
||||
| PositionalType::Optional(_, sig_shape) => {
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(named) = &internal.args.named {
|
||||
for (name, val) in named.iter() {
|
||||
if let NamedValue::Value(_, spanned_expr) = val {
|
||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
||||
match signature.named.get(name) {
|
||||
None => {
|
||||
unreachable!("should have error'd in parsing");
|
||||
}
|
||||
Some((named_type, _)) => {
|
||||
if let NamedType::Mandatory(_, sig_shape)
|
||||
| NamedType::Optional(_, sig_shape) = named_type
|
||||
{
|
||||
check_merge(
|
||||
&mut arg_shapes,
|
||||
&apply_shape(found, *sig_shape),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!("registry has lost name it provided");
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arg_shapes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
@ -76,60 +77,56 @@ impl WholeStreamCommand for Ansi {
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
pub 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()),
|
||||
"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()),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ 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 {
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewConfiguration};
|
||||
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use crate::primitive::get_color_config;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
use nu_table::TextStyle;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
pub struct Autoview;
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoview {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"autoview"
|
||||
}
|
||||
@ -82,31 +84,17 @@ impl RunnableContextWithoutInput {
|
||||
}
|
||||
|
||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let configuration = AutoViewConfiguration::new();
|
||||
|
||||
let binary = context.get_command("binaryview");
|
||||
let text = context.get_command("textview");
|
||||
let table = context.get_command("table");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
let pivot_mode = crate::data::config::config(Tag::unknown());
|
||||
let pivot_mode = if let Some(v) = pivot_mode?.get("pivot_mode") {
|
||||
match v.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Always,
|
||||
}
|
||||
} else {
|
||||
AutoPivotMode::Always
|
||||
};
|
||||
let pivot_mode = configuration.pivot_mode();
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
let color_hm = get_color_config();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
@ -254,8 +242,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
Value {
|
||||
value: UntaggedValue::Row(row),
|
||||
..
|
||||
} if pivot_mode == AutoPivotMode::Always
|
||||
|| (pivot_mode == AutoPivotMode::Auto
|
||||
} if pivot_mode.is_always()
|
||||
|| (pivot_mode.is_auto()
|
||||
&& (row
|
||||
.entries
|
||||
.iter()
|
||||
@ -271,15 +259,14 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
entries.push(vec![
|
||||
nu_table::StyledString::new(
|
||||
key.to_string(),
|
||||
nu_table::TextStyle {
|
||||
alignment: nu_table::Alignment::Left,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
TextStyle::new()
|
||||
.alignment(nu_table::Alignment::Left)
|
||||
.fg(ansi_term::Color::Green)
|
||||
.bold(Some(true)),
|
||||
),
|
||||
nu_table::StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
nu_table::TextStyle::basic(),
|
||||
nu_table::TextStyle::basic_left(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
@ -287,7 +274,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
let table =
|
||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||
|
||||
nu_table::draw_table(&table, term_width);
|
||||
nu_table::draw_table(&table, term_width, &color_hm);
|
||||
}
|
||||
|
||||
Value {
|
||||
@ -338,12 +325,12 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Autoview;
|
||||
use super::Command;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Autoview {})
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
4
crates/nu-cli/src/commands/autoview/mod.rs
Normal file
4
crates/nu-cli/src/commands/autoview/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod command;
|
||||
mod options;
|
||||
|
||||
pub use command::Command as Autoview;
|
60
crates/nu-cli/src/commands/autoview/options.rs
Normal file
60
crates/nu-cli/src/commands/autoview/options.rs
Normal file
@ -0,0 +1,60 @@
|
||||
pub use nu_data::config::NuConfig;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
impl AutoPivotMode {
|
||||
pub fn is_auto(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Auto => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_always(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Always => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn is_never(&self) -> bool {
|
||||
match &self {
|
||||
AutoPivotMode::Never => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConfigExtensions: Debug + Send {
|
||||
fn pivot_mode(&self) -> AutoPivotMode;
|
||||
}
|
||||
|
||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
if let Some(mode) = vars.get("pivot_mode") {
|
||||
let mode = match mode.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Never,
|
||||
};
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
AutoPivotMode::Never
|
||||
}
|
||||
|
||||
impl ConfigExtensions for NuConfig {
|
||||
fn pivot_mode(&self) -> AutoPivotMode {
|
||||
pivot_mode(self)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -425,7 +425,7 @@ fn spawn(
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
let cfg = nu_data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
|
@ -4,6 +4,7 @@ pub(crate) mod expr;
|
||||
pub(crate) mod external;
|
||||
pub(crate) mod internal;
|
||||
pub(crate) mod maybe_text_codec;
|
||||
pub(crate) mod plugin;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use dynamic::Command as DynamicCommand;
|
||||
|
@ -1,30 +1,16 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::commands::command::{whole_stream_command, WholeStreamCommand};
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::jsonrpc::JsonRpc;
|
||||
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JsonRpc<T> {
|
||||
jsonrpc: String,
|
||||
pub method: String,
|
||||
pub params: T,
|
||||
}
|
||||
|
||||
impl<T> JsonRpc<T> {
|
||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
||||
JsonRpc {
|
||||
jsonrpc: "2.0".into(),
|
||||
method: method.into(),
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "method")]
|
||||
@ -35,15 +21,77 @@ pub enum NuResult {
|
||||
},
|
||||
}
|
||||
|
||||
enum PluginCommand {
|
||||
Filter(PluginFilter),
|
||||
Sink(PluginSink),
|
||||
}
|
||||
|
||||
impl PluginCommand {
|
||||
fn command(self) -> Result<crate::commands::Command, ShellError> {
|
||||
match self {
|
||||
PluginCommand::Filter(cmd) => Ok(whole_stream_command(cmd)),
|
||||
PluginCommand::Sink(cmd) => Ok(whole_stream_command(cmd)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PluginMode {
|
||||
Filter,
|
||||
Sink,
|
||||
}
|
||||
|
||||
pub struct PluginCommandBuilder {
|
||||
mode: PluginMode,
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
impl PluginCommandBuilder {
|
||||
pub fn new(
|
||||
name: impl Into<String>,
|
||||
path: impl Into<String>,
|
||||
config: impl Into<Signature>,
|
||||
) -> Self {
|
||||
let config = config.into();
|
||||
|
||||
PluginCommandBuilder {
|
||||
mode: if config.is_filter {
|
||||
PluginMode::Filter
|
||||
} else {
|
||||
PluginMode::Sink
|
||||
},
|
||||
name: name.into(),
|
||||
path: path.into(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Result<crate::commands::Command, ShellError> {
|
||||
let mode = &self.mode;
|
||||
|
||||
let name = self.name.clone();
|
||||
let path = self.path.clone();
|
||||
let config = self.config.clone();
|
||||
|
||||
let cmd = match mode {
|
||||
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
|
||||
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
|
||||
};
|
||||
|
||||
cmd.command()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct PluginCommand {
|
||||
pub struct PluginFilter {
|
||||
name: String,
|
||||
path: String,
|
||||
config: Signature,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PluginCommand {
|
||||
impl WholeStreamCommand for PluginFilter {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
@ -61,11 +109,11 @@ impl WholeStreamCommand for PluginCommand {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
filter_plugin(self.path.clone(), args, registry).await
|
||||
run_filter(self.path.clone(), args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn filter_plugin(
|
||||
async fn run_filter(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -84,11 +132,34 @@ pub async fn filter_plugin(
|
||||
|
||||
let args = args.evaluate_once_with_scope(®istry, &scope).await?;
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process");
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
let ps1_file = match ext {
|
||||
Some(ext) => ext == "ps1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
let mut child: Child = if ps1_file {
|
||||
Command::new("pwsh")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.args(&[
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
&real_path.to_string_lossy(),
|
||||
])
|
||||
.spawn()
|
||||
.expect("Failed to spawn PowerShell process")
|
||||
} else {
|
||||
Command::new(path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn child process")
|
||||
};
|
||||
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
@ -111,6 +182,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("begin_filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
@ -136,6 +208,8 @@ pub async fn filter_plugin(
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("begin_filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -168,6 +242,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("end_filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Err(_) => {
|
||||
@ -193,6 +268,8 @@ pub async fn filter_plugin(
|
||||
let stream = match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("end_filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -220,6 +297,7 @@ pub async fn filter_plugin(
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("quit", vec![]);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("quit:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
@ -246,6 +324,8 @@ pub async fn filter_plugin(
|
||||
|
||||
let request = JsonRpc::new("filter", v);
|
||||
let request_raw = serde_json::to_string(&request);
|
||||
trace!("filter:request {:?}", &request_raw);
|
||||
|
||||
match request_raw {
|
||||
Ok(request_raw) => {
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes());
|
||||
@ -262,6 +342,8 @@ pub async fn filter_plugin(
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let response = serde_json::from_str::<NuResult>(&input);
|
||||
trace!("filter:response {:?}", &response);
|
||||
|
||||
match response {
|
||||
Ok(NuResult::response { params }) => match params {
|
||||
Ok(params) => futures::stream::iter(params).to_output_stream(),
|
||||
@ -313,11 +395,11 @@ impl WholeStreamCommand for PluginSink {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sink_plugin(self.path.clone(), args, registry).await
|
||||
run_sink(self.path.clone(), args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sink_plugin(
|
||||
async fn run_sink(
|
||||
path: String,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
@ -335,7 +417,33 @@ pub async fn sink_plugin(
|
||||
let _ = writeln!(tmpfile, "{}", request_raw);
|
||||
let _ = tmpfile.flush();
|
||||
|
||||
let child = std::process::Command::new(path).arg(tmpfile.path()).spawn();
|
||||
let real_path = Path::new(&path);
|
||||
let ext = real_path.extension();
|
||||
let ps1_file = match ext {
|
||||
Some(ext) => ext == "ps1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
// TODO: This sink may not work in powershell, trying to find
|
||||
// an example of what CallInfo would look like in this temp file
|
||||
let child = if ps1_file {
|
||||
Command::new("pwsh")
|
||||
.args(&[
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
&real_path.to_string_lossy(),
|
||||
&tmpfile
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("Failed getting tmpfile path"),
|
||||
])
|
||||
.spawn()
|
||||
} else {
|
||||
Command::new(path).arg(&tmpfile.path()).spawn()
|
||||
};
|
||||
|
||||
if let Ok(mut child) = child {
|
||||
let _ = child.wait();
|
@ -45,7 +45,7 @@ pub async fn clear(
|
||||
|
||||
// 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)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.clear();
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl WholeStreamCommand for Command {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -56,7 +56,7 @@ pub async fn get(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
|
@ -50,7 +50,7 @@ pub async fn set(
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
let result = crate::data::config::read(name_span, &Some(configuration))?;
|
||||
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -54,7 +54,7 @@ pub async fn remove(
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
|
@ -39,8 +39,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set completion_mode to circular",
|
||||
example: "config set [completion_mode circular]",
|
||||
description: "Set nonzero_exit_errors to true",
|
||||
example: "config set nonzero_exit_errors $true",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
@ -55,7 +55,7 @@ pub async fn set(
|
||||
|
||||
// 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)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
|
@ -58,7 +58,7 @@ pub async fn set_into(
|
||||
|
||||
// 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)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
// In the original code, this is set to `Some` if the `load flag is set`
|
||||
let configuration = None;
|
||||
|
@ -7,6 +7,11 @@ use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CountArgs {
|
||||
column: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,7 +19,11 @@ impl WholeStreamCommand for Count {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("count")
|
||||
Signature::build("count").switch(
|
||||
"column",
|
||||
"Calculate number of columns in table",
|
||||
Some('c'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -24,22 +33,47 @@ impl WholeStreamCommand for Count {
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
_registry: &CommandRegistry,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let rows: Vec<Value> = args.input.collect().await;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (CountArgs { column }, input) = args.process(®istry).await?;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::int(rows.len()).into_value(name),
|
||||
))
|
||||
let count = if column {
|
||||
if rows.is_empty() {
|
||||
0
|
||||
} else {
|
||||
match &rows[0].value {
|
||||
UntaggedValue::Row(dictionary) => dictionary.length(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Cannot obtain column count",
|
||||
"cannot obtain column count",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rows.len()
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(UntaggedValue::int(count).into_value(tag)))
|
||||
}
|
||||
|
||||
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()]),
|
||||
}]
|
||||
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()]),
|
||||
},
|
||||
Example {
|
||||
description: "Count the number of columns in the calendar table",
|
||||
example: "cal | count -c",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {})
|
||||
}
|
||||
}
|
46
crates/nu-cli/src/commands/date/command.rs
Normal file
46
crates/nu-cli/src/commands/date/command.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Work with dates."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
Ok(OutputStream::one(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 {})
|
||||
}
|
||||
}
|
63
crates/nu-cli/src/commands/date/format.rs
Normal file
63
crates/nu-cli/src/commands/date/format.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
format: Tagged<String>,
|
||||
raw: Option<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date format")
|
||||
.required("format", SyntaxShape::String, "strftime format")
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"format the current date using the given format string."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
format(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn format(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (FormatArgs { format, raw }, _) = args.process(®istry).await?;
|
||||
|
||||
let dt_fmt = format.to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if let Some(true) = raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
11
crates/nu-cli/src/commands/date/mod.rs
Normal file
11
crates/nu-cli/src/commands/date/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
pub mod command;
|
||||
pub mod format;
|
||||
pub mod now;
|
||||
pub mod utc;
|
||||
|
||||
mod utils;
|
||||
|
||||
pub use command::Command as Date;
|
||||
pub use format::Date as DateFormat;
|
||||
pub use now::Date as DateNow;
|
||||
pub use utc::Date as DateUTC;
|
50
crates/nu-cli/src/commands/date/now.rs
Normal file
50
crates/nu-cli/src/commands/date/now.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date now"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date now")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
now(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn now(
|
||||
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 no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
50
crates/nu-cli/src/commands/date/utc.rs
Normal file
50
crates/nu-cli/src/commands/date/utc.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date utc"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date utc")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
utc(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(
|
||||
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 no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Utc> = Utc::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
64
crates/nu-cli/src/commands/date/utils.rs
Normal file
64
crates/nu-cli/src/commands/date/utils.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::DateTime;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::UntaggedValue;
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
@ -22,12 +22,12 @@ impl WholeStreamCommand for Drop {
|
||||
Signature::build("drop").optional(
|
||||
"rows",
|
||||
SyntaxShape::Number,
|
||||
"starting from the back, the number of rows to drop",
|
||||
"starting from the back, the number of rows to remove",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Drop the last number of rows."
|
||||
"Remove the last number of rows. If you want to remove columns, try 'reject'."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -102,7 +102,7 @@ impl Iterator for RangeIterator {
|
||||
if self.curr != self.end {
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
|
||||
self.curr = match crate::data::value::compute_values(
|
||||
self.curr = match nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
@ -7,7 +7,7 @@ use nu_protocol::{
|
||||
did_you_mean, ColumnPath, PathMember, Primitive, ReturnSuccess, Signature, SyntaxShape,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_source::HasFallibleSpan;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
pub struct Get;
|
||||
@ -65,7 +65,7 @@ pub fn get_column_path(path: &ColumnPath, obj: &Value) -> Result<Value, ShellErr
|
||||
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));
|
||||
let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
match &obj_source.value {
|
||||
UntaggedValue::Table(rows) => match column_path_tried {
|
||||
|
@ -179,7 +179,7 @@ pub async fn group_by(
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
|
||||
};
|
||||
@ -234,12 +234,12 @@ pub fn group(
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| as_string(row));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByBlock => Err(ShellError::unimplemented(
|
||||
"Block not implemented: This should never happen.",
|
||||
@ -250,7 +250,7 @@ pub fn group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::group;
|
||||
use crate::utils::data::helpers::{committers, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
||||
use nu_errors::ShellError;
|
||||
use nu_source::*;
|
||||
|
||||
|
@ -102,7 +102,7 @@ pub async fn group_by_date(
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format("%Y-%m-%d"));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
@ -113,12 +113,12 @@ pub async fn group_by_date(
|
||||
group_key?.format("%Y-%m-%d")
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format(&fmt));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
@ -129,7 +129,7 @@ pub async fn group_by_date(
|
||||
group_key?.format(&fmt)
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ use futures::stream::StreamExt;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Dictionary;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Headers;
|
||||
|
||||
@ -32,11 +32,18 @@ impl WholeStreamCommand for Headers {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Create headers for a raw string",
|
||||
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Don't panic on rows with different headers",
|
||||
example: r#"echo "a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,8 +91,13 @@ pub async fn headers(
|
||||
match &r.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let mut entries = IndexMap::new();
|
||||
for (i, (_, v)) in d.entries.iter().enumerate() {
|
||||
entries.insert(headers[i].clone(), v.clone());
|
||||
for (i, header) in headers.iter().enumerate() {
|
||||
let value = match d.entries.get_index(i) {
|
||||
Some((_, value)) => value.clone(),
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing).into(),
|
||||
};
|
||||
|
||||
entries.insert(header.clone(), value);
|
||||
}
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(r.tag.clone()),
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::commands::command::Command;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_data::command::signature_dict;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
@ -38,6 +38,21 @@ impl WholeStreamCommand for Help {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut cmd_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
|
||||
|
||||
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
|
||||
|
||||
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
|
||||
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
|
||||
|
||||
cmd_dict.into_value()
|
||||
}
|
||||
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
@ -1,16 +1,13 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_protocol::{
|
||||
ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Histogram;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HistogramArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Histogram {
|
||||
fn name(&self) -> &str {
|
||||
@ -18,10 +15,17 @@ impl WholeStreamCommand for Histogram {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("histogram").rest(
|
||||
SyntaxShape::String,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
Signature::build("histogram")
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"Use data at the column path given as valuator",
|
||||
None,
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"column name to give the histogram's frequency column",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -64,22 +68,37 @@ pub async fn histogram(
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (input, args) = args.evaluate_once(®istry).await?.parts();
|
||||
|
||||
let (HistogramArgs { rest: mut columns }, input) = args.process(®istry).await?;
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
|
||||
let column_grouper = if !columns.is_empty() {
|
||||
Some(columns.remove(0))
|
||||
let mut columns = args
|
||||
.positional_iter()
|
||||
.map(|c| c.as_column_path())
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let evaluate_with = if let Some(path) = args.get("use") {
|
||||
Some(evaluator(path.as_column_path()?.item))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let column_names_supplied: Vec<_> = columns.iter().map(|f| f.item.clone()).collect();
|
||||
|
||||
let frequency_column_name = if column_names_supplied.is_empty() {
|
||||
"frequency".to_string()
|
||||
let column_grouper = if !columns.is_empty() {
|
||||
match columns.remove(0).split_last() {
|
||||
Some((key, _)) => Some(key.as_string().tagged(&name)),
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
column_names_supplied[0].clone()
|
||||
None
|
||||
};
|
||||
|
||||
let frequency_column_name = if columns.is_empty() {
|
||||
"frequency".to_string()
|
||||
} else if let Some((key, _)) = columns[0].split_last() {
|
||||
key.as_string()
|
||||
} else {
|
||||
"frecuency".to_string()
|
||||
};
|
||||
|
||||
let column = if let Some(ref column) = column_grouper {
|
||||
@ -88,13 +107,13 @@ pub async fn histogram(
|
||||
"value".to_string().tagged(&name)
|
||||
};
|
||||
|
||||
let results = crate::utils::data::report(
|
||||
let results = nu_data::utils::report(
|
||||
&UntaggedValue::table(&values).into_value(&name),
|
||||
crate::utils::data::Operation {
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: None,
|
||||
eval: &None,
|
||||
eval: &evaluate_with,
|
||||
},
|
||||
&name,
|
||||
)?;
|
||||
@ -108,13 +127,13 @@ pub async fn histogram(
|
||||
.table_entries()
|
||||
.map(move |value| {
|
||||
let values = value.table_entries().cloned().collect::<Vec<_>>();
|
||||
let count = values.len();
|
||||
let occurrences = values.len();
|
||||
|
||||
(count, values[count - 1].clone())
|
||||
(occurrences, values[occurrences - 1].clone())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(move |(count, value)| {
|
||||
.map(move |(occurrences, value)| {
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let column_value = labels
|
||||
.get(idx)
|
||||
@ -128,7 +147,16 @@ pub async fn histogram(
|
||||
.clone();
|
||||
|
||||
fact.insert_value(&column.item, column_value);
|
||||
fact.insert_untagged("count", UntaggedValue::int(count));
|
||||
fact.insert_untagged("occurrences", UntaggedValue::int(occurrences));
|
||||
|
||||
let percentage = format!(
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::str_::from::action(&value, &name, Some(2), true)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
|
||||
|
||||
let string = std::iter::repeat("*")
|
||||
.take(value.as_u64().map_err(|_| {
|
||||
@ -146,6 +174,23 @@ pub async fn histogram(
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(
|
||||
value,
|
||||
&path,
|
||||
Box::new(move |(_, _, error)| error),
|
||||
);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn splitter(
|
||||
by: Option<Tagged<String>>,
|
||||
) -> Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send> {
|
||||
|
@ -1,10 +1,32 @@
|
||||
use crate::cli::History as HistoryFile;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::NuConfig;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_LOCATION: &str = "history.txt";
|
||||
|
||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
let default_path = nu_data::config::user_data()
|
||||
.map(|mut p| {
|
||||
p.push(DEFAULT_LOCATION);
|
||||
p
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||
|
||||
vars.get("history-path")
|
||||
.map_or(default_path.clone(), |custom_path| {
|
||||
match custom_path.as_string() {
|
||||
Ok(path) => PathBuf::from(path),
|
||||
Err(_) => default_path,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct History;
|
||||
|
||||
@ -32,9 +54,10 @@ impl WholeStreamCommand for History {
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let config = NuConfig::new();
|
||||
let tag = args.call_info.name_tag;
|
||||
let history_path = HistoryFile::path();
|
||||
let file = File::open(history_path);
|
||||
let path = history_path(&config);
|
||||
let file = File::open(path);
|
||||
if let Ok(file) = file {
|
||||
let reader = BufReader::new(file);
|
||||
let output = reader.lines().filter_map(move |line| match line {
|
||||
|
@ -2,11 +2,13 @@ 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_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Insert;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -49,14 +51,14 @@ async fn process_row(
|
||||
mut context: Arc<Context>,
|
||||
input: Value,
|
||||
mut value: Arc<Value>,
|
||||
column: Arc<ColumnPath>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let value = Arc::make_mut(&mut value);
|
||||
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
..
|
||||
tag: block_tag,
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
@ -73,52 +75,57 @@ async fn process_row(
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let values = stream.drain_vec().await;
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
let result = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to insert with"))?;
|
||||
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
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(
|
||||
} => match obj.insert_data_at_column_path(&field, result) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
block_tag.clone(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
_ => match input {
|
||||
obj
|
||||
@
|
||||
value => match input {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match obj.insert_data_at_column_path(&column, value.clone()) {
|
||||
} => match scope.it.insert_data_at_column_path(&field, value.clone()) {
|
||||
Ok(v) => OutputStream::one(ReturnSuccess::value(v)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
_ => match input.insert_data_at_column_path(&field, 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,
|
||||
))),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
88
crates/nu-cli/src/commands/into_int.rs
Normal file
88
crates/nu-cli/src/commands/into_int.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
pub struct IntoInt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IntoIntArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IntoInt {
|
||||
fn name(&self) -> &str {
|
||||
"into-int"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into-int").rest(SyntaxShape::Any, "the values to into-int")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert value to integer"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
into_int(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert filesize to integer",
|
||||
example: "echo 1kb | into-int $it | = $it / 1024",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn into_int(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (args, _): (IntoIntArgs, _) = args.process(®istry).await?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
} => match primitive_val {
|
||||
Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::int(size.to_bigint().expect("Conversion should never fail.")),
|
||||
tag,
|
||||
}))),
|
||||
Primitive::Int(_) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
}))),
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Could not convert int value",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
},
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(i))),
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IntoInt;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(IntoInt {})
|
||||
}
|
||||
}
|
@ -14,8 +14,6 @@ pub struct LsArgs {
|
||||
pub long: bool,
|
||||
#[serde(rename = "short-names")]
|
||||
pub short_names: bool,
|
||||
#[serde(rename = "with-symlink-targets")]
|
||||
pub with_symlink_targets: bool,
|
||||
#[serde(rename = "du")]
|
||||
pub du: bool,
|
||||
}
|
||||
@ -44,12 +42,6 @@ impl WholeStreamCommand for Ls {
|
||||
"Only print the file names and not the path",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
// Delete this
|
||||
"with-symlink-targets",
|
||||
"Display the paths to the target files that symlinks point to",
|
||||
Some('w'),
|
||||
)
|
||||
.switch(
|
||||
"du",
|
||||
"Display the apparent directory size in place of the directory metadata size",
|
||||
|
@ -266,7 +266,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
Block::extract(value)?
|
||||
}
|
||||
@ -321,7 +321,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
<$param_kind>::extract(&value)?
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -142,7 +142,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -138,7 +138,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -162,7 +162,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -5,6 +5,7 @@ pub mod max;
|
||||
pub mod median;
|
||||
pub mod min;
|
||||
pub mod mode;
|
||||
pub mod product;
|
||||
pub mod stddev;
|
||||
pub mod sum;
|
||||
pub mod variance;
|
||||
@ -19,6 +20,7 @@ pub use max::SubCommand as MathMaximum;
|
||||
pub use median::SubCommand as MathMedian;
|
||||
pub use min::SubCommand as MathMinimum;
|
||||
pub use mode::SubCommand as MathMode;
|
||||
pub use product::SubCommand as MathProduct;
|
||||
pub use stddev::SubCommand as MathStddev;
|
||||
pub use sum::SubCommand as MathSummation;
|
||||
pub use variance::SubCommand as MathVariance;
|
||||
|
126
crates/nu-cli/src/commands/math/product.rs
Normal file
126
crates/nu-cli/src/commands/math/product.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{convert_number_to_u64, Number},
|
||||
Primitive, Signature, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math product"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math product")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the product of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
},
|
||||
product,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the product of a list of numbers",
|
||||
example: "echo [2 3 3 4] | math product",
|
||||
result: Some(vec![UntaggedValue::int(72).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn to_byte(value: &Value) -> Option<Value> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
|
||||
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
|
||||
num.clone(),
|
||||
))))
|
||||
.into_untagged_value(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate product of given values
|
||||
pub fn product(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let prod = reducer_for(Reduce::Product);
|
||||
|
||||
let first = values.get(0).ok_or_else(|| {
|
||||
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
|
||||
})?;
|
||||
|
||||
match first {
|
||||
v if v.is_filesize() => to_byte(&prod(
|
||||
UntaggedValue::int(1).into_untagged_value(),
|
||||
values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
|
||||
..
|
||||
} => UntaggedValue::int(*num as usize).into_untagged_value(),
|
||||
other => other.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to decimal",
|
||||
"could not convert to decimal",
|
||||
&name.span,
|
||||
)
|
||||
}),
|
||||
|
||||
v if v.is_none() => prod(
|
||||
UntaggedValue::int(1).into_untagged_value(),
|
||||
values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => UntaggedValue::int(1).into_untagged_value(),
|
||||
other => other.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
_ => prod(UntaggedValue::int(1).into_untagged_value(), values.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::data::value::{compare_values, compute_values};
|
||||
use nu_data::value::{compare_values, compute_values};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
@ -47,6 +47,7 @@ pub fn reducer_for(
|
||||
)),
|
||||
Reduce::Minimum => Box::new(|_, values| min(values)),
|
||||
Reduce::Maximum => Box::new(|_, values| max(values)),
|
||||
Reduce::Product => Box::new(|_, values| product(values)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +55,7 @@ pub enum Reduce {
|
||||
Summation,
|
||||
Minimum,
|
||||
Maximum,
|
||||
Product,
|
||||
Default,
|
||||
}
|
||||
|
||||
@ -133,3 +135,34 @@ pub fn min(data: Vec<Value>) -> Result<Value, ShellError> {
|
||||
tag: Tag::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn product(data: Vec<Value>) -> Result<Value, ShellError> {
|
||||
if data.is_empty() {
|
||||
return Err(ShellError::unexpected(ERR_EMPTY_DATA));
|
||||
}
|
||||
|
||||
let mut prod = UntaggedValue::int(1).into_untagged_value();
|
||||
for value in data {
|
||||
match value.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
prod = match compute_values(Operator::Multiply, &prod, &value) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned_unknown(),
|
||||
right_type.spanned_unknown(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Attempted to compute the product of a value that cannot be multiplied.",
|
||||
"value appears here",
|
||||
value.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(prod)
|
||||
}
|
||||
|
@ -83,30 +83,32 @@ impl WholeStreamCommand for SubCommand {
|
||||
entries: column_totals,
|
||||
})
|
||||
.into_untagged_value())
|
||||
};
|
||||
}?;
|
||||
|
||||
match res {
|
||||
Ok(v) => {
|
||||
if v.value.is_table() {
|
||||
Ok(OutputStream::from(
|
||||
v.table_entries()
|
||||
.map(|v| ReturnSuccess::value(v.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(v)))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
if res.value.is_table() {
|
||||
Ok(OutputStream::from(
|
||||
res.table_entries()
|
||||
.map(|v| ReturnSuccess::value(v.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(res)))
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the stddev of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math stddev",
|
||||
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the stddev of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math stddev",
|
||||
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573").expect("Could not convert to decimal from string")).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the sample stddev of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math stddev -s",
|
||||
result: Some(vec![UntaggedValue::decimal(BigDecimal::from_str("1.581138830084189665999446772216359266859777569662608413428752426396297219319619110672124054189650148").expect("Could not convert to decimal from string")).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::compute_values;
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::FromPrimitive;
|
||||
use nu_data::value::compute_values;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Operator, Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value,
|
||||
@ -81,30 +81,32 @@ impl WholeStreamCommand for SubCommand {
|
||||
entries: column_totals,
|
||||
})
|
||||
.into_untagged_value())
|
||||
};
|
||||
}?;
|
||||
|
||||
match res {
|
||||
Ok(v) => {
|
||||
if v.value.is_table() {
|
||||
Ok(OutputStream::from(
|
||||
v.table_entries()
|
||||
.map(|v| ReturnSuccess::value(v.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(v)))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
if res.value.is_table() {
|
||||
Ok(OutputStream::from(
|
||||
res.table_entries()
|
||||
.map(|v| ReturnSuccess::value(v.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(res)))
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance",
|
||||
result: Some(vec![UntaggedValue::decimal(2).into()]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance",
|
||||
result: Some(vec![UntaggedValue::decimal(2).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the sample variance of a list of numbers",
|
||||
example: "echo [1 2 3 4 5] | math variance -s",
|
||||
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::value::merge_values;
|
||||
use crate::prelude::*;
|
||||
use nu_data::value::merge_values;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::base::select_fields;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::span_for_spanned_list;
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -106,7 +106,7 @@ async fn operate(
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let after_span = span_for_spanned_list(after.members().iter().map(|p| p.span));
|
||||
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if after.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
@ -154,7 +154,7 @@ async fn operate(
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
let before_span = span_for_spanned_list(before.members().iter().map(|p| p.span));
|
||||
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
|
||||
|
||||
if before.members().len() == 1 {
|
||||
let keys = column_paths
|
||||
@ -207,7 +207,7 @@ fn move_after(
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let from_fields = span_for_spanned_list(from.members().iter().map(|p| p.span));
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
@ -270,7 +270,7 @@ fn move_before(
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let from_fields = span_for_spanned_list(from.members().iter().map(|p| p.span));
|
||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||
let from = if let Some((last, _)) = from.split_last() {
|
||||
last.as_string()
|
||||
} else {
|
||||
|
60
crates/nu-cli/src/commands/path/dirname.rs
Normal file
60
crates/nu-cli/src/commands/path/dirname.rs
Normal file
@ -0,0 +1,60 @@
|
||||
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 PathDirname;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathDirname {
|
||||
fn name(&self) -> &str {
|
||||
"path dirname"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path dirname").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the dirname 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 dirname of a path",
|
||||
example: "echo '/home/joe/test.txt' | path dirname",
|
||||
result: Some(vec![Value::from("/home/joe")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.parent() {
|
||||
Some(dirname) => dirname.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathDirname;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(PathDirname {})
|
||||
}
|
||||
}
|
61
crates/nu-cli/src/commands/path/filestem.rs
Normal file
61
crates/nu-cli/src/commands/path/filestem.rs
Normal file
@ -0,0 +1,61 @@
|
||||
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 PathFilestem;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathFilestem {
|
||||
fn name(&self) -> &str {
|
||||
"path filestem"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path filestem")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the filestem 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 filestem of a path",
|
||||
example: "echo '/home/joe/test.txt' | path filestem",
|
||||
result: Some(vec![Value::from("test")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.file_stem() {
|
||||
Some(stem) => stem.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathFilestem;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(PathFilestem {})
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
mod basename;
|
||||
mod command;
|
||||
mod dirname;
|
||||
mod exists;
|
||||
mod expand;
|
||||
mod extension;
|
||||
mod filestem;
|
||||
mod r#type;
|
||||
|
||||
use crate::prelude::*;
|
||||
@ -13,9 +15,11 @@ use std::path::Path;
|
||||
|
||||
pub use basename::PathBasename;
|
||||
pub use command::Path as PathCommand;
|
||||
pub use dirname::PathDirname;
|
||||
pub use exists::PathExists;
|
||||
pub use expand::PathExpand;
|
||||
pub use extension::PathExtension;
|
||||
pub use filestem::PathFilestem;
|
||||
pub use r#type::PathType;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::files::get_file_type;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::filesystem_shell::get_file_type;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::base::reject_fields;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::reject_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
@ -23,7 +23,7 @@ impl WholeStreamCommand for Reject {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove the given columns from the table."
|
||||
"Remove the given columns from the table. If you want to remove rows, try 'drop'."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -9,7 +9,7 @@ use nu_protocol::{hir::Block, Signature, SyntaxShape};
|
||||
#[derive(new, Clone)]
|
||||
pub struct AliasCommand {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
args: Vec<(String, SyntaxShape)>,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ impl WholeStreamCommand for AliasCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
let mut alias = Signature::build(&self.name);
|
||||
|
||||
for arg in &self.args {
|
||||
alias = alias.optional(arg, SyntaxShape::Any, "");
|
||||
for (arg, shape) in &self.args {
|
||||
alias = alias.optional(arg, *shape, "");
|
||||
}
|
||||
|
||||
alias
|
||||
@ -53,7 +53,7 @@ impl WholeStreamCommand for AliasCommand {
|
||||
for (pos, arg) in positional.iter().enumerate() {
|
||||
scope
|
||||
.vars
|
||||
.insert(alias_command.args[pos].to_string(), arg.clone());
|
||||
.insert(alias_command.args[pos].0.to_string(), arg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ impl WholeStreamCommand for Size {
|
||||
"lines".to_string() => UntaggedValue::int(0).into(),
|
||||
"words".to_string() => UntaggedValue::int(7).into(),
|
||||
"chars".to_string() => UntaggedValue::int(38).into(),
|
||||
"max length".to_string() => UntaggedValue::int(38).into(),
|
||||
"bytes".to_string() => UntaggedValue::int(38).into(),
|
||||
})
|
||||
.into()]),
|
||||
}]
|
||||
@ -96,7 +96,7 @@ fn count(contents: &str, tag: impl Into<Tag>) -> Value {
|
||||
dict.insert_untagged("lines", UntaggedValue::int(lines));
|
||||
dict.insert_untagged("words", UntaggedValue::int(words));
|
||||
dict.insert_untagged("chars", UntaggedValue::int(chars));
|
||||
dict.insert_untagged("max length", UntaggedValue::int(bytes));
|
||||
dict.insert_untagged("bytes", UntaggedValue::int(bytes));
|
||||
|
||||
dict.into_value()
|
||||
}
|
||||
|
86
crates/nu-cli/src/commands/sleep.rs
Normal file
86
crates/nu-cli/src/commands/sleep.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
pub struct Sleep;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SleepArgs {
|
||||
pub dur: Tagged<u64>,
|
||||
pub rest: Vec<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Sleep {
|
||||
fn name(&self) -> &str {
|
||||
"sleep"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sleep")
|
||||
.required("duration", SyntaxShape::Unit, "time to sleep")
|
||||
.rest(SyntaxShape::Unit, "additional time")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"delay for a specified amount of time"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sleep(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sleep for 1sec",
|
||||
example: "sleep 1sec",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Sleep for 3sec",
|
||||
example: "sleep 1sec 1sec 1sec",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn sleep(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let (SleepArgs { dur, rest }, ..) = args.process(®istry).await?;
|
||||
|
||||
let total_dur = dur.item + rest.iter().map(|val| val.item).sum::<u64>();
|
||||
let total_dur = time::Duration::from_nanos(total_dur);
|
||||
thread::sleep(total_dur);
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Sleep;
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
let start = Instant::now();
|
||||
test_examples(Sleep {});
|
||||
let elapsed = start.elapsed();
|
||||
println!("{:?}", elapsed);
|
||||
assert!(elapsed >= std::time::Duration::from_secs(4));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::base::coerce_compare;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::coerce_compare;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
@ -135,6 +135,24 @@ pub fn sort(
|
||||
} => {
|
||||
let should_sort_case_insensitively = insensitive && vec.iter().all(|x| x.is_string());
|
||||
|
||||
if let Some(values) = vec
|
||||
.windows(2)
|
||||
.map(|elem| coerce_compare(&elem[0], &elem[1]))
|
||||
.find(|elem| elem.is_err())
|
||||
{
|
||||
let (type_1, type_2) = values
|
||||
.err()
|
||||
.expect("An error ocourred in the checking of types");
|
||||
return Err(ShellError::labeled_error(
|
||||
"Not all values can be compared",
|
||||
format!(
|
||||
"Unable to sort values, as \"{}\" cannot compare against \"{}\"",
|
||||
type_1, type_2
|
||||
),
|
||||
tag,
|
||||
));
|
||||
}
|
||||
|
||||
vec.sort_by(|a, b| {
|
||||
if should_sort_case_insensitively {
|
||||
let lowercase_a_string = a.expect_string().to_ascii_lowercase();
|
||||
|
@ -86,12 +86,12 @@ pub fn split(
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::split(&values, &Some(block), &name)
|
||||
nu_data::utils::split(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| as_string(row));
|
||||
|
||||
crate::utils::data::split(&values, &Some(block), &name)
|
||||
nu_data::utils::split(&values, &Some(block), &name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,7 +124,7 @@ pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::split;
|
||||
use crate::utils::data::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::*;
|
||||
|
||||
|
74
crates/nu-cli/src/commands/str_/case/camel_case.rs
Normal file
74
crates/nu-cli/src/commands/str_/case/camel_case.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::operate;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use inflector::cases::camelcase::to_camel_case;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str camel-case"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str camel-case").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text to camelCase by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts a string to camelCase"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry, &to_camel_case).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "convert a string to camelCase",
|
||||
example: "echo 'NuShell' | str camel-case",
|
||||
result: Some(vec![Value::from("nuShell")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{to_camel_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
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 camel_case_from_kebab() {
|
||||
let word = string("this-is-the-first-case");
|
||||
let expected = string("thisIsTheFirstCase");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_camel_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn camel_case_from_snake() {
|
||||
let word = string("this_is_the_second_case");
|
||||
let expected = string("thisIsTheSecondCase");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_camel_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
74
crates/nu-cli/src/commands/str_/case/kebab_case.rs
Normal file
74
crates/nu-cli/src/commands/str_/case/kebab_case.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::operate;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use inflector::cases::kebabcase::to_kebab_case;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str kebab-case"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str kebab-case").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text to kebab-case by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts a string to kebab-case"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry, &to_kebab_case).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "convert a string to kebab-case",
|
||||
example: "echo 'NuShell' | str kebab-case",
|
||||
result: Some(vec![Value::from("nu-shell")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{to_kebab_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
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 kebab_case_from_camel() {
|
||||
let word = string("thisIsTheFirstCase");
|
||||
let expected = string("this-is-the-first-case");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_kebab_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn kebab_case_from_screaming_snake() {
|
||||
let word = string("THIS_IS_THE_SECOND_CASE");
|
||||
let expected = string("this-is-the-second-case");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_kebab_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
79
crates/nu-cli/src/commands/str_/case/mod.rs
Normal file
79
crates/nu-cli/src/commands/str_/case/mod.rs
Normal file
@ -0,0 +1,79 @@
|
||||
pub mod camel_case;
|
||||
pub mod kebab_case;
|
||||
pub mod pascal_case;
|
||||
pub mod screaming_snake_case;
|
||||
pub mod snake_case;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub use camel_case::SubCommand as CamelCase;
|
||||
pub use pascal_case::SubCommand as PascalCase;
|
||||
pub use screaming_snake_case::SubCommand as ScreamingSnakeCase;
|
||||
pub use snake_case::SubCommand as SnakeCase;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub async fn operate<F>(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
case_operation: &'static F,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
F: Fn(&str) -> String + Send + Sync + 'static,
|
||||
{
|
||||
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(), &case_operation)?)
|
||||
} 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(), &case_operation)),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
pub fn action<F>(
|
||||
input: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
case_operation: &F,
|
||||
) -> Result<Value, ShellError>
|
||||
where
|
||||
F: Fn(&str) -> String + Send + Sync + 'static,
|
||||
{
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
Ok(UntaggedValue::string(case_operation(s)).into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
74
crates/nu-cli/src/commands/str_/case/pascal_case.rs
Normal file
74
crates/nu-cli/src/commands/str_/case/pascal_case.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::operate;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use inflector::cases::pascalcase::to_pascal_case;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str pascal-case"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str pascal-case").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text to PascalCase by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts a string to PascalCase"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry, &to_pascal_case).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "convert a string to PascalCase",
|
||||
example: "echo 'nu-shell' | str pascal-case",
|
||||
result: Some(vec![Value::from("NuShell")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{to_pascal_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
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 pascal_case_from_kebab() {
|
||||
let word = string("this-is-the-first-case");
|
||||
let expected = string("ThisIsTheFirstCase");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_pascal_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn pascal_case_from_snake() {
|
||||
let word = string("this_is_the_second_case");
|
||||
let expected = string("ThisIsTheSecondCase");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_pascal_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
74
crates/nu-cli/src/commands/str_/case/screaming_snake_case.rs
Normal file
74
crates/nu-cli/src/commands/str_/case/screaming_snake_case.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::operate;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use inflector::cases::screamingsnakecase::to_screaming_snake_case;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str screaming-snake-case"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str screaming-snake-case").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text to SCREAMING_SNAKE_CASE by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts a string to SCREAMING_SNAKE_CASE"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry, &to_screaming_snake_case).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "convert a string to SCREAMING_SNAKE_CASE",
|
||||
example: "echo 'NuShell' | str screaming-snake-case",
|
||||
result: Some(vec![Value::from("NU_SHELL")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{to_screaming_snake_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
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 snake_case_from_kebab() {
|
||||
let word = string("this-is-the-first-case");
|
||||
let expected = string("THIS_IS_THE_FIRST_CASE");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_screaming_snake_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn snake_case_from_snake() {
|
||||
let word = string("this_is_the_second_case");
|
||||
let expected = string("THIS_IS_THE_SECOND_CASE");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_screaming_snake_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
74
crates/nu-cli/src/commands/str_/case/snake_case.rs
Normal file
74
crates/nu-cli/src/commands/str_/case/snake_case.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::operate;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use inflector::cases::snakecase::to_snake_case;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str snake-case"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str snake-case").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text to snake_case by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts a string to snake_case"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry, &to_snake_case).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "convert a string to snake_case",
|
||||
example: "echo 'NuShell' | str snake-case",
|
||||
result: Some(vec![Value::from("nu_shell")]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{to_snake_case, SubCommand};
|
||||
use crate::commands::str_::case::action;
|
||||
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 snake_case_from_kebab() {
|
||||
let word = string("this-is-the-first-case");
|
||||
let expected = string("this_is_the_first_case");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_snake_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn snake_case_from_camel() {
|
||||
let word = string("thisIsTheSecondCase");
|
||||
let expected = string("this_is_the_second_case");
|
||||
|
||||
let actual = action(&word, Tag::unknown(), &to_snake_case).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -112,7 +112,7 @@ async fn operate(
|
||||
}
|
||||
|
||||
// TODO If you're using the with-system-locale feature and you're on Windows, Clang 3.9 or higher is also required.
|
||||
fn action(
|
||||
pub fn action(
|
||||
input: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
digits: Option<u64>,
|
||||
|
@ -13,6 +13,7 @@ struct Arguments {
|
||||
pattern: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
range: Option<Value>,
|
||||
end: bool,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
@ -43,6 +44,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"optional start and/or end index",
|
||||
Some('r'),
|
||||
)
|
||||
.switch("end", "search from the end of the string", Some('e'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -80,10 +82,15 @@ impl WholeStreamCommand for SubCommand {
|
||||
result: Some(vec![UntaggedValue::int(2).into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "Alternativly you can use this form",
|
||||
description: "Alternatively you can use this form",
|
||||
example: "echo '123456' | str index-of '3' -r [1 4]",
|
||||
result: Some(vec![UntaggedValue::int(2).into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "Returns index of pattern in string",
|
||||
example: "echo '/this/is/some/path/file.txt' | str index-of '/' -e",
|
||||
result: Some(vec![UntaggedValue::int(18).into_untagged_value()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -99,6 +106,7 @@ async fn operate(
|
||||
pattern,
|
||||
rest,
|
||||
range,
|
||||
end,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
@ -110,7 +118,7 @@ async fn operate(
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &pattern, &range, v.tag())?)
|
||||
ReturnSuccess::value(action(&v, &pattern, &range, end, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
@ -119,7 +127,7 @@ async fn operate(
|
||||
let pattern = pattern.clone();
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &pattern, &range, old.tag())),
|
||||
Box::new(move |old| action(old, &pattern, &range, end, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -133,6 +141,7 @@ fn action(
|
||||
input: &Value,
|
||||
pattern: &str,
|
||||
range: &Value,
|
||||
end: bool,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let r = process_range(&input, &range)?;
|
||||
@ -142,7 +151,14 @@ fn action(
|
||||
let start_index = r.0 as usize;
|
||||
let end_index = r.1 as usize;
|
||||
|
||||
if let Some(result) = s[start_index..end_index].find(pattern) {
|
||||
if end {
|
||||
if let Some(result) = s[start_index..end_index].rfind(pattern) {
|
||||
Ok(UntaggedValue::int(result + start_index).into_value(tag))
|
||||
} else {
|
||||
let not_found = -1;
|
||||
Ok(UntaggedValue::int(not_found).into_value(tag))
|
||||
}
|
||||
} else if let Some(result) = s[start_index..end_index].find(pattern) {
|
||||
Ok(UntaggedValue::int(result + start_index).into_value(tag))
|
||||
} else {
|
||||
let not_found = -1;
|
||||
@ -246,22 +262,24 @@ mod tests {
|
||||
fn returns_index_of_substring() {
|
||||
let word = string("Cargo.tomL");
|
||||
let pattern = ".tomL";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String("".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int(5.into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn index_of_does_not_exist_in_string() {
|
||||
let word = string("Cargo.tomL");
|
||||
let pattern = "Lm";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String("".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -269,22 +287,24 @@ mod tests {
|
||||
fn returns_index_of_next_substring() {
|
||||
let word = string("Cargo.Cargo");
|
||||
let pattern = "Cargo";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String("1,".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int(6.into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn index_does_not_exist_due_to_end_index() {
|
||||
let word = string("Cargo.Banana");
|
||||
let pattern = "Banana";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String(",5".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -292,11 +312,12 @@ mod tests {
|
||||
fn returns_index_of_nums_in_middle_due_to_index_limit_from_both_ends() {
|
||||
let word = string("123123123");
|
||||
let pattern = "123";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String("2,6".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int(3.into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@ -304,11 +325,12 @@ mod tests {
|
||||
fn index_does_not_exists_due_to_strict_bounds() {
|
||||
let word = string("123456");
|
||||
let pattern = "1";
|
||||
let end = false;
|
||||
let index_of_bounds =
|
||||
UntaggedValue::Primitive(Primitive::String("2,4".to_string())).into_untagged_value();
|
||||
let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value();
|
||||
|
||||
let actual = action(&word, &pattern, &index_of_bounds, Tag::unknown()).unwrap();
|
||||
let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
mod capitalize;
|
||||
mod case;
|
||||
mod collect;
|
||||
mod command;
|
||||
mod contains;
|
||||
mod downcase;
|
||||
mod ends_with;
|
||||
mod find_replace;
|
||||
mod from;
|
||||
pub mod from;
|
||||
mod index_of;
|
||||
mod length;
|
||||
mod reverse;
|
||||
@ -19,6 +20,11 @@ mod trim;
|
||||
mod upcase;
|
||||
|
||||
pub use capitalize::SubCommand as StrCapitalize;
|
||||
pub use case::camel_case::SubCommand as StrCamelCase;
|
||||
pub use case::kebab_case::SubCommand as StrKebabCase;
|
||||
pub use case::pascal_case::SubCommand as StrPascalCase;
|
||||
pub use case::screaming_snake_case::SubCommand as StrScreamingSnakeCase;
|
||||
pub use case::snake_case::SubCommand as StrSnakeCase;
|
||||
pub use collect::SubCommand as StrCollect;
|
||||
pub use command::Command as Str;
|
||||
pub use contains::SubCommand as StrContains;
|
||||
|
@ -1,18 +1,21 @@
|
||||
use crate::commands::table::options::{ConfigExtensions, NuConfig as TableConfiguration};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::{format_leaf, style_leaf};
|
||||
use crate::prelude::*;
|
||||
use crate::primitive::get_color_config;
|
||||
use nu_data::value::{format_leaf, style_leaf};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_table::{draw_table, Alignment, StyledString, TextStyle, Theme};
|
||||
use nu_table::{draw_table, Alignment, StyledString, TextStyle};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
const STREAM_PAGE_SIZE: usize = 1000;
|
||||
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
|
||||
|
||||
pub struct Table;
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Table {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"table"
|
||||
}
|
||||
@ -35,106 +38,41 @@ impl WholeStreamCommand for Table {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
table(args, registry).await
|
||||
table(TableConfiguration::new(), args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_color(s: String) -> Option<ansi_term::Color> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green),
|
||||
"r" | "red" => Some(ansi_term::Color::Red),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue),
|
||||
"b" | "black" => Some(ansi_term::Color::Black),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan),
|
||||
"w" | "white" => Some(ansi_term::Color::White),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_list(values: &[Value], starting_idx: usize) -> nu_table::Table {
|
||||
let config = crate::data::config::config(Tag::unknown());
|
||||
|
||||
let header_style = if let Ok(config) = &config {
|
||||
let header_align = config.get("header_align").map_or(Alignment::Left, |a| {
|
||||
a.as_string()
|
||||
.map_or(Alignment::Center, |a| match a.to_lowercase().as_str() {
|
||||
"center" | "c" => Alignment::Center,
|
||||
"right" | "r" => Alignment::Right,
|
||||
_ => Alignment::Center,
|
||||
})
|
||||
});
|
||||
|
||||
let header_color = match config.get("header_color") {
|
||||
Some(c) => match c.as_string() {
|
||||
Ok(color) => str_to_color(color.to_lowercase()).unwrap_or(ansi_term::Color::Green),
|
||||
_ => ansi_term::Color::Green,
|
||||
},
|
||||
_ => ansi_term::Color::Green,
|
||||
};
|
||||
|
||||
let header_bold = config
|
||||
.get("header_bold")
|
||||
.map(|x| x.as_bool().unwrap_or(true))
|
||||
.unwrap_or(true);
|
||||
|
||||
TextStyle {
|
||||
alignment: header_align,
|
||||
color: Some(header_color),
|
||||
is_bold: header_bold,
|
||||
}
|
||||
} else {
|
||||
TextStyle::default_header()
|
||||
};
|
||||
|
||||
pub fn from_list(
|
||||
values: &[Value],
|
||||
configuration: &TableConfiguration,
|
||||
starting_idx: usize,
|
||||
color_hm: &HashMap<String, ansi_term::Style>,
|
||||
) -> nu_table::Table {
|
||||
let header_style = configuration.header_style();
|
||||
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
|
||||
.into_iter()
|
||||
.map(|x| StyledString::new(x, header_style.clone()))
|
||||
.map(|x| StyledString::new(x, header_style))
|
||||
.collect();
|
||||
let entries = values_to_entries(values, &mut headers, starting_idx);
|
||||
|
||||
if let Ok(config) = config {
|
||||
if let Some(style) = config.get("table_mode") {
|
||||
if let Ok(table_mode) = style.as_string() {
|
||||
if table_mode == "light" {
|
||||
return nu_table::Table {
|
||||
headers,
|
||||
data: entries,
|
||||
theme: Theme::light(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let entries = values_to_entries(values, &mut headers, configuration, starting_idx, &color_hm);
|
||||
nu_table::Table {
|
||||
headers,
|
||||
data: entries,
|
||||
theme: Theme::compact(),
|
||||
}
|
||||
}
|
||||
|
||||
fn are_table_indexes_disabled() -> bool {
|
||||
let config = crate::data::config::config(Tag::unknown());
|
||||
match config {
|
||||
Ok(config) => {
|
||||
let disable_indexes = config.get("disable_table_indexes");
|
||||
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||
}
|
||||
_ => false,
|
||||
theme: configuration.table_mode(),
|
||||
}
|
||||
}
|
||||
|
||||
fn values_to_entries(
|
||||
values: &[Value],
|
||||
headers: &mut Vec<StyledString>,
|
||||
configuration: &TableConfiguration,
|
||||
starting_idx: usize,
|
||||
color_hm: &HashMap<String, ansi_term::Style>,
|
||||
) -> Vec<Vec<StyledString>> {
|
||||
let disable_indexes = are_table_indexes_disabled();
|
||||
let disable_indexes = configuration.disabled_indexes();
|
||||
let mut entries = vec![];
|
||||
|
||||
if headers.is_empty() {
|
||||
headers.push(StyledString::new("".to_string(), TextStyle::basic()));
|
||||
headers.push(StyledString::new("".to_string(), TextStyle::basic_left()));
|
||||
}
|
||||
|
||||
for (idx, value) in values.iter().enumerate() {
|
||||
@ -148,11 +86,11 @@ fn values_to_entries(
|
||||
..
|
||||
} => StyledString::new(
|
||||
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
|
||||
style_leaf(&UntaggedValue::nothing()),
|
||||
style_leaf(&UntaggedValue::nothing(), &color_hm),
|
||||
),
|
||||
_ => StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
style_leaf(value),
|
||||
style_leaf(value, &color_hm),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
@ -165,12 +103,12 @@ fn values_to_entries(
|
||||
|
||||
StyledString::new(
|
||||
format_leaf(data.borrow()).plain_string(100_000),
|
||||
style_leaf(data.borrow()),
|
||||
style_leaf(data.borrow(), &color_hm),
|
||||
)
|
||||
}
|
||||
_ => StyledString::new(
|
||||
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
|
||||
style_leaf(&UntaggedValue::nothing()),
|
||||
style_leaf(&UntaggedValue::nothing(), &color_hm),
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -178,16 +116,22 @@ fn values_to_entries(
|
||||
.collect();
|
||||
|
||||
// Indices are green, bold, right-aligned:
|
||||
// unless we change them :)
|
||||
if !disable_indexes {
|
||||
row.insert(
|
||||
0,
|
||||
StyledString::new(
|
||||
(starting_idx + idx).to_string(),
|
||||
TextStyle {
|
||||
alignment: Alignment::Right,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
TextStyle::new().alignment(Alignment::Right).style(
|
||||
color_hm
|
||||
.get("index_color")
|
||||
.unwrap_or(
|
||||
&ansi_term::Style::default()
|
||||
.bold()
|
||||
.fg(ansi_term::Color::Green),
|
||||
)
|
||||
.to_owned(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -200,11 +144,10 @@ fn values_to_entries(
|
||||
0,
|
||||
StyledString::new(
|
||||
"#".to_owned(),
|
||||
TextStyle {
|
||||
alignment: Alignment::Center,
|
||||
color: Some(ansi_term::Color::Green),
|
||||
is_bold: true,
|
||||
},
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(ansi_term::Color::Green)
|
||||
.bold(Some(true)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -212,12 +155,22 @@ fn values_to_entries(
|
||||
entries
|
||||
}
|
||||
|
||||
async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
async fn table(
|
||||
configuration: TableConfiguration,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let mut args = args.evaluate_once(®istry).await?;
|
||||
let mut finished = false;
|
||||
// Ideally, get_color_config would get all the colors configured in the config.toml
|
||||
// and create a style based on those settings. However, there are few places where
|
||||
// this just won't work right now, like header styling, because a style needs to know
|
||||
// more than just color, it needs fg & bg color, bold, dimmed, italic, underline,
|
||||
// blink, reverse, hidden, strikethrough and most of those aren't available in the
|
||||
// config.toml.... yet.
|
||||
let color_hm = get_color_config();
|
||||
|
||||
// let host = args.host.clone();
|
||||
let mut start_number = match args.get("start_number") {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(i)),
|
||||
@ -289,9 +242,9 @@ async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
|
||||
let input: Vec<Value> = new_input.into();
|
||||
|
||||
if !input.is_empty() {
|
||||
let t = from_list(&input, start_number);
|
||||
let t = from_list(&input, &configuration, start_number, &color_hm);
|
||||
|
||||
draw_table(&t, term_width);
|
||||
draw_table(&t, term_width, &color_hm);
|
||||
}
|
||||
|
||||
start_number += input.len();
|
4
crates/nu-cli/src/commands/table/mod.rs
Normal file
4
crates/nu-cli/src/commands/table/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod command;
|
||||
mod options;
|
||||
|
||||
pub use command::Command as Table;
|
107
crates/nu-cli/src/commands/table/options.rs
Normal file
107
crates/nu-cli/src/commands/table/options.rs
Normal file
@ -0,0 +1,107 @@
|
||||
pub use nu_data::config::NuConfig;
|
||||
use nu_data::primitive::lookup_ansi_color_style;
|
||||
use nu_protocol::Value;
|
||||
use nu_table::{Alignment, TextStyle};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait ConfigExtensions: Debug + Send {
|
||||
fn table_mode(&self) -> nu_table::Theme;
|
||||
fn disabled_indexes(&self) -> bool;
|
||||
fn header_style(&self) -> TextStyle;
|
||||
}
|
||||
|
||||
pub fn header_alignment_from_value(align_value: Option<&Value>) -> nu_table::Alignment {
|
||||
match align_value {
|
||||
Some(v) => match v
|
||||
.as_string()
|
||||
.unwrap_or_else(|_| "none".to_string())
|
||||
.as_ref()
|
||||
{
|
||||
"l" | "left" => nu_table::Alignment::Left,
|
||||
"c" | "center" => nu_table::Alignment::Center,
|
||||
"r" | "right" => nu_table::Alignment::Right,
|
||||
_ => nu_table::Alignment::Center,
|
||||
},
|
||||
_ => nu_table::Alignment::Center,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_from_key_and_subkey(config: &NuConfig, key: &str, subkey: &str) -> Option<Value> {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
if let Some(config_vars) = vars.get(key) {
|
||||
for (kee, value) in config_vars.row_entries() {
|
||||
if kee == subkey {
|
||||
return Some(value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn header_bold_from_value(bold_value: Option<&Value>) -> bool {
|
||||
bold_value
|
||||
.map(|x| x.as_bool().unwrap_or(true))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
vars.get("table_mode")
|
||||
.map_or(nu_table::Theme::compact(), |mode| match mode.as_string() {
|
||||
Ok(m) if m == "basic" => nu_table::Theme::basic(),
|
||||
Ok(m) if m == "compact" => nu_table::Theme::compact(),
|
||||
Ok(m) if m == "light" => nu_table::Theme::light(),
|
||||
Ok(m) if m == "thin" => nu_table::Theme::thin(),
|
||||
Ok(m) if m == "with_love" => nu_table::Theme::with_love(),
|
||||
Ok(m) if m == "compact_double" => nu_table::Theme::compact_double(),
|
||||
_ => nu_table::Theme::compact(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disabled_indexes(config: &NuConfig) -> bool {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
vars.get("disable_table_indexes")
|
||||
.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||
}
|
||||
|
||||
impl ConfigExtensions for NuConfig {
|
||||
fn header_style(&self) -> TextStyle {
|
||||
// FIXME: I agree, this is the long way around, please suggest and alternative.
|
||||
let head_color = get_color_from_key_and_subkey(self, "color_config", "header_color");
|
||||
let head_color_style = match head_color {
|
||||
Some(s) => {
|
||||
lookup_ansi_color_style(s.as_string().unwrap_or_else(|_| "green".to_string()))
|
||||
}
|
||||
None => ansi_term::Color::Green.normal(),
|
||||
};
|
||||
let head_bold = get_color_from_key_and_subkey(self, "color_config", "header_bold");
|
||||
let head_bold_bool = match head_bold {
|
||||
Some(b) => header_bold_from_value(Some(&b)),
|
||||
None => true,
|
||||
};
|
||||
let head_align = get_color_from_key_and_subkey(self, "color_config", "header_align");
|
||||
let head_alignment = match head_align {
|
||||
Some(a) => header_alignment_from_value(Some(&a)),
|
||||
None => Alignment::Center,
|
||||
};
|
||||
|
||||
TextStyle::new()
|
||||
.alignment(head_alignment)
|
||||
.bold(Some(head_bold_bool))
|
||||
.fg(head_color_style
|
||||
.foreground
|
||||
.unwrap_or(ansi_term::Color::Green))
|
||||
}
|
||||
|
||||
fn table_mode(&self) -> nu_table::Theme {
|
||||
table_mode(self)
|
||||
}
|
||||
|
||||
fn disabled_indexes(&self) -> bool {
|
||||
disabled_indexes(self)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{AnchorLocation, Tagged};
|
||||
@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HtmlThemes {
|
||||
@ -183,6 +182,7 @@ fn get_theme_from_asset_file(
|
||||
Ok(convert_html_theme_to_hash_map(is_dark, th))
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get_asset_by_name_as_html_themes(
|
||||
zip_name: &str,
|
||||
json_name: &str,
|
||||
@ -194,11 +194,20 @@ fn get_asset_by_name_as_html_themes(
|
||||
Cow::Owned(bytes) => bytes,
|
||||
};
|
||||
let reader = std::io::Cursor::new(asset);
|
||||
let mut archive = zip::ZipArchive::new(reader)?;
|
||||
let mut zip_file = archive.by_name(json_name)?;
|
||||
let mut contents = String::new();
|
||||
zip_file.read_to_string(&mut contents)?;
|
||||
Ok(serde_json::from_str(&contents)?)
|
||||
#[cfg(feature = "zip")]
|
||||
{
|
||||
use std::io::Read;
|
||||
let mut archive = zip::ZipArchive::new(reader)?;
|
||||
let mut zip_file = archive.by_name(json_name)?;
|
||||
let mut contents = String::new();
|
||||
zip_file.read_to_string(&mut contents)?;
|
||||
Ok(serde_json::from_str(&contents)?)
|
||||
}
|
||||
#[cfg(not(feature = "zip"))]
|
||||
{
|
||||
let th = HtmlThemes::default();
|
||||
Ok(th)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let th = HtmlThemes::default();
|
||||
|
@ -240,7 +240,7 @@ async fn to_json(
|
||||
|
||||
ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::String(serde_json_string))
|
||||
.into_value(&name_tag),
|
||||
.into_value(&value.tag),
|
||||
)
|
||||
}
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
|
@ -10,7 +10,8 @@ pub struct Touch;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TouchArgs {
|
||||
pub target: Tagged<PathBuf>,
|
||||
target: Tagged<PathBuf>,
|
||||
rest: Vec<Tagged<PathBuf>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -19,14 +20,16 @@ impl WholeStreamCommand for Touch {
|
||||
"touch"
|
||||
}
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("touch").required(
|
||||
"filename",
|
||||
SyntaxShape::Path,
|
||||
"the path of the file you want to create",
|
||||
)
|
||||
Signature::build("touch")
|
||||
.required(
|
||||
"filename",
|
||||
SyntaxShape::Path,
|
||||
"the path of the file you want to create",
|
||||
)
|
||||
.rest(SyntaxShape::Path, "additional files to create")
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
"creates a file"
|
||||
"creates one or more files"
|
||||
}
|
||||
async fn run(
|
||||
&self,
|
||||
@ -37,26 +40,39 @@ impl WholeStreamCommand for Touch {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates \"fixture.json\"",
|
||||
example: "touch fixture.json",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates \"fixture.json\"",
|
||||
example: "touch fixture.json",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Creates files a, b and c",
|
||||
example: "touch a b c",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn touch(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let (TouchArgs { target }, _) = args.process(®istry).await?;
|
||||
let (TouchArgs { target, rest }, _) = 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,
|
||||
)),
|
||||
for item in vec![target].into_iter().chain(rest.into_iter()) {
|
||||
match OpenOptions::new().write(true).create(true).open(&item) {
|
||||
Ok(_) => continue,
|
||||
Err(err) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"File Error",
|
||||
err.to_string(),
|
||||
&item.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use indexmap::indexmap;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Signature;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Uniq;
|
||||
|
||||
@ -28,6 +29,36 @@ impl WholeStreamCommand for Uniq {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
uniq(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove duplicate rows of a list/table",
|
||||
example: "echo [2 3 3 4] | uniq",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(2).into(),
|
||||
UntaggedValue::int(3).into(),
|
||||
UntaggedValue::int(4).into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Remove duplicate rows and show counts of a list/table",
|
||||
example: "echo [1 2 2] | uniq -c",
|
||||
result: Some(vec![
|
||||
UntaggedValue::row(indexmap! {
|
||||
"value".to_string() => UntaggedValue::int(1).into(),
|
||||
"count".to_string() => UntaggedValue::int(1).into(),
|
||||
})
|
||||
.into(),
|
||||
UntaggedValue::row(indexmap! {
|
||||
"value".to_string() => UntaggedValue::int(2).into(),
|
||||
"count".to_string() => UntaggedValue::int(2).into(),
|
||||
})
|
||||
.into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn uniq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
@ -46,7 +77,7 @@ async fn uniq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
||||
|
||||
if should_show_count {
|
||||
for item in uniq_values {
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_protocol::Value;
|
||||
let value = {
|
||||
match item.0.value {
|
||||
UntaggedValue::Row(mut row) => {
|
||||
|
@ -3,7 +3,10 @@ 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_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::HasFallibleSpan;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
@ -54,13 +57,15 @@ async fn process_row(
|
||||
input: Value,
|
||||
mut replacement: Arc<Value>,
|
||||
field: Arc<ColumnPath>,
|
||||
tag: Arc<Tag>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = &*tag;
|
||||
let replacement = Arc::make_mut(&mut replacement);
|
||||
|
||||
Ok(match replacement {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
..
|
||||
tag: block_tag,
|
||||
} => {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
@ -77,60 +82,74 @@ async fn process_row(
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let values = stream.drain_vec().await;
|
||||
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
return Err(error.clone());
|
||||
}
|
||||
|
||||
let result = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
||||
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
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(
|
||||
} => 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,
|
||||
))),
|
||||
},
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
block_tag.clone(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
_ => match input {
|
||||
obj
|
||||
@
|
||||
replacement => match input {
|
||||
Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => match obj.replace_data_at_column_path(&field, replacement.clone()) {
|
||||
} => match scope
|
||||
.it
|
||||
.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,
|
||||
field.maybe_span().unwrap_or_else(|| tag.span),
|
||||
))),
|
||||
},
|
||||
Value { tag, .. } => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
Value { value: _, ref tag } => {
|
||||
match input.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",
|
||||
field.maybe_span().unwrap_or_else(|| tag.span),
|
||||
))),
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -140,6 +159,7 @@ async fn update(
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name_tag = Arc::new(raw_args.call_info.name_tag.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?;
|
||||
@ -148,13 +168,14 @@ async fn update(
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
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 {
|
||||
match process_row(scope, context, input, replacement, field, tag).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, UntaggedValue};
|
||||
|
||||
const GIT_COMMIT_HASH: &str = include_str!(concat!(env!("OUT_DIR"), "/git_commit_hash"));
|
||||
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
@ -48,6 +50,14 @@ pub fn version(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputS
|
||||
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
|
||||
);
|
||||
|
||||
let commit_hash = Some(GIT_COMMIT_HASH.trim()).filter(|x| !x.is_empty());
|
||||
if let Some(commit_hash) = commit_hash {
|
||||
indexmap.insert(
|
||||
"commit_hash".to_string(),
|
||||
UntaggedValue::string(commit_hash).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
indexmap.insert("features".to_string(), features_enabled(&tag).into_value());
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
@ -69,11 +79,6 @@ fn features_enabled(tag: impl Into<Tag>) -> TaggedListBuilder {
|
||||
names.push_untagged(UntaggedValue::string("trash"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "starship-prompt")]
|
||||
{
|
||||
names.push_untagged(UntaggedValue::string("starship"));
|
||||
}
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ impl WholeStreamCommand for Where {
|
||||
},
|
||||
Example {
|
||||
description: "List all files that were modified in the last two months",
|
||||
example: "ls | where modified <= 2M",
|
||||
example: "ls | where modified <= 2mon",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
144
crates/nu-cli/src/completion/command.rs
Normal file
144
crates/nu-cli/src/completion/command.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use std::iter::FromIterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
use crate::completion::{Context, Suggestion};
|
||||
use crate::context;
|
||||
|
||||
pub struct Completer;
|
||||
|
||||
impl Completer {
|
||||
pub fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
|
||||
let context: &context::Context = ctx.as_ref();
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(context.registry.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| v.starts_with(partial))
|
||||
.map(|v| Suggestion {
|
||||
replacement: format!("{} ", v),
|
||||
display: v,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if partial != "" {
|
||||
let path_completer = crate::completion::path::Completer;
|
||||
let path_results = path_completer.complete(ctx, partial);
|
||||
suggestions.extend(path_results.into_iter().filter(|suggestion| {
|
||||
// TODO better path abstractions to avoid a mess like this
|
||||
let path = {
|
||||
#[cfg(feature = "directories")]
|
||||
{
|
||||
let home_prefix = format!("~{}", std::path::MAIN_SEPARATOR);
|
||||
if let Some(mut home) = dirs::home_dir() {
|
||||
home.push(suggestion.replacement.replacen(&home_prefix, "", 1));
|
||||
home
|
||||
} else {
|
||||
PathBuf::from(&suggestion.replacement)
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "directories"))]
|
||||
{
|
||||
PathBuf::from(&suggestion.replacement)
|
||||
}
|
||||
};
|
||||
|
||||
path.is_dir() || is_executable(&path)
|
||||
}));
|
||||
}
|
||||
|
||||
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(';')
|
||||
// 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)
|
||||
}
|
412
crates/nu-cli/src/completion/engine.rs
Normal file
412
crates/nu-cli/src/completion/engine.rs
Normal file
@ -0,0 +1,412 @@
|
||||
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();
|
||||
result.append(&mut self.expression(&range.left));
|
||||
result.append(&mut self.expression(&range.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: &Commands) -> 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(|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::SignatureRegistry;
|
||||
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 SignatureRegistry for VecRegistry {
|
||||
fn has(&self, name: &str) -> bool {
|
||||
self.0.iter().any(|v| &v.name == name)
|
||||
}
|
||||
|
||||
fn get(&self, name: &str) -> Option<nu_protocol::Signature> {
|
||||
self.0.iter().find(|v| &v.name == name).map(Clone::clone)
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
mod completion_location {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::{classify_block, lite_parse, SignatureRegistry};
|
||||
|
||||
fn completion_location(
|
||||
line: &str,
|
||||
registry: &dyn SignatureRegistry,
|
||||
pos: usize,
|
||||
) -> Vec<LocationType> {
|
||||
let lite_block = match lite_parse(line, 0) {
|
||||
Ok(v) => v,
|
||||
Err(e) => e.partial.expect("lite_parse result"),
|
||||
};
|
||||
|
||||
let block = classify_block(&lite_block, registry);
|
||||
|
||||
super::completion_location(line, &block.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)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
33
crates/nu-cli/src/completion/flag.rs
Normal file
33
crates/nu-cli/src/completion/flag.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use crate::completion::{Context, Suggestion};
|
||||
use crate::context;
|
||||
|
||||
pub struct Completer;
|
||||
|
||||
impl Completer {
|
||||
pub fn complete(&self, ctx: &Context<'_>, cmd: String, partial: &str) -> Vec<Suggestion> {
|
||||
let context: &context::Context = ctx.as_ref();
|
||||
|
||||
if let Some(cmd) = context.registry.get_command(&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| v.starts_with(partial))
|
||||
.map(|v| Suggestion {
|
||||
replacement: format!("{} ", v),
|
||||
display: v,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,8 @@
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod engine;
|
||||
pub(crate) mod flag;
|
||||
pub(crate) mod path;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::context;
|
||||
@ -8,11 +13,11 @@ pub struct Suggestion {
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
pub struct Context<'a>(&'a context::Context, &'a rustyline::Context<'a>);
|
||||
pub struct Context<'a>(&'a context::Context);
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(a: &'a context::Context, b: &'a rustyline::Context<'a>) -> Context<'a> {
|
||||
Context(a, b)
|
||||
pub fn new(a: &'a context::Context) -> Context<'a> {
|
||||
Context(a)
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,12 +27,6 @@ impl<'a> AsRef<context::Context> for Context<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<rustyline::Context<'a>> for Context<'a> {
|
||||
fn as_ref(&self) -> &rustyline::Context<'a> {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Completer {
|
||||
fn complete(
|
||||
&self,
|
||||
@ -35,6 +34,4 @@ pub trait Completer {
|
||||
pos: usize,
|
||||
ctx: &Context<'_>,
|
||||
) -> Result<(usize, Vec<Suggestion>), ShellError>;
|
||||
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String>;
|
||||
}
|
60
crates/nu-cli/src/completion/path.rs
Normal file
60
crates/nu-cli/src/completion/path.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::completion::{Context, Suggestion};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub struct Completer;
|
||||
|
||||
impl Completer {
|
||||
pub fn complete(&self, _ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
|
||||
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 == "" {
|
||||
PathBuf::from(".")
|
||||
} else if base_dir_name == format!("~{}", SEP) {
|
||||
#[cfg(feature = "directories")]
|
||||
{
|
||||
dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"))
|
||||
}
|
||||
#[cfg(not(feature = "directories"))]
|
||||
{
|
||||
PathBuf::from("~")
|
||||
}
|
||||
} else {
|
||||
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 file_name.starts_with(partial) {
|
||||
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(Suggestion {
|
||||
replacement: path,
|
||||
display: file_name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
@ -228,10 +228,15 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.registry.get_command(name)
|
||||
}
|
||||
|
||||
pub(crate) fn is_command_registered(&self, name: &str) -> bool {
|
||||
self.registry.has(name)
|
||||
}
|
||||
|
||||
pub(crate) fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
||||
self.registry.expect_command(name)
|
||||
}
|
||||
|
@ -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,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(),
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user