mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
188 Commits
Author | SHA1 | Date | |
---|---|---|---|
55cab9eb4f | |||
b39dda0550 | |||
7c0a52a81e | |||
318d13ed58 | |||
21a3ceee92 | |||
a8f6a13239 | |||
b9f1371994 | |||
51c685aa99 | |||
9e39284de9 | |||
26899bc0f0 | |||
a74d05061d | |||
4140834e4c | |||
bd44bcee32 | |||
1e4678f929 | |||
fe5055cf29 | |||
d9d956e54f | |||
6c2c16a971 | |||
955a5ed8fb | |||
a59414203f | |||
631b067281 | |||
02bac0a326 | |||
7c8fb060f1 | |||
04c0e94349 | |||
2a946af81e | |||
18be6768c9 | |||
fbe61a06f6 | |||
7a4d6d64fd | |||
0eae9c49b0 | |||
d0bca1fb0f | |||
7c7e5112ea | |||
ec96e85d04 | |||
d60d71a697 | |||
6f0dd8e885 | |||
de99e35106 | |||
774be79321 | |||
721f704260 | |||
2846e3f5d9 | |||
b9ca3b2039 | |||
8ac572ed27 | |||
c4163c3621 | |||
1d7c909080 | |||
500683831c | |||
9a2fe7ec0c | |||
3ae3e3d23d | |||
fc07e849fe | |||
fadcdde7f8 | |||
d056bf070f | |||
2591050fbe | |||
e8dfd4ba39 | |||
383e874166 | |||
e8a2250ef8 | |||
440e12abc4 | |||
25ba6ea459 | |||
5ec226a416 | |||
a021b99614 | |||
e9de4c08b0 | |||
2e968d2557 | |||
bc6fa85a4b | |||
4e6c2c0fa1 | |||
7eadbd938d | |||
31a5de973d | |||
94fc8a1334 | |||
aa1cd7eba6 | |||
16faafb7a8 | |||
e376c2d0d4 | |||
c4dc61425d | |||
128f5bce30 | |||
82d69305b6 | |||
57a009b8e6 | |||
a2e6f5ebdb | |||
995dbd25b3 | |||
1d0d0425d4 | |||
51890baace | |||
4bca36f479 | |||
7d78f40bf6 | |||
131b5b56d7 | |||
fcd94efbd6 | |||
13257004bc | |||
8b193db0cb | |||
5537dce3cc | |||
fd5da62c66 | |||
927578a26f | |||
290c712cde | |||
2486492c4d | |||
7bf10b980c | |||
55c243a17b | |||
df526f73be | |||
29a77fd6ae | |||
be9ebd9e18 | |||
01f1208ad1 | |||
9dbb3e80fe | |||
a5c14ba7d4 | |||
4b11b283ac | |||
6fdfc84904 | |||
bff81f24aa | |||
ed515cbc0c | |||
87a6d7166c | |||
55baee9a9a | |||
0886afe650 | |||
872f6166e1 | |||
fe348e236f | |||
e0f083d117 | |||
48171f8e24 | |||
bcdf74562b | |||
3a5ee1aed0 | |||
d8c4b9c4fb | |||
6ae7884786 | |||
41834d16d6 | |||
1ee51f2afa | |||
65ee7aa372 | |||
ac38ee82f4 | |||
5fcc7f2328 | |||
3bcc2aad80 | |||
6165b6ae77 | |||
e335e4fddc | |||
5ab4199d71 | |||
f075e2459d | |||
94a26abf21 | |||
bcbdc33049 | |||
21ef3895b3 | |||
3e99dc01b0 | |||
3e325a1974 | |||
2b92e3e8a7 | |||
cb90b90cbf | |||
9776a252ee | |||
751de20f93 | |||
28388b4e3a | |||
4fdbf30308 | |||
722f191e82 | |||
20f6114617 | |||
3075e2cfbf | |||
e2973d2176 | |||
0ff08bb63a | |||
08c0bf52bc | |||
d0229cb96e | |||
0612e5ccfb | |||
1b4f7b34c8 | |||
86e6fcd309 | |||
dc9cd7d8b9 | |||
c0cc9ce7cd | |||
be2f66397b | |||
07760b4129 | |||
d79a3130b8 | |||
440a589f9e | |||
f5fcf9d635 | |||
9b8b1bad57 | |||
874ecd6c88 | |||
57e2fec497 | |||
0905a2c3a2 | |||
3aa00b78f9 | |||
6769d46dbb | |||
efac712f62 | |||
2bb23c57df | |||
bc699a2cc1 | |||
758c128147 | |||
3795c2a39d | |||
311c0e3f50 | |||
25a8caa9b0 | |||
c80a9585b0 | |||
e73491441a | |||
b93b80ccaa | |||
48128c9db6 | |||
6dafaa197d | |||
1634d8e087 | |||
7a583083b8 | |||
75156ab0c9 | |||
9fd6923821 | |||
91a929b2a9 | |||
0f8e31af06 | |||
bd71c2f34d | |||
001123dbd6 | |||
cfee151d4e | |||
fc59291191 | |||
4fc05cac56 | |||
cc4616f25b | |||
e82fbb7bcf | |||
8cd639f6a2 | |||
a8f555856a | |||
3792562046 | |||
d05c48a1d7 | |||
36cc5eb933 | |||
f9f74a0f7d | |||
77f42931ff | |||
73f62266c6 | |||
df2f3d25b0 | |||
599c43ce04 | |||
5c2199e7f4 | |||
3ad4e0348f |
@ -21,15 +21,6 @@ strategy:
|
||||
windows-stable:
|
||||
image: windows-2019
|
||||
style: 'unflagged'
|
||||
linux-nightly-canary:
|
||||
image: ubuntu-18.04
|
||||
style: 'canary'
|
||||
macos-nightly-canary:
|
||||
image: macos-10.14
|
||||
style: 'canary'
|
||||
windows-nightly-canary:
|
||||
image: windows-2019
|
||||
style: 'canary'
|
||||
fmt:
|
||||
image: ubuntu-18.04
|
||||
style: 'fmt'
|
||||
@ -62,15 +53,9 @@ steps:
|
||||
- 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
|
||||
condition: eq(variables['style'], 'canary')
|
||||
displayName: Run tests
|
||||
- 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 --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
|
2054
Cargo.lock
generated
2054
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
103
Cargo.toml
103
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.30.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,47 +18,46 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.30.0", path = "./crates/nu-cli", default-features = false }
|
||||
nu-command = { version = "0.30.0", path = "./crates/nu-command" }
|
||||
nu-data = { version = "0.30.0", path = "./crates/nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.30.0", path = "./crates/nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "./crates/nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.30.0", path = "./crates/nu-value-ext" }
|
||||
nu-cli = { version="0.33.0", path="./crates/nu-cli", default-features=false }
|
||||
nu-command = { version="0.33.0", path="./crates/nu-command" }
|
||||
nu-completion = { version="0.33.0", path="./crates/nu-completion" }
|
||||
nu-data = { version="0.33.0", path="./crates/nu-data" }
|
||||
nu-engine = { version="0.33.0", path="./crates/nu-engine" }
|
||||
nu-errors = { version="0.33.0", path="./crates/nu-errors" }
|
||||
nu-parser = { version="0.33.0", path="./crates/nu-parser" }
|
||||
nu-path = { version="0.33.0", path="./crates/nu-path" }
|
||||
nu-plugin = { version="0.33.0", path="./crates/nu-plugin" }
|
||||
nu-protocol = { version="0.33.0", path="./crates/nu-protocol" }
|
||||
nu-source = { version="0.33.0", path="./crates/nu-source" }
|
||||
nu-value-ext = { version="0.33.0", path="./crates/nu-value-ext" }
|
||||
|
||||
nu_plugin_binaryview = { version = "0.30.0", path = "./crates/nu_plugin_binaryview", optional = true }
|
||||
nu_plugin_chart = { version = "0.30.0", path = "./crates/nu_plugin_chart", optional = true }
|
||||
nu_plugin_fetch = { version = "0.30.0", path = "./crates/nu_plugin_fetch", optional = true }
|
||||
nu_plugin_from_bson = { version = "0.30.0", path = "./crates/nu_plugin_from_bson", optional = true }
|
||||
nu_plugin_from_sqlite = { version = "0.30.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
|
||||
nu_plugin_inc = { version = "0.30.0", path = "./crates/nu_plugin_inc", optional = true }
|
||||
nu_plugin_match = { version = "0.30.0", path = "./crates/nu_plugin_match", optional = true }
|
||||
nu_plugin_post = { version = "0.30.0", path = "./crates/nu_plugin_post", optional = true }
|
||||
nu_plugin_ps = { version = "0.30.0", path = "./crates/nu_plugin_ps", optional = true }
|
||||
nu_plugin_query_json = { version = "0.30.0", path = "./crates/nu_plugin_query_json", optional = true }
|
||||
nu_plugin_s3 = { version = "0.30.0", path = "./crates/nu_plugin_s3", optional = true }
|
||||
nu_plugin_selector = { version = "0.30.0", path = "./crates/nu_plugin_selector", optional = true }
|
||||
nu_plugin_start = { version = "0.30.0", path = "./crates/nu_plugin_start", optional = true }
|
||||
nu_plugin_sys = { version = "0.30.0", path = "./crates/nu_plugin_sys", optional = true }
|
||||
nu_plugin_textview = { version = "0.30.0", path = "./crates/nu_plugin_textview", optional = true }
|
||||
nu_plugin_to_bson = { version = "0.30.0", path = "./crates/nu_plugin_to_bson", optional = true }
|
||||
nu_plugin_to_sqlite = { version = "0.30.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
|
||||
nu_plugin_tree = { version = "0.30.0", path = "./crates/nu_plugin_tree", optional = true }
|
||||
nu_plugin_xpath = { version = "0.30.0", path = "./crates/nu_plugin_xpath", optional = true }
|
||||
nu_plugin_binaryview = { version="0.33.0", path="./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_chart = { version="0.33.0", path="./crates/nu_plugin_chart", optional=true }
|
||||
nu_plugin_fetch = { version="0.33.0", path="./crates/nu_plugin_fetch", optional=true }
|
||||
nu_plugin_from_bson = { version="0.33.0", path="./crates/nu_plugin_from_bson", optional=true }
|
||||
nu_plugin_from_sqlite = { version="0.33.0", path="./crates/nu_plugin_from_sqlite", optional=true }
|
||||
nu_plugin_inc = { version="0.33.0", path="./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version="0.33.0", path="./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_post = { version="0.33.0", path="./crates/nu_plugin_post", optional=true }
|
||||
nu_plugin_ps = { version="0.33.0", path="./crates/nu_plugin_ps", optional=true }
|
||||
nu_plugin_query_json = { version="0.33.0", path="./crates/nu_plugin_query_json", optional=true }
|
||||
nu_plugin_s3 = { version="0.33.0", path="./crates/nu_plugin_s3", optional=true }
|
||||
nu_plugin_selector = { version="0.33.0", path="./crates/nu_plugin_selector", optional=true }
|
||||
nu_plugin_start = { version="0.33.0", path="./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_sys = { version="0.33.0", path="./crates/nu_plugin_sys", optional=true }
|
||||
nu_plugin_textview = { version="0.33.0", path="./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_to_bson = { version="0.33.0", path="./crates/nu_plugin_to_bson", optional=true }
|
||||
nu_plugin_to_sqlite = { version="0.33.0", path="./crates/nu_plugin_to_sqlite", optional=true }
|
||||
nu_plugin_tree = { version="0.33.0", path="./crates/nu_plugin_tree", optional=true }
|
||||
nu_plugin_xpath = { version="0.33.0", path="./crates/nu_plugin_xpath", optional=true }
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.30.0", path = "./crates/nu-test-support" }
|
||||
nu-test-support = { version="0.33.0", path="./crates/nu-test-support" }
|
||||
dunce = "1.0.1"
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
@ -68,32 +67,17 @@ hamcrest2 = "0.3.0"
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
directories-support = [
|
||||
"nu-cli/directories",
|
||||
"nu-cli/dirs",
|
||||
"nu-command/directories",
|
||||
"nu-command/dirs",
|
||||
"nu-data/directories",
|
||||
"nu-data/dirs",
|
||||
"nu-engine/dirs",
|
||||
]
|
||||
ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-cli/term", "nu-command/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
|
||||
which-support = [
|
||||
"nu-cli/ichwh",
|
||||
"nu-cli/which",
|
||||
"nu-command/ichwh",
|
||||
"nu-command/which",
|
||||
"nu-engine/which",
|
||||
]
|
||||
which-support = ["nu-cli/which", "nu-command/which", "nu-engine/which"]
|
||||
executable-support = ["nu-completion/is_executable"]
|
||||
|
||||
default = [
|
||||
"nu-cli/shadow-rs",
|
||||
"sys",
|
||||
"ps",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"term-support",
|
||||
@ -102,6 +86,7 @@ default = [
|
||||
"post",
|
||||
"fetch",
|
||||
"zip-support",
|
||||
"executable-support",
|
||||
]
|
||||
|
||||
stable = ["default"]
|
||||
@ -160,6 +145,18 @@ zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
#This is disabled in extra for now
|
||||
table-pager = ["nu-command/table-pager"]
|
||||
|
||||
#dataframe feature for nushell
|
||||
dataframe = [
|
||||
"nu-engine/dataframe",
|
||||
"nu-protocol/dataframe",
|
||||
"nu-command/dataframe",
|
||||
"nu-value-ext/dataframe",
|
||||
"nu-data/dataframe",
|
||||
"nu_plugin_post/dataframe",
|
||||
"nu_plugin_to_bson/dataframe",
|
||||
]
|
||||
|
||||
|
||||
[profile.release]
|
||||
#strip = "symbols" #Couldn't get working +nightly
|
||||
codegen-units = 1 #Reduce parallel codegen units
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2020 Yehuda Katz, Jonathan Turner
|
||||
Copyright (c) 2019 - 2021 Yehuda Katz, Jonathan Turner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
64
README.md
64
README.md
@ -47,7 +47,7 @@ Try it in Gitpod.
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||
To build Nu, you will need to use the **latest stable (1.51 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
@ -61,13 +61,19 @@ Optional dependencies:
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
```bash
|
||||
```shell
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
||||
|
||||
```shell
|
||||
winget 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/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
|
||||
@ -77,7 +83,7 @@ cargo build --workspace --features=extra
|
||||
|
||||
Want to try Nu right away? Execute the following to get started.
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker run -it quay.io/nushell/nu:latest
|
||||
```
|
||||
|
||||
@ -86,30 +92,30 @@ docker run -it quay.io/nushell/nu:latest
|
||||
If you want to pull a pre-built container, you can browse tags for the [nushell organization](https://quay.io/organization/nushell)
|
||||
on Quay.io. Pulling a container would come down to:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker pull quay.io/nushell/nu
|
||||
docker pull quay.io/nushell/nu-base
|
||||
```
|
||||
|
||||
Both "nu-base" and "nu" provide the nu binary, however nu-base also includes the source code at `/code`
|
||||
Both "nu-base" and "nu" provide the nu binary, however, nu-base also includes the source code at `/code`
|
||||
in the container and all dependencies.
|
||||
|
||||
Optionally, you can also build the containers locally using the [dockerfiles provided](docker):
|
||||
To build the base image:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker build -f docker/Dockerfile.nu-base -t nushell/nu-base .
|
||||
```
|
||||
|
||||
And then to build the smaller container (using a Multistage build):
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker build -f docker/Dockerfile -t nushell/nu .
|
||||
```
|
||||
|
||||
Either way, you can run either container as follows:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
docker run -it nushell/nu-base
|
||||
docker run -it nushell/nu
|
||||
/> exit
|
||||
@ -136,13 +142,13 @@ These values can be piped through a series of steps, in a series of commands cal
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||
Just as the Unix philosophy, Nu allows commands to output from stdout and read from stdin.
|
||||
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
||||
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
- Commands that produce a stream (eg, `ls`)
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
- Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
- Commands that consume the output of the pipeline (e.g., `autoview`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
@ -171,7 +177,7 @@ We could have also written the above:
|
||||
```
|
||||
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||
For example, we could use the built-in `ps` command as well to get a list of the running processes, using the same `where` as above.
|
||||
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
||||
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
@ -188,7 +194,7 @@ For example, we could use the built-in `ps` command as well to get a list of the
|
||||
|
||||
### Opening files
|
||||
|
||||
Nu can load file and URL contents as raw text or as structured data (if it recognizes the format).
|
||||
Nu can load file and URL contents as raw text or structured data (if it recognizes the format).
|
||||
For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```shell
|
||||
@ -220,7 +226,7 @@ We can pipeline this into a command that gets the contents of one of the columns
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.21.0
|
||||
version │ 0.32.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
@ -228,7 +234,7 @@ Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.21.0
|
||||
0.32.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
@ -247,9 +253,9 @@ To set one of these variables, you can use `config set`. For example:
|
||||
### Shells
|
||||
|
||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories at the same time.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
|
||||
|
||||
To do so, use the `enter` command, which will allow you create a new "shell" and enter it at the specified path.
|
||||
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
|
||||
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
|
||||
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
|
||||
|
||||
@ -263,7 +269,7 @@ This allows you to extend nu for your needs.
|
||||
There are a few examples in the `plugins` directory.
|
||||
|
||||
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
|
||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, which then makes it available for use.
|
||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
|
||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
@ -271,7 +277,7 @@ If the plugin is a sink, it is given the full vector of final data and is given
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer consistent first-class support for Windows, macOS, and Linux.
|
||||
|
||||
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
|
||||
@ -287,28 +293,28 @@ You can find a list of Nu commands, complete with documentation, in [quick comma
|
||||
|
||||
## Progress
|
||||
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features |
|
||||
| Aliases | | | X | | | Aliases allow for shortening large commands, while passing flags |
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables |
|
||||
| Environment | | | X | | | Temporary environment and scoped environment variables |
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable |
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish |
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons |
|
||||
| Documentation | | | X | | | Book updated to latest release, including usage examples |
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | X | | | | No functions, yet, only aliases |
|
||||
| Variables | | X | | | | Nu doesn't yet support variables |
|
||||
| Completions | | X | | | | Completions are currently barebones, at best |
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked |
|
||||
| Functions | | | X | | | Functions and aliases are supported |
|
||||
| Variables | | | X | | | Nu supports variables and environment variables |
|
||||
| Completions | | | X | | | Completions for filepaths |
|
||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
||||
|
||||
## Current Roadmap
|
||||
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release and capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
13
crates/README.md
Normal file
13
crates/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Nushell core libraries and plugins
|
||||
|
||||
These sub-crates form both the foundation for Nu and a set of plugins which extend Nu with additional functionality.
|
||||
|
||||
Foundational libraries are split into two kinds of crates:
|
||||
|
||||
* Core crates - those crates that work together to build the Nushell language engine
|
||||
* Support crates - a set of crates that support the engine with additional features like JSON support, ANSI support, and more.
|
||||
|
||||
Plugins are likewise also split into two types:
|
||||
|
||||
* Core plugins - plugins that provide part of the default experience of Nu, including access to the system properties, processes, and web-connectivity features.
|
||||
* Extra plugins - these plugins run a wide range of differnt capabilities like working with different file types, charting, viewing binary data, and more.
|
@ -9,7 +9,7 @@ description = "Library for ANSI terminal colors and styles (bold, underline)"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-ansi-term"
|
||||
version = "0.30.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@ -18,10 +18,15 @@ doctest = false
|
||||
[features]
|
||||
derive_serde_style = ["serde"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.90"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
[dependencies]
|
||||
overload = "0.1.1"
|
||||
serde = { version="1.0.90", features=["derive"], optional=true }
|
||||
itertools = "0.10.0"
|
||||
|
||||
# [dependencies.serde]
|
||||
# version = "1.0.90"
|
||||
# features = ["derive"]
|
||||
# optional = true
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
|
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
|
||||
|
||||
fn main() {
|
||||
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
|
||||
|
||||
// a gradient from hex colors
|
||||
let start = Rgb::from_hex(0x40c9ff);
|
||||
let end = Rgb::from_hex(0xe81cff);
|
||||
let grad0 = Gradient::new(start, end);
|
||||
|
||||
// a gradient from color::rgb()
|
||||
let start = Color::Rgb(64, 201, 255);
|
||||
let end = Color::Rgb(232, 28, 255);
|
||||
let gradient = Gradient::from_color_rgb(start, end);
|
||||
|
||||
// a slightly different gradient
|
||||
let start2 = Color::Rgb(128, 64, 255);
|
||||
let end2 = Color::Rgb(0, 28, 255);
|
||||
let gradient2 = Gradient::from_color_rgb(start2, end2);
|
||||
|
||||
// reverse the gradient
|
||||
let gradient3 = gradient.reverse();
|
||||
|
||||
let build_fg = gradient.build(text, TargetGround::Foreground);
|
||||
println!("{}", build_fg);
|
||||
let build_bg = gradient.build(text, TargetGround::Background);
|
||||
println!("{}", build_bg);
|
||||
let bgt = build_all_gradient_text(text, gradient, gradient2);
|
||||
println!("{}", bgt);
|
||||
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
|
||||
println!("{}", bgt2);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
grad0.build("nushell is awesome", TargetGround::Foreground)
|
||||
);
|
||||
}
|
@ -320,7 +320,7 @@ impl fmt::Display for Infix {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.1.prefix())
|
||||
}
|
||||
Difference::NoDifference => {
|
||||
Difference::Empty => {
|
||||
Ok(()) // nothing to write
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub enum Difference {
|
||||
|
||||
/// The before style is exactly the same as the after style, so no further
|
||||
/// control codes need to be printed.
|
||||
NoDifference,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Difference {
|
||||
@ -40,7 +40,7 @@ impl Difference {
|
||||
// it commented out for now, and defaulting to Reset.
|
||||
|
||||
if first == next {
|
||||
return NoDifference;
|
||||
return Empty;
|
||||
}
|
||||
|
||||
// Cannot un-bold, so must Reset.
|
||||
@ -153,10 +153,10 @@ mod test {
|
||||
};
|
||||
}
|
||||
|
||||
test!(nothing: Green.normal(); Green.normal() => NoDifference);
|
||||
test!(nothing: Green.normal(); Green.normal() => Empty);
|
||||
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
|
||||
test!(lowercase: Green.bold(); Green.normal() => Reset);
|
||||
test!(nothing2: Green.bold(); Green.bold() => NoDifference);
|
||||
test!(nothing2: Green.bold(); Green.bold() => Empty);
|
||||
|
||||
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
|
||||
|
||||
|
@ -266,7 +266,7 @@ where
|
||||
match Difference::between(&window[0].style, &window[1].style) {
|
||||
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
|
||||
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
|
||||
NoDifference => { /* Do nothing! */ }
|
||||
Empty => { /* Do nothing! */ }
|
||||
}
|
||||
|
||||
w.write_str(&window[1].string)?;
|
||||
|
105
crates/nu-ansi-term/src/gradient.rs
Normal file
105
crates/nu-ansi-term/src/gradient.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::{rgb::Rgb, Color};
|
||||
|
||||
/// Linear color gradient between two color stops
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Gradient {
|
||||
/// Start Color of Gradient
|
||||
pub start: Rgb,
|
||||
|
||||
/// End Color of Gradient
|
||||
pub end: Rgb,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
|
||||
#[inline]
|
||||
pub const fn new(start: Rgb, end: Rgb) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
|
||||
let start_grad = match start {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
let end_grad = match end {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
|
||||
Self {
|
||||
start: start_grad,
|
||||
end: end_grad,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the [Rgb] color between `start` and `end` for `t`
|
||||
pub fn at(&self, t: f32) -> Rgb {
|
||||
self.start.lerp(self.end, t)
|
||||
}
|
||||
|
||||
/// Returns the reverse of `self`
|
||||
#[inline]
|
||||
pub const fn reverse(&self) -> Self {
|
||||
Self::new(self.end, self.start)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build(&self, text: &str, target: TargetGround) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let temp = format!(
|
||||
"\x1B[{}m{}",
|
||||
self.at(i as f32 * delta).ansi_color_code(target),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let step = i as f32 * delta;
|
||||
let temp = format!(
|
||||
"\x1B[{};{}m{}",
|
||||
foreground
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Foreground),
|
||||
background
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Background),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetGround {
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
impl TargetGround {
|
||||
#[inline]
|
||||
pub const fn code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Foreground => 30,
|
||||
Self::Background => 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ANSIColorCode {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String;
|
||||
}
|
@ -121,13 +121,13 @@
|
||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `Color::RGB` variant,
|
||||
//! You can also access full 24-bit color by using the `Color::Rgb` variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::RGB;
|
||||
//! use nu_ansi_term::Color::Rgb;
|
||||
//!
|
||||
//! RGB(70, 130, 180).paint("Steel blue");
|
||||
//! Rgb(70, 130, 180).paint("Steel blue");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Combining successive colored strings
|
||||
@ -233,7 +233,7 @@
|
||||
#![crate_type = "rlib"]
|
||||
#![crate_type = "dylib"]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||
|
||||
@ -265,3 +265,9 @@ mod util;
|
||||
pub use util::*;
|
||||
|
||||
mod debug;
|
||||
|
||||
pub mod gradient;
|
||||
pub use gradient::*;
|
||||
|
||||
mod rgb;
|
||||
pub use rgb::*;
|
||||
|
173
crates/nu-ansi-term/src/rgb.rs
Normal file
173
crates/nu-ansi-term/src/rgb.rs
Normal file
@ -0,0 +1,173 @@
|
||||
// Code liberally borrowed from here
|
||||
// https://github.com/navierr/coloriz
|
||||
use std::ops;
|
||||
use std::u32;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u8,
|
||||
/// Green
|
||||
pub g: u8,
|
||||
/// Blue
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Rgb {
|
||||
/// Creates a new [Rgb] color
|
||||
#[inline]
|
||||
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with a hex code
|
||||
#[inline]
|
||||
pub const fn from_hex(hex: u32) -> Self {
|
||||
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
|
||||
}
|
||||
|
||||
pub fn from_hex_string(hex: String) -> Self {
|
||||
if hex.chars().count() == 8 && hex.starts_with("0x") {
|
||||
// eprintln!("hex:{:?}", hex);
|
||||
let (_, value_string) = hex.split_at(2);
|
||||
// eprintln!("value_string:{:?}", value_string);
|
||||
let int_val = u64::from_str_radix(value_string, 16);
|
||||
match int_val {
|
||||
Ok(num) => Self::new(
|
||||
((num & 0xff0000) >> 16) as u8,
|
||||
((num & 0xff00) >> 8) as u8,
|
||||
(num & 0xff) as u8,
|
||||
),
|
||||
// Don't fail, just make the color black
|
||||
// Should we fail?
|
||||
_ => Self::new(0, 0, 0),
|
||||
}
|
||||
} else {
|
||||
// Don't fail, just make the color black.
|
||||
// Should we fail?
|
||||
Self::new(0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with three [f32] values
|
||||
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
|
||||
Self::new(
|
||||
(r.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(g.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(b.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color
|
||||
#[inline]
|
||||
pub const fn gray(x: u8) -> Self {
|
||||
Self::new(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color with a [f32] value
|
||||
pub fn gray_f32(x: f32) -> Self {
|
||||
Self::from_f32(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color from a [HSL] color
|
||||
// pub fn from_hsl(hsl: HSL) -> Self {
|
||||
// if hsl.s == 0.0 {
|
||||
// return Self::gray_f32(hsl.l);
|
||||
// }
|
||||
|
||||
// let q = if hsl.l < 0.5 {
|
||||
// hsl.l * (1.0 + hsl.s)
|
||||
// } else {
|
||||
// hsl.l + hsl.s - hsl.l * hsl.s
|
||||
// };
|
||||
// let p = 2.0 * hsl.l - q;
|
||||
// let h2c = |t: f32| {
|
||||
// let t = t.clamp(0.0, 1.0);
|
||||
// if 6.0 * t < 1.0 {
|
||||
// p + 6.0 * (q - p) * t
|
||||
// } else if t < 0.5 {
|
||||
// q
|
||||
// } else if 1.0 < 1.5 * t {
|
||||
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
|
||||
// } else {
|
||||
// p
|
||||
// }
|
||||
// };
|
||||
|
||||
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
|
||||
// }
|
||||
|
||||
/// Computes the linear interpolation between `self` and `other` for `t`
|
||||
pub fn lerp(&self, other: Self, t: f32) -> Self {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
self * (1.0 - t) + other * t
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb {
|
||||
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||
Self::new(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32)> for Rgb {
|
||||
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||
Self::from_f32(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ANSIColorCode;
|
||||
use crate::TargetGround;
|
||||
impl ANSIColorCode for Rgb {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String {
|
||||
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_add(rhs.r),
|
||||
lhs.g.saturating_add(rhs.g),
|
||||
lhs.b.saturating_add(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_sub(rhs.r),
|
||||
lhs.g.saturating_sub(rhs.g),
|
||||
lhs.b.saturating_sub(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
|
||||
Rgb::new(
|
||||
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
-(rgb: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
255 - rgb.r,
|
||||
255 - rgb.g,
|
||||
255 - rgb.b)
|
||||
}
|
||||
);
|
@ -364,10 +364,16 @@ pub enum Color {
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit RGB color, as specified by ISO-8613-3.
|
||||
/// A 24-bit Rgb color, as specified by ISO-8613-3.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::White
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Returns a `Style` with the foreground color set to this color.
|
||||
///
|
||||
@ -546,7 +552,7 @@ impl Color {
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::RGB(31, 31, 31).on(Color::White);
|
||||
/// let style = Color::Rgb(31, 31, 31).on(Color::White);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(self, background: Color) -> Style {
|
||||
@ -584,13 +590,13 @@ mod serde_json_tests {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::RGB(123, 123, 123),
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&colors).unwrap(),
|
||||
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
|
||||
String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
|
||||
);
|
||||
}
|
||||
|
||||
@ -599,11 +605,11 @@ mod serde_json_tests {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::RGB(123, 123, 123),
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
for color in colors.into_iter() {
|
||||
for color in colors.iter() {
|
||||
let serialized = serde_json::to_string(&color).unwrap();
|
||||
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
|
@ -43,7 +43,7 @@ pub fn unstyle(strs: &AnsiStrings) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
for i in strs.0.iter() {
|
||||
s += &i.deref();
|
||||
s += i.deref();
|
||||
}
|
||||
|
||||
s
|
||||
|
@ -5,71 +5,69 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.30.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-command = { version = "0.30.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.30.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.30.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.30.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.30.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.30.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.30.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.30.0", path = "../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.30.0", path = "../nu-ansi-term" }
|
||||
nu-completion = { version="0.33.0", path="../nu-completion" }
|
||||
nu-command = { version="0.33.0", path="../nu-command" }
|
||||
nu-data = { version="0.33.0", path="../nu-data" }
|
||||
nu-engine = { version="0.33.0", path="../nu-engine" }
|
||||
nu-errors = { version="0.33.0", path="../nu-errors" }
|
||||
nu-json = { version="0.33.0", path="../nu-json" }
|
||||
nu-parser = { version="0.33.0", path="../nu-parser" }
|
||||
nu-plugin = { version="0.33.0", path="../nu-plugin" }
|
||||
nu-protocol = { version="0.33.0", path="../nu-protocol" }
|
||||
nu-source = { version="0.33.0", path="../nu-source" }
|
||||
nu-stream = { version="0.33.0", path="../nu-stream" }
|
||||
nu-table = { version="0.33.0", path="../nu-table" }
|
||||
nu-test-support = { version="0.33.0", path="../nu-test-support" }
|
||||
nu-value-ext = { version="0.33.0", path="../nu-value-ext" }
|
||||
nu-ansi-term = { version="0.33.0", path="../nu-ansi-term" }
|
||||
nu-pretty-hex = { version="0.33.0", path="../nu-pretty-hex" }
|
||||
|
||||
Inflector = "0.11"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
arboard = { version="1.1.0", optional=true }
|
||||
async-recursion = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||
bigdecimal = { version="0.2.0", features=["serde"] }
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "1.0.1"
|
||||
calamine = "0.17.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono = { version="0.4.19", features=["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.11.0"
|
||||
csv = "1.1.5"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
derive-new = "0.5.8"
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
directories-next = "2.0.0"
|
||||
dirs-next = "2.0.0"
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.28"
|
||||
filesize = "0.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
futures-util = "0.3.12"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
glob = "0.3.0"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
ichwh = { version = "0.3.4", optional = true }
|
||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
meval = "0.2.0"
|
||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-bigint = { version="0.3.1", features=["serde"] }
|
||||
num-format = { version="0.4.0", features=["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
parking_lot = "0.11.1"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.2.1"
|
||||
ptree = { version = "0.3.1", optional = true }
|
||||
ptree = { version="0.3.1", optional=true }
|
||||
query_interface = "0.3.5"
|
||||
quick-xml = "0.21.0"
|
||||
rand = "0.8.3"
|
||||
@ -77,31 +75,30 @@ rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "8.0.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
rustyline = { version="8.1.0", optional=true }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
shellexpand = "2.1.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.2.0"
|
||||
term = { version = "0.7.0", optional = true }
|
||||
term = { version="0.7.0", optional=true }
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.2"
|
||||
titlecase = "1.1.0"
|
||||
toml = "0.5.8"
|
||||
trash = { version = "1.3.0", optional = true }
|
||||
trash = { version="1.3.0", optional=true }
|
||||
unicode-segmentation = "1.7.1"
|
||||
url = "2.1.1"
|
||||
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
|
||||
which = { version = "4.0.2", optional = true }
|
||||
zip = { version = "0.5.9", optional = true }
|
||||
shadow-rs = { version = "0.5", default-features = false, optional = true }
|
||||
uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
|
||||
which = { version="4.0.2", optional=true }
|
||||
zip = { version="0.5.9", optional=true }
|
||||
shadow-rs = { version="0.5", default-features=false, optional=true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
@ -116,7 +113,7 @@ users = "0.11.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.2"
|
||||
version = "0.25.3"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.5"
|
||||
@ -131,5 +128,3 @@ clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
dirs = ["dirs-next"]
|
||||
directories = ["directories-next"]
|
||||
|
4
crates/nu-cli/README.md
Normal file
4
crates/nu-cli/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# nu-cli
|
||||
|
||||
This crate provides the fundamental needs when creating the Nushell interactive REPL. In it, you'll find features for interacting with the line editor (the piece which writes the prompt and takes input from the user), keybindings, handlers for the commandline arguments passed to the REPL as it starts up, and more.
|
||||
|
525
crates/nu-cli/src/app.rs
Normal file
525
crates/nu-cli/src/app.rs
Normal file
@ -0,0 +1,525 @@
|
||||
mod logger;
|
||||
mod options;
|
||||
mod options_parser;
|
||||
|
||||
pub use options::{CliOptions, NuScript, Options};
|
||||
use options_parser::{NuParser, OptionsParser};
|
||||
|
||||
use nu_command::{commands::NuSignature as Nu, utils::test_bins as binaries};
|
||||
use nu_engine::{get_full_help, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Call, Expression, SpannedExpression, Synthetic};
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::{Span, Tag};
|
||||
use nu_stream::InputStream;
|
||||
|
||||
pub struct App {
|
||||
parser: Box<dyn OptionsParser>,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
|
||||
Self { parser, options }
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), ShellError> {
|
||||
let nu = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
let ui = App::new(nu, options);
|
||||
|
||||
ui.main(args)
|
||||
}
|
||||
|
||||
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
|
||||
let argv = quote_positionals(argv).join(" ");
|
||||
|
||||
if let Err(cause) = self.parse(&argv) {
|
||||
self.parser
|
||||
.context()
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(cause, &nu_source::Text::from(argv));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if self.help() {
|
||||
let context = self.parser.context();
|
||||
let stream = nu_stream::OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Nu, &context.scope))
|
||||
.into_value(nu_source::Tag::unknown()),
|
||||
);
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if self.version() {
|
||||
let context = self.parser.context();
|
||||
|
||||
let stream = nu_command::commands::version(nu_engine::CommandArgs {
|
||||
context: context.clone(),
|
||||
call_info: nu_engine::UnevaluatedCallInfo {
|
||||
args: Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("version".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
name_tag: Tag::unknown(),
|
||||
},
|
||||
input: InputStream::empty(),
|
||||
})?;
|
||||
|
||||
let stream = {
|
||||
let command = context
|
||||
.get_command("pivot")
|
||||
.expect("could not find version command");
|
||||
|
||||
context.run_command(
|
||||
command,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("pivot".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?
|
||||
};
|
||||
|
||||
consume(context, stream)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(bin) = self.testbin() {
|
||||
match bin.as_deref() {
|
||||
Ok("echo_env") => binaries::echo_env(),
|
||||
Ok("cococo") => binaries::cococo(),
|
||||
Ok("meow") => binaries::meow(),
|
||||
Ok("iecho") => binaries::iecho(),
|
||||
Ok("fail") => binaries::fail(),
|
||||
Ok("nonu") => binaries::nonu(),
|
||||
Ok("chop") => binaries::chop(),
|
||||
Ok("repeater") => binaries::repeater(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut opts = CliOptions::new();
|
||||
|
||||
opts.config = self.config().map(std::ffi::OsString::from);
|
||||
opts.stdin = self.takes_stdin();
|
||||
opts.save_history = self.save_history();
|
||||
|
||||
use logger::{configure, debug_filters, logger, trace_filters};
|
||||
|
||||
logger(|builder| {
|
||||
configure(&self, builder)?;
|
||||
trace_filters(&self, builder)?;
|
||||
debug_filters(&self, builder)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if let Some(commands) = self.commands() {
|
||||
let commands = commands?;
|
||||
let script = NuScript::code(&commands)?;
|
||||
opts.scripts = vec![script];
|
||||
let context = crate::create_default_context(false)?;
|
||||
return crate::run_script_file(context, opts);
|
||||
}
|
||||
|
||||
if let Some(scripts) = self.scripts() {
|
||||
let mut source_files = vec![];
|
||||
for script in scripts {
|
||||
let script_name = script?;
|
||||
let path = std::ffi::OsString::from(&script_name);
|
||||
|
||||
match NuScript::source_file(path.as_os_str()) {
|
||||
Ok(file) => source_files.push(file),
|
||||
Err(_) => {
|
||||
eprintln!("File not found: {}", script_name);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for file in source_files {
|
||||
let mut opts = opts.clone();
|
||||
opts.scripts = vec![file];
|
||||
|
||||
let context = crate::create_default_context(false)?;
|
||||
crate::run_script_file(context, opts)?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let context = crate::create_default_context(true)?;
|
||||
|
||||
if !self.skip_plugins() {
|
||||
let _ = crate::register_plugins(&context);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
{
|
||||
crate::cli(context, opts)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rustyline-support"))]
|
||||
{
|
||||
println!("Nushell needs the 'rustyline-support' feature for CLI support");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commands(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("commands").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn help(&self) -> bool {
|
||||
self.options
|
||||
.get("help")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> bool {
|
||||
self.options
|
||||
.get("version")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("args").map(|v| {
|
||||
v.table_entries()
|
||||
.map(|v| match &v.value {
|
||||
UntaggedValue::Error(err) => Err(err.clone()),
|
||||
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn takes_stdin(&self) -> bool {
|
||||
self.options
|
||||
.get("stdin")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Option<String> {
|
||||
self.options
|
||||
.get("config-file")
|
||||
.map(|v| v.as_string().expect("not a string"))
|
||||
}
|
||||
|
||||
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("develop").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("debug").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("loglevel").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("testbin").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn skip_plugins(&self) -> bool {
|
||||
self.options
|
||||
.get("skip-plugins")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn save_history(&self) -> bool {
|
||||
self.options
|
||||
.get("no-history")
|
||||
.map(|v| !matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
|
||||
self.parser.parse(&args).map(|options| {
|
||||
self.options.swap(&options);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn quote_positionals(parameters: &[String]) -> Vec<String> {
|
||||
parameters
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|arg| {
|
||||
if arg.contains(' ') {
|
||||
format!("\"{}\"", arg)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn consume(context: &EvaluationContext, stream: InputStream) -> Result<(), ShellError> {
|
||||
let autoview_cmd = context
|
||||
.get_command("autoview")
|
||||
.expect("could not find autoview command");
|
||||
|
||||
let stream = context.run_command(
|
||||
autoview_cmd,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?;
|
||||
|
||||
for _ in stream {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cli_app() -> App {
|
||||
let parser = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
|
||||
App::new(parser, options)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu")?;
|
||||
assert!(!ui.version());
|
||||
assert!(!ui.help());
|
||||
assert!(!ui.takes_stdin());
|
||||
assert!(ui.save_history());
|
||||
assert!(!ui.skip_plugins());
|
||||
assert_eq!(ui.config(), None);
|
||||
assert_eq!(ui.loglevel(), None);
|
||||
assert_eq!(ui.debug(), None);
|
||||
assert_eq!(ui.develop(), None);
|
||||
assert_eq!(ui.testbin(), None);
|
||||
assert_eq!(ui.commands(), None);
|
||||
assert_eq!(ui.scripts(), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
|
||||
assert!(ui.config().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --develop=cli,parser")?;
|
||||
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --debug=cli,run")?;
|
||||
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_loglevels() -> Result<(), ShellError> {
|
||||
for level in &["error", "warn", "info", "debug", "trace"] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --loglevel={}", *level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
|
||||
let ui = cli_app();
|
||||
let args = format!("nu -l {}", *level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --loglevel=nada")?;
|
||||
assert_eq!(
|
||||
ui.loglevel().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error("nada is not supported."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu code.nu bootstrap.nu")?;
|
||||
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
|
||||
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_test_binaries() -> Result<(), ShellError> {
|
||||
for binarie_name in &[
|
||||
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
|
||||
] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --testbin={}", *binarie_name);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --testbin=andres")?;
|
||||
assert_eq!(
|
||||
ui.testbin().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"andres is not supported."
|
||||
))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_version() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --version")?;
|
||||
assert!(ui.version());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_help() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --help")?;
|
||||
assert!(ui.help());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_take_stdin() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --stdin")?;
|
||||
assert!(ui.takes_stdin());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --no-history")?;
|
||||
assert!(!ui.save_history());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --skip-plugins")?;
|
||||
assert!(ui.skip_plugins());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"ls | get name\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
|
||||
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"echo 'hola'\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knows_custom_configurations() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --config-file /path/to/config.toml")?;
|
||||
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
52
crates/nu-cli/src/app/logger.rs
Normal file
52
crates/nu-cli/src/app/logger.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use super::App;
|
||||
use log::LevelFilter;
|
||||
use nu_errors::ShellError;
|
||||
use pretty_env_logger::env_logger::Builder;
|
||||
|
||||
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
|
||||
let mut builder = pretty_env_logger::formatted_builder();
|
||||
f(&mut builder)?;
|
||||
let _ = builder.try_init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(level) = app.loglevel() {
|
||||
let level = match level.as_deref() {
|
||||
Ok("error") => LevelFilter::Error,
|
||||
Ok("warn") => LevelFilter::Warn,
|
||||
Ok("info") => LevelFilter::Info,
|
||||
Ok("debug") => LevelFilter::Debug,
|
||||
Ok("trace") => LevelFilter::Trace,
|
||||
Ok(_) | Err(_) => LevelFilter::Warn,
|
||||
};
|
||||
|
||||
logger.filter_module("nu", level);
|
||||
};
|
||||
|
||||
if let Ok(s) = std::env::var("RUST_LOG") {
|
||||
logger.parse_filters(&s);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.develop() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Trace);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.debug() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Debug);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
100
crates/nu-cli/src/app/options.rs
Normal file
100
crates/nu-cli/src/app/options.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CliOptions {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
}
|
||||
|
||||
impl Default for CliOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
inner: RefCell<IndexMap<String, Value>>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(IndexMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
self.inner.borrow().get(key).map(Clone::clone)
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &str, value: Value) {
|
||||
self.inner.borrow_mut().insert(key.into(), value);
|
||||
}
|
||||
|
||||
pub fn shift(&self) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(ref mut args),
|
||||
..
|
||||
}) = self.inner.borrow_mut().get_mut("args")
|
||||
{
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap(&self, other: &Options) {
|
||||
self.inner.swap(&other.inner);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code(content: &str) -> Result<Self, ShellError> {
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: content.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
143
crates/nu-cli/src/app/options_parser.rs
Normal file
143
crates/nu-cli/src/app/options_parser.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use super::Options;
|
||||
|
||||
use nu_command::commands::{loglevels, testbins, NuSignature as Nu};
|
||||
use nu_command::commands::{Autoview, Pivot, Table, Version as NuVersion};
|
||||
use nu_engine::{whole_stream_command, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
pub struct NuParser {
|
||||
context: EvaluationContext,
|
||||
}
|
||||
|
||||
pub trait OptionsParser {
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError>;
|
||||
fn context(&self) -> &EvaluationContext;
|
||||
}
|
||||
|
||||
impl NuParser {
|
||||
pub fn new() -> Self {
|
||||
let context = EvaluationContext::basic();
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(Nu {}),
|
||||
whole_stream_command(NuVersion {}),
|
||||
whole_stream_command(Autoview {}),
|
||||
whole_stream_command(Pivot {}),
|
||||
whole_stream_command(Table {}),
|
||||
]);
|
||||
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsParser for NuParser {
|
||||
fn context(&self) -> &EvaluationContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError> {
|
||||
let options = Options::default();
|
||||
let (lite_result, _err) = nu_parser::lex(input, 0);
|
||||
let (lite_result, _err) = nu_parser::parse_block(lite_result);
|
||||
|
||||
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
|
||||
|
||||
if let Some(reason) = err {
|
||||
return Err(reason.into());
|
||||
}
|
||||
|
||||
match parsed.block[0].pipelines[0].list[0] {
|
||||
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
|
||||
if let Some(ref params) = args.named {
|
||||
params.iter().for_each(|(k, v)| {
|
||||
let value = match v {
|
||||
NamedValue::AbsentSwitch => {
|
||||
Some(UntaggedValue::from(false).into_untagged_value())
|
||||
}
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
|
||||
}
|
||||
NamedValue::AbsentValue => None,
|
||||
NamedValue::Value(span, exprs) => {
|
||||
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
|
||||
.expect("value");
|
||||
Some(value.value.into_value(Tag::from(span)))
|
||||
}
|
||||
};
|
||||
|
||||
let value =
|
||||
value
|
||||
.map(|v| match k.as_ref() {
|
||||
"testbin" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if testbins().iter().any(|n| name == *n) {
|
||||
Some(v)
|
||||
} else {
|
||||
Some(
|
||||
UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
),
|
||||
)
|
||||
.into_value(v.tag),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
"loglevel" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if loglevels().iter().any(|n| name == *n) {
|
||||
Some(v)
|
||||
} else {
|
||||
Some(
|
||||
UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
),
|
||||
)
|
||||
.into_value(v.tag),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
_ => Some(v),
|
||||
})
|
||||
.flatten();
|
||||
|
||||
if let Some(value) = value {
|
||||
options.put(&k, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut positional_args = vec![];
|
||||
|
||||
if let Some(positional) = &args.positional {
|
||||
for pos in positional {
|
||||
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
|
||||
positional_args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if !positional_args.is_empty() {
|
||||
options.put(
|
||||
"args",
|
||||
UntaggedValue::Table(positional_args).into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Error(ref reason) => {
|
||||
return Err(reason.clone().into());
|
||||
}
|
||||
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ use crate::line_editor::{
|
||||
use nu_data::config;
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_stream::InputStream;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@ -31,69 +30,6 @@ use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Options {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
|
||||
let text = content
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: text,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
@ -123,7 +59,10 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
||||
pub fn run_script_file(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
@ -144,7 +83,10 @@ pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
||||
pub fn cli(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
|
||||
@ -157,8 +99,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION",
|
||||
format!("{:?}", startup_commands_start_time.elapsed()),
|
||||
"CMD_DURATION_MS",
|
||||
format!("{}", startup_commands_start_time.elapsed().as_millis()),
|
||||
);
|
||||
trace!(
|
||||
"startup commands took {:?}",
|
||||
@ -167,7 +109,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
|
||||
//Configure rustyline
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
let history_path = if let Some(cfg) = &context.configs.lock().global_config {
|
||||
let history_path = if let Some(cfg) = &context.configs().lock().global_config {
|
||||
let _ = configure_rustyline_editor(&mut rl, cfg);
|
||||
let helper = Some(nu_line_editor_helper(&context, cfg));
|
||||
rl.set_helper(helper);
|
||||
@ -182,7 +124,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
|
||||
//set vars from cfg if present
|
||||
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs.lock().global_config {
|
||||
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs().lock().global_config
|
||||
{
|
||||
(
|
||||
cfg.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
@ -205,7 +148,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
clap::crate_version!()
|
||||
nu_command::commands::core_version()
|
||||
);
|
||||
}
|
||||
|
||||
@ -217,25 +160,19 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
loop {
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
if context.ctrl_c().load(Ordering::SeqCst) {
|
||||
context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
let cwd = context.shell_manager().path();
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = &prompt {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
context.scope.enter_scope();
|
||||
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
||||
|
||||
if let Some(block) =
|
||||
std::sync::Arc::<nu_protocol::hir::Block>::get_mut(&mut prompt_block)
|
||||
{
|
||||
block.set_redirect(ExternalRedirection::Stdout);
|
||||
}
|
||||
let (prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
||||
|
||||
if err.is_some() {
|
||||
context.scope.exit_scope();
|
||||
@ -250,7 +187,12 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
} else {
|
||||
let run_result = run_block(&prompt_block, &context, InputStream::empty());
|
||||
let run_result = run_block(
|
||||
&prompt_block,
|
||||
&context,
|
||||
InputStream::empty(),
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
context.scope.exit_scope();
|
||||
|
||||
match run_result {
|
||||
@ -267,14 +209,14 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
context.host.lock().print_err(e, &Text::from(prompt_line));
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
context.host.lock().print_err(e, &Text::from(prompt_line));
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
@ -302,11 +244,13 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
};
|
||||
|
||||
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
|
||||
if let Some(helper) = rl.helper_mut() {
|
||||
helper.colored_prompt = colored_prompt;
|
||||
}
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
|
||||
readline = rl.readline_with_initial(&prompt, (cmd, ""));
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
@ -331,9 +275,10 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
};
|
||||
|
||||
// Store cmd duration in an env var
|
||||
context
|
||||
.scope
|
||||
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed()));
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION_MS",
|
||||
format!("{}", cmd_start_time.elapsed().as_millis()),
|
||||
);
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
@ -358,7 +303,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
|
||||
context
|
||||
.host
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(err, &Text::from(session_text.clone()));
|
||||
|
||||
@ -372,7 +317,7 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = context
|
||||
.configs
|
||||
.configs()
|
||||
.lock()
|
||||
.global_config
|
||||
.as_ref()
|
||||
@ -398,8 +343,8 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
}
|
||||
|
||||
LineResult::CtrlD => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
context.shell_manager().remove_at_current();
|
||||
if context.shell_manager().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -421,15 +366,15 @@ pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn E
|
||||
|
||||
pub fn load_local_cfg_if_present(context: &EvaluationContext) {
|
||||
trace!("Loading local cfg if present");
|
||||
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager.path())) {
|
||||
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager().path())) {
|
||||
Ok(Some(cfg_path)) => {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
|
||||
context.host.lock().print_err(err, &Text::from(""))
|
||||
context.host().lock().print_err(err, &Text::from(""))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
//Report error while checking for local cfg file
|
||||
context.host.lock().print_err(e, &Text::from(""))
|
||||
context.host().lock().print_err(e, &Text::from(""))
|
||||
}
|
||||
Ok(None) => {
|
||||
//No local cfg file present in start dir
|
||||
@ -439,7 +384,7 @@ pub fn load_local_cfg_if_present(context: &EvaluationContext) {
|
||||
|
||||
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
|
||||
context.host.lock().print_err(err, &Text::from(""));
|
||||
context.host().lock().print_err(err, &Text::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +394,7 @@ pub fn load_global_cfg(context: &EvaluationContext) {
|
||||
load_cfg_as_global_cfg(context, path);
|
||||
}
|
||||
Err(e) => {
|
||||
context.host.lock().print_err(e, &Text::from(""));
|
||||
context.host().lock().print_err(e, &Text::from(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -477,7 +422,7 @@ pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, She
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
ctx.scope.enter_scope();
|
||||
let (classified_block, err) = nu_parser::parse(&line, 0, &ctx.scope);
|
||||
let (classified_block, err) = nu_parser::parse(line, 0, &ctx.scope);
|
||||
if let Some(err) = err {
|
||||
ctx.scope.exit_scope();
|
||||
return Err(err.into());
|
||||
@ -485,7 +430,12 @@ pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, She
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
|
||||
let result = run_block(&classified_block, ctx, input_stream);
|
||||
let result = run_block(
|
||||
&classified_block,
|
||||
ctx,
|
||||
input_stream,
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
result?.collect_string(Tag::unknown()).map(|x| x.item)
|
||||
@ -509,14 +459,14 @@ fn current_branch() -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nu_engine::basic_evaluation_context;
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
let (tokens, err) = nu_parser::lex(&data, 0);
|
||||
let (lite_block, err2) = nu_parser::parse_block(tokens);
|
||||
if err.is_none() && err2.is_none() {
|
||||
let context = basic_evaluation_context().unwrap();
|
||||
let context = EvaluationContext::basic();
|
||||
let _ = nu_parser::classify_block(&lite_block, &context.scope);
|
||||
}
|
||||
true
|
||||
|
@ -1,37 +0,0 @@
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod engine;
|
||||
pub(crate) mod flag;
|
||||
pub(crate) mod matchers;
|
||||
pub(crate) mod path;
|
||||
|
||||
use matchers::Matcher;
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Suggestion {
|
||||
pub display: String,
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
pub struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> CompletionContext<'a> {
|
||||
pub fn new(a: &'a EvaluationContext) -> CompletionContext<'a> {
|
||||
CompletionContext(a)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
|
||||
fn as_ref(&self) -> &EvaluationContext {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Completer {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion>;
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
pub struct PathCompleter;
|
||||
|
||||
pub struct PathSuggestion {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) suggestion: Suggestion,
|
||||
}
|
||||
|
||||
impl PathCompleter {
|
||||
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
||||
let expanded = nu_parser::expand_ndots(partial);
|
||||
let expanded = expanded.replace(std::path::is_separator, &SEP.to_string());
|
||||
let expanded: &str = expanded.as_ref();
|
||||
|
||||
let (base_dir_name, partial) = match expanded.rfind(SEP) {
|
||||
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
|
||||
None => ("", expanded),
|
||||
};
|
||||
|
||||
let base_dir = if base_dir_name.is_empty() {
|
||||
PathBuf::from(".")
|
||||
} else {
|
||||
#[cfg(feature = "directories")]
|
||||
{
|
||||
let home_prefix = format!("~{}", SEP);
|
||||
if base_dir_name.starts_with(&home_prefix) {
|
||||
let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~"));
|
||||
home_dir.push(&base_dir_name[2..]);
|
||||
home_dir
|
||||
} else {
|
||||
PathBuf::from(base_dir_name)
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "directories"))]
|
||||
{
|
||||
PathBuf::from(base_dir_name)
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matcher.matches(partial, file_name.as_str()) {
|
||||
let mut path = format!("{}{}", base_dir_name, file_name);
|
||||
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
||||
path.push(std::path::MAIN_SEPARATOR);
|
||||
file_name.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
|
||||
Some(PathSuggestion {
|
||||
path: entry.path(),
|
||||
suggestion: Suggestion {
|
||||
replacement: path,
|
||||
display: file_name,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for PathCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
_ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
self.path_suggestions(partial, matcher)
|
||||
.into_iter()
|
||||
.map(|ps| ps.suggestion)
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
pub(crate) trait RenderView {
|
||||
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError>;
|
||||
}
|
@ -1,63 +1,42 @@
|
||||
use rustyline::{KeyCode, Modifiers};
|
||||
use rustyline::{KeyCode as RustyKeyCode, Modifiers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn convert_keyevent(key_event: KeyEvent) -> rustyline::KeyEvent {
|
||||
pub fn convert_keyevent(key_event: KeyCode, modifiers: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
match key_event {
|
||||
KeyEvent::UnknownEscSeq => convert_to_rl_keyevent(rustyline::KeyCode::UnknownEscSeq, None),
|
||||
KeyEvent::Backspace => convert_to_rl_keyevent(rustyline::KeyCode::Backspace, None),
|
||||
KeyEvent::BackTab => convert_to_rl_keyevent(rustyline::KeyCode::BackTab, None),
|
||||
KeyEvent::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteStart, None)
|
||||
KeyCode::UnknownEscSeq => convert_to_rl_keyevent(RustyKeyCode::UnknownEscSeq, modifiers),
|
||||
KeyCode::Backspace => convert_to_rl_keyevent(RustyKeyCode::Backspace, modifiers),
|
||||
KeyCode::BackTab => convert_to_rl_keyevent(RustyKeyCode::BackTab, modifiers),
|
||||
KeyCode::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteStart, modifiers)
|
||||
}
|
||||
KeyEvent::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteEnd, None)
|
||||
KeyCode::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteEnd, modifiers)
|
||||
}
|
||||
KeyEvent::Char(c) => convert_to_rl_keyevent(rustyline::KeyCode::Char(c), None),
|
||||
KeyEvent::ControlDown => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlLeft => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlRight => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlUp => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::Ctrl(c) => rustyline::KeyEvent::ctrl(c),
|
||||
KeyEvent::Delete => convert_to_rl_keyevent(rustyline::KeyCode::Delete, None),
|
||||
KeyEvent::Down => convert_to_rl_keyevent(rustyline::KeyCode::Down, None),
|
||||
KeyEvent::End => convert_to_rl_keyevent(rustyline::KeyCode::End, None),
|
||||
KeyEvent::Enter => convert_to_rl_keyevent(rustyline::KeyCode::Enter, None),
|
||||
KeyEvent::Esc => convert_to_rl_keyevent(rustyline::KeyCode::Esc, None),
|
||||
KeyEvent::F(u) => convert_to_rl_keyevent(rustyline::KeyCode::F(u), None),
|
||||
KeyEvent::Home => convert_to_rl_keyevent(rustyline::KeyCode::Home, None),
|
||||
KeyEvent::Insert => convert_to_rl_keyevent(rustyline::KeyCode::Insert, None),
|
||||
KeyEvent::Left => convert_to_rl_keyevent(rustyline::KeyCode::Left, None),
|
||||
KeyEvent::Meta(c) => rustyline::KeyEvent::new(c, Modifiers::NONE),
|
||||
KeyEvent::Null => convert_to_rl_keyevent(rustyline::KeyCode::Null, None),
|
||||
KeyEvent::PageDown => convert_to_rl_keyevent(rustyline::KeyCode::PageDown, None),
|
||||
KeyEvent::PageUp => convert_to_rl_keyevent(rustyline::KeyCode::PageUp, None),
|
||||
KeyEvent::Right => convert_to_rl_keyevent(rustyline::KeyCode::Right, None),
|
||||
KeyEvent::ShiftDown => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftLeft => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftRight => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftUp => convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::SHIFT)),
|
||||
KeyEvent::Tab => convert_to_rl_keyevent(rustyline::KeyCode::Tab, None),
|
||||
KeyEvent::Up => convert_to_rl_keyevent(rustyline::KeyCode::Up, None),
|
||||
KeyCode::Char(c) => convert_to_rl_keyevent(RustyKeyCode::Char(c), modifiers),
|
||||
KeyCode::Delete => convert_to_rl_keyevent(RustyKeyCode::Delete, modifiers),
|
||||
KeyCode::Down => convert_to_rl_keyevent(RustyKeyCode::Down, modifiers),
|
||||
KeyCode::End => convert_to_rl_keyevent(RustyKeyCode::End, modifiers),
|
||||
KeyCode::Enter => convert_to_rl_keyevent(RustyKeyCode::Enter, modifiers),
|
||||
KeyCode::Esc => convert_to_rl_keyevent(RustyKeyCode::Esc, modifiers),
|
||||
KeyCode::F(u) => convert_to_rl_keyevent(RustyKeyCode::F(u), modifiers),
|
||||
KeyCode::Home => convert_to_rl_keyevent(RustyKeyCode::Home, modifiers),
|
||||
KeyCode::Insert => convert_to_rl_keyevent(RustyKeyCode::Insert, modifiers),
|
||||
KeyCode::Left => convert_to_rl_keyevent(RustyKeyCode::Left, modifiers),
|
||||
KeyCode::Null => convert_to_rl_keyevent(RustyKeyCode::Null, modifiers),
|
||||
KeyCode::PageDown => convert_to_rl_keyevent(RustyKeyCode::PageDown, modifiers),
|
||||
KeyCode::PageUp => convert_to_rl_keyevent(RustyKeyCode::PageUp, modifiers),
|
||||
KeyCode::Right => convert_to_rl_keyevent(RustyKeyCode::Right, modifiers),
|
||||
KeyCode::Tab => convert_to_rl_keyevent(RustyKeyCode::Tab, modifiers),
|
||||
KeyCode::Up => convert_to_rl_keyevent(RustyKeyCode::Up, modifiers),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_rl_keyevent(key_event: KeyCode, modifier: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
fn convert_to_rl_keyevent(
|
||||
key_code: RustyKeyCode,
|
||||
modifier: Option<Modifiers>,
|
||||
) -> rustyline::KeyEvent {
|
||||
rustyline::KeyEvent {
|
||||
0: key_event,
|
||||
0: key_code,
|
||||
1: modifier.unwrap_or(Modifiers::NONE),
|
||||
}
|
||||
}
|
||||
@ -132,12 +111,14 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
Cmd::Complete => rustyline::Cmd::Complete,
|
||||
Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward,
|
||||
Cmd::CompleteHint => rustyline::Cmd::CompleteHint,
|
||||
Cmd::Dedent(movement) => rustyline::Cmd::Dedent(convert_movement(movement)),
|
||||
Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord,
|
||||
Cmd::EndOfFile => rustyline::Cmd::EndOfFile,
|
||||
Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory,
|
||||
Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory,
|
||||
Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward,
|
||||
Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward,
|
||||
Cmd::Indent(movement) => rustyline::Cmd::Indent(convert_movement(movement)),
|
||||
Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string),
|
||||
Cmd::Interrupt => rustyline::Cmd::Interrupt,
|
||||
Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)),
|
||||
@ -145,8 +126,11 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u),
|
||||
Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)),
|
||||
Cmd::NextHistory => rustyline::Cmd::NextHistory,
|
||||
Cmd::Newline => rustyline::Cmd::Newline,
|
||||
Cmd::Noop => rustyline::Cmd::Noop,
|
||||
Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c),
|
||||
#[cfg(windows)]
|
||||
Cmd::PasteFromClipboard => rustyline::Cmd::PasteFromClipboard,
|
||||
Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory,
|
||||
Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert,
|
||||
Cmd::Replace {
|
||||
@ -169,14 +153,28 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
}
|
||||
|
||||
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
|
||||
let rusty_modifiers = match keybinding.modifiers {
|
||||
Some(mods) => match mods {
|
||||
NuModifiers::Ctrl => Some(Modifiers::CTRL),
|
||||
NuModifiers::Alt => Some(Modifiers::ALT),
|
||||
NuModifiers::Shift => Some(Modifiers::SHIFT),
|
||||
NuModifiers::None => Some(Modifiers::NONE),
|
||||
NuModifiers::CtrlShift => Some(Modifiers::CTRL_SHIFT),
|
||||
NuModifiers::AltShift => Some(Modifiers::ALT_SHIFT),
|
||||
NuModifiers::CtrlAlt => Some(Modifiers::CTRL_ALT),
|
||||
NuModifiers::CtrlAltShift => Some(Modifiers::CTRL_ALT_SHIFT),
|
||||
// _ => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
(
|
||||
convert_keyevent(keybinding.key),
|
||||
convert_keyevent(keybinding.key, rusty_modifiers),
|
||||
convert_cmd(keybinding.binding),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum KeyEvent {
|
||||
pub enum KeyCode {
|
||||
/// Unsupported escape sequence (on unix platform)
|
||||
UnknownEscSeq,
|
||||
/// ⌫ or `KeyEvent::Ctrl('H')`
|
||||
@ -189,16 +187,6 @@ pub enum KeyEvent {
|
||||
BracketedPasteEnd,
|
||||
/// Single char
|
||||
Char(char),
|
||||
/// Ctrl-↓
|
||||
ControlDown,
|
||||
/// Ctrl-←
|
||||
ControlLeft,
|
||||
/// Ctrl-→
|
||||
ControlRight,
|
||||
/// Ctrl-↑
|
||||
ControlUp,
|
||||
/// Ctrl-char
|
||||
Ctrl(char),
|
||||
/// ⌦
|
||||
Delete,
|
||||
/// ↓ arrow key
|
||||
@ -217,9 +205,7 @@ pub enum KeyEvent {
|
||||
Insert,
|
||||
/// ← arrow key
|
||||
Left,
|
||||
/// Escape-char or Alt-char
|
||||
Meta(char),
|
||||
/// `KeyEvent::Char('\0')`
|
||||
// /// `KeyEvent::Char('\0')`
|
||||
Null,
|
||||
/// ⇟
|
||||
PageDown,
|
||||
@ -227,14 +213,6 @@ pub enum KeyEvent {
|
||||
PageUp,
|
||||
/// → arrow key
|
||||
Right,
|
||||
/// Shift-↓
|
||||
ShiftDown,
|
||||
/// Shift-←
|
||||
ShiftLeft,
|
||||
/// Shift-→
|
||||
ShiftRight,
|
||||
/// Shift-↑
|
||||
ShiftUp,
|
||||
/// ⇥ or `KeyEvent::Ctrl('I')`
|
||||
Tab,
|
||||
/// ↑ arrow key
|
||||
@ -259,6 +237,8 @@ pub enum Cmd {
|
||||
CompleteBackward,
|
||||
/// complete-hint
|
||||
CompleteHint,
|
||||
/// Dedent current line
|
||||
Dedent(Movement),
|
||||
/// downcase-word
|
||||
DowncaseWord,
|
||||
/// vi-eof-maybe
|
||||
@ -271,6 +251,8 @@ pub enum Cmd {
|
||||
HistorySearchBackward,
|
||||
/// history-search-forward
|
||||
HistorySearchForward,
|
||||
/// Indent current line
|
||||
Indent(Movement),
|
||||
/// Insert text
|
||||
Insert { repeat: RepeatCount, string: String },
|
||||
/// Interrupt signal (Ctrl-C)
|
||||
@ -283,12 +265,17 @@ pub enum Cmd {
|
||||
/// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
|
||||
/// vi-prev-word
|
||||
Move(Movement),
|
||||
/// Inserts a newline
|
||||
Newline,
|
||||
/// next-history
|
||||
NextHistory,
|
||||
/// No action
|
||||
Noop,
|
||||
/// vi-replace
|
||||
Overwrite(char),
|
||||
/// Paste from the clipboard
|
||||
#[cfg(windows)]
|
||||
PasteFromClipboard,
|
||||
/// previous-history
|
||||
PreviousHistory,
|
||||
/// quoted-insert
|
||||
@ -422,12 +409,43 @@ pub enum CharSearch {
|
||||
BackwardAfter(char),
|
||||
}
|
||||
|
||||
/// The set of modifier keys that were triggered along with a key press.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum NuModifiers {
|
||||
/// Control modifier
|
||||
#[serde(alias = "CTRL")]
|
||||
Ctrl = 8,
|
||||
/// Escape or Alt modifier
|
||||
#[serde(alias = "ALT")]
|
||||
Alt = 4,
|
||||
/// Shift modifier
|
||||
#[serde(alias = "SHIFT")]
|
||||
Shift = 2,
|
||||
/// No modifier
|
||||
#[serde(alias = "NONE")]
|
||||
None = 0,
|
||||
/// Ctrl + Shift
|
||||
#[serde(alias = "CTRL_SHIFT")]
|
||||
CtrlShift = 10,
|
||||
/// Alt + Shift
|
||||
#[serde(alias = "ALT_SHIFT")]
|
||||
AltShift = 6,
|
||||
/// Ctrl + Alt
|
||||
#[serde(alias = "CTRL_ALT")]
|
||||
CtrlAlt = 12,
|
||||
/// Ctrl + Alt + Shift
|
||||
#[serde(alias = "CTRL_ALT_SHIFT")]
|
||||
CtrlAltShift = 14,
|
||||
}
|
||||
|
||||
/// The number of times one command should be repeated.
|
||||
pub type RepeatCount = usize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Keybinding {
|
||||
key: KeyEvent,
|
||||
key: KeyCode,
|
||||
modifiers: Option<NuModifiers>,
|
||||
binding: Cmd,
|
||||
}
|
||||
|
||||
@ -442,7 +460,7 @@ pub(crate) fn load_keybindings(
|
||||
// Silently fail if there is no file there
|
||||
if let Ok(contents) = contents {
|
||||
let keybindings: Keybindings = serde_yaml::from_str(&contents)?;
|
||||
|
||||
// eprint!("{}{}{}", keybindings.key, keybindings.mo);
|
||||
for keybinding in keybindings.into_iter() {
|
||||
let (k, b) = convert_keybinding(keybinding);
|
||||
|
||||
|
@ -1,31 +1,26 @@
|
||||
#![recursion_limit = "2048"]
|
||||
|
||||
#[macro_use]
|
||||
mod prelude;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate quickcheck;
|
||||
#[cfg(test)]
|
||||
#[macro_use(quickcheck)]
|
||||
extern crate quickcheck_macros;
|
||||
|
||||
mod app;
|
||||
mod cli;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod completion;
|
||||
mod format;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
mod line_editor;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod shell;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub use crate::cli::cli;
|
||||
|
||||
pub use crate::app::App;
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
pub use crate::cli::{NuScript, Options};
|
||||
|
||||
pub use nu_command::commands::default_context::create_default_context;
|
||||
pub use nu_command::{
|
||||
commands::NuSignature as Nu, commands::Version as NuVersion, create_default_context,
|
||||
};
|
||||
pub use nu_data::config;
|
||||
pub use nu_data::dict::TaggedListBuilder;
|
||||
pub use nu_data::primitive;
|
||||
|
@ -1,14 +1,15 @@
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_errors::ShellError;
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::prelude::*;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_engine::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::keybinding::{convert_keyevent, KeyEvent};
|
||||
use crate::keybinding::{convert_keyevent, KeyCode};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
@ -19,7 +20,8 @@ use rustyline::{
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, Movement, Word,
|
||||
line_buffer::LineBuffer,
|
||||
At, Cmd, ConditionalEventHandler, Editor, EventHandler, Modifiers, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
@ -30,12 +32,40 @@ pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>)
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
eprintln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
struct PartialCompleteHintHandler;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
impl ConditionalEventHandler for PartialCompleteHintHandler {
|
||||
fn handle(
|
||||
&self,
|
||||
_evt: &rustyline::Event,
|
||||
_n: rustyline::RepeatCount,
|
||||
_positive: bool,
|
||||
ctx: &rustyline::EventContext,
|
||||
) -> Option<Cmd> {
|
||||
Some(match ctx.hint_text() {
|
||||
Some(hint_text) if ctx.pos() == ctx.line().len() => {
|
||||
let mut line_buffer = LineBuffer::with_capacity(hint_text.len());
|
||||
line_buffer.update(hint_text, 0);
|
||||
line_buffer.move_to_next_word(At::AfterEnd, Word::Vi, 1);
|
||||
|
||||
let text = hint_text[0..line_buffer.pos()].to_string();
|
||||
|
||||
Cmd::Insert(1, text)
|
||||
}
|
||||
_ => Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
@ -50,18 +80,20 @@ pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
//M modifier, E KeyEvent, K KeyCode
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyEvent::ControlLeft),
|
||||
convert_keyevent(KeyCode::Left, Some(Modifiers::CTRL)),
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyEvent::ControlRight),
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
convert_keyevent(KeyCode::Right, Some(Modifiers::CTRL)),
|
||||
EventHandler::Conditional(Box::new(PartialCompleteHintHandler)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyEvent::BracketedPasteStart),
|
||||
convert_keyevent(KeyCode::BracketedPasteStart, None),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
@ -227,14 +259,14 @@ pub fn rustyline_hinter(
|
||||
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
let cc = _context.ctrl_c().clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
if _context.ctrl_c().load(Ordering::SeqCst) {
|
||||
_context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
#[macro_export]
|
||||
macro_rules! return_err {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Err(_) => return,
|
||||
Ok(expr) => expr,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_out_stream {
|
||||
(target: $target:tt, $desc:tt = $expr:expr) => {{
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
"{} = {}",
|
||||
$desc,
|
||||
match o {
|
||||
Err(err) => format!("{:?}", err),
|
||||
Ok(value) => value.display(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
nu_stream::OutputStream::new(objects)
|
||||
} else {
|
||||
$expr
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use nu_engine::Host;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_errors::ShellError;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_protocol::outln;
|
||||
pub(crate) use nu_stream::ActionStream;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_value_ext::ValueExt;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use std::sync::atomic::Ordering;
|
||||
|
||||
#[allow(clippy::clippy::wrong_self_convention)]
|
||||
pub trait FromInputStream {
|
||||
fn from_input_stream(self) -> ActionStream;
|
||||
}
|
||||
|
||||
impl<T> FromInputStream for T
|
||||
where
|
||||
T: Iterator<Item = nu_protocol::Value> + Send + Sync + 'static,
|
||||
{
|
||||
fn from_input_stream(self) -> ActionStream {
|
||||
ActionStream {
|
||||
values: Box::new(self.map(nu_protocol::ReturnSuccess::value)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,212 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
use nu_ansi_term::Color;
|
||||
use nu_completion::NuCompleter;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod completer;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod helper;
|
||||
pub struct Helper {
|
||||
completer: NuCompleter,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
context: EvaluationContext,
|
||||
pub colored_prompt: String,
|
||||
validator: NuValidator,
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) use helper::Helper;
|
||||
impl Helper {
|
||||
pub(crate) fn new(
|
||||
context: EvaluationContext,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
) -> Helper {
|
||||
Helper {
|
||||
completer: NuCompleter {},
|
||||
hinter,
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
validator: NuValidator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
|
||||
fn signature_registry(&self) -> &dyn nu_parser::ParserScope {
|
||||
&self.0.scope
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompletionSuggestion(nu_completion::Suggestion);
|
||||
|
||||
impl rustyline::completion::Candidate for CompletionSuggestion {
|
||||
fn display(&self) -> &str {
|
||||
&self.0.display
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
&self.0.replacement
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for Helper {
|
||||
type Candidate = CompletionSuggestion;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = CompletionContext(&self.context);
|
||||
let (position, suggestions) = self.completer.complete(line, pos, &ctx);
|
||||
let suggestions = suggestions.into_iter().map(CompletionSuggestion).collect();
|
||||
Ok((position, suggestions))
|
||||
}
|
||||
|
||||
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
||||
let end = line.pos();
|
||||
line.replace(start..end, elected)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
type Hint = String;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
self.hinter.as_ref().and_then(|h| h.hint(line, pos, ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::highlight::Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> Cow<'b, str> {
|
||||
use std::borrow::Cow::Borrowed;
|
||||
|
||||
if default {
|
||||
Borrowed(&self.colored_prompt)
|
||||
} else {
|
||||
Borrowed(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let cfg = &self.context.configs().lock();
|
||||
if let Some(palette) = &cfg.syntax_config {
|
||||
Painter::paint_string(line, &self.context.scope, palette)
|
||||
} else {
|
||||
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::validate::Validator for Helper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct NuValidator {}
|
||||
|
||||
impl rustyline::validate::Validator for NuValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let src = ctx.input();
|
||||
|
||||
let (tokens, err) = nu_parser::lex(src, 0);
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::parse_block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustyline::validate::ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
let first = iter.next()?.tag.clone();
|
||||
let last = iter.last();
|
||||
|
||||
Some(match last {
|
||||
None => first,
|
||||
Some(last) => first.until(&last.tag),
|
||||
})
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::EvaluationContext;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,196 +0,0 @@
|
||||
use crate::completion;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub struct Helper {
|
||||
completer: NuCompleter,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
context: EvaluationContext,
|
||||
pub colored_prompt: String,
|
||||
validator: NuValidator,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub(crate) fn new(
|
||||
context: EvaluationContext,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
) -> Helper {
|
||||
Helper {
|
||||
completer: NuCompleter {},
|
||||
hinter,
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
validator: NuValidator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Candidate for completion::Suggestion {
|
||||
fn display(&self) -> &str {
|
||||
&self.display
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
&self.replacement
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for Helper {
|
||||
type Candidate = completion::Suggestion;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = completion::CompletionContext::new(&self.context);
|
||||
Ok(self.completer.complete(line, pos, &ctx))
|
||||
}
|
||||
|
||||
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
||||
let end = line.pos();
|
||||
line.replace(start..end, elected)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
type Hint = String;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::highlight::Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> Cow<'b, str> {
|
||||
use std::borrow::Cow::Borrowed;
|
||||
|
||||
if default {
|
||||
Borrowed(&self.colored_prompt)
|
||||
} else {
|
||||
Borrowed(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::validate::Validator for Helper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct NuValidator {}
|
||||
|
||||
impl rustyline::validate::Validator for NuValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let src = ctx.input();
|
||||
|
||||
let (tokens, err) = nu_parser::lex(src, 0);
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::parse_block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustyline::validate::ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
let first = iter.next()?.tag.clone();
|
||||
let last = iter.last();
|
||||
|
||||
Some(match last {
|
||||
None => first,
|
||||
Some(last) => first.until(&last.tag),
|
||||
})
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::basic_evaluation_context;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
@ -5,69 +5,68 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.30.0"
|
||||
version = "0.33.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = { version = "0.30.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.30.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.30.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.30.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.30.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.30.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.30.0", path = "../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.30.0", path = "../nu-ansi-term" }
|
||||
nu-data = { version="0.33.0", path="../nu-data" }
|
||||
nu-engine = { version="0.33.0", path="../nu-engine" }
|
||||
nu-errors = { version="0.33.0", path="../nu-errors" }
|
||||
nu-json = { version="0.33.0", path="../nu-json" }
|
||||
nu-path = { version="0.33.0", path="../nu-path" }
|
||||
nu-parser = { version="0.33.0", path="../nu-parser" }
|
||||
nu-plugin = { version="0.33.0", path="../nu-plugin" }
|
||||
nu-protocol = { version="0.33.0", path="../nu-protocol" }
|
||||
nu-source = { version="0.33.0", path="../nu-source" }
|
||||
nu-stream = { version="0.33.0", path="../nu-stream" }
|
||||
nu-table = { version="0.33.0", path="../nu-table" }
|
||||
nu-test-support = { version="0.33.0", path="../nu-test-support" }
|
||||
nu-value-ext = { version="0.33.0", path="../nu-value-ext" }
|
||||
nu-ansi-term = { version="0.33.0", path="../nu-ansi-term" }
|
||||
nu-pretty-hex = { version="0.33.0", path="../nu-pretty-hex" }
|
||||
|
||||
Inflector = "0.11"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
arboard = { version="1.1.0", optional=true }
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||
bigdecimal = { version="0.2.0", features=["serde"] }
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "1.0.1"
|
||||
calamine = "0.17.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono = { version="0.4.19", features=["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.11.0"
|
||||
crossterm = { version = "0.19.0", optional = true }
|
||||
crossterm = { version="0.19.0", optional=true }
|
||||
csv = "1.1.3"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
derive-new = "0.5.8"
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
directories-next = "2.0.0"
|
||||
dirs-next = "2.0.0"
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.28"
|
||||
filesize = "0.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
getset = "0.1.1"
|
||||
glob = "0.3.0"
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.7.0"
|
||||
ichwh = { version = "0.3.4", optional = true }
|
||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.14"
|
||||
md5 = "0.7.0"
|
||||
meval = "0.2.0"
|
||||
minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] }
|
||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
minus = { version="3.3.0", optional=true, features=["async_std_lib", "search"] }
|
||||
num-bigint = { version="0.3.1", features=["serde"] }
|
||||
num-format = { version="0.4.0", features=["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
parking_lot = "0.11.1"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.2.1"
|
||||
ptree = { version = "0.3.1", optional = true }
|
||||
ptree = { version="0.3.1", optional=true }
|
||||
query_interface = "0.3.5"
|
||||
quick-xml = "0.21.0"
|
||||
rand = "0.7.3"
|
||||
@ -75,30 +74,36 @@ rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "8.0.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
rustyline = { version="8.1.0", optional=true }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
shellexpand = "2.1.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.2.0"
|
||||
term = { version = "0.7.0", optional = true }
|
||||
term = { version="0.7.0", optional=true }
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.2"
|
||||
titlecase = "1.1.0"
|
||||
toml = "0.5.8"
|
||||
trash = { version = "1.3.0", optional = true }
|
||||
trash = { version="1.3.0", optional=true }
|
||||
unicode-segmentation = "1.7.1"
|
||||
url = "2.2.0"
|
||||
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
|
||||
which = { version = "4.0.2", optional = true }
|
||||
zip = { version = "0.5.9", optional = true }
|
||||
uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true }
|
||||
which = { version="4.1.0", optional=true }
|
||||
zip = { version="0.5.9", optional=true }
|
||||
|
||||
[dependencies.polars]
|
||||
git = "https://github.com/pola-rs/polars"
|
||||
rev = "f60d86bc0921bd42635e8a33e7aad28ebe62dc3e"
|
||||
version = "0.14.2"
|
||||
optional = true
|
||||
features = ["parquet", "json", "random", "pivot", "strings", "is_in"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
@ -113,7 +118,7 @@ users = "0.11.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.2"
|
||||
version = "0.25.3"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.5"
|
||||
@ -128,6 +133,5 @@ clipboard-cli = ["arboard"]
|
||||
rustyline-support = ["rustyline"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
directories = ["directories-next"]
|
||||
dirs = ["dirs-next"]
|
||||
table-pager = ["minus", "crossterm"]
|
||||
dataframe = ["nu-protocol/dataframe", "polars"]
|
||||
|
7
crates/nu-command/README.md
Normal file
7
crates/nu-command/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# nu-command
|
||||
|
||||
The Nu command crate contains the full set of internal commands, that is, the commands that can be form the set of built-in commands in a Nushell engine.
|
||||
|
||||
The default set of commands that Nushell ships with can be found in the [default context](src/default_context.rs).
|
||||
|
||||
The commands themselves live in the [commands module](src/commands/).
|
658
crates/nu-command/src/classified/external.rs
Normal file
658
crates/nu-command/src/classified/external.rs
Normal file
@ -0,0 +1,658 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{evaluate_baseline_expr, BufCodecReader};
|
||||
use nu_engine::{MaybeTextCodec, StringOrBinary};
|
||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::{borrow::Cow, io::BufReader};
|
||||
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
context.sync_path_to_env();
|
||||
if !context.host().lock().is_external_cmd(&command.name) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Command not found",
|
||||
format!("command {} not found", &command.name),
|
||||
&command.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, external_redirection)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn trim_double_quotes(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
|
||||
match (chars.next(), chars.next_back()) {
|
||||
(Some('"'), Some('"')) => chars.collect(),
|
||||
_ => input.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_where_needed(input: &str) -> String {
|
||||
input.split(' ').join("\\ ").split('\'').join("\\'")
|
||||
}
|
||||
|
||||
fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager().path();
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, context)?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
// what value we would put in its place.
|
||||
if value.value.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
match &value.value {
|
||||
UntaggedValue::Table(table) => {
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args.push((
|
||||
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||
is_literal,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not convert to positional arguments",
|
||||
"could not convert to positional arguments",
|
||||
value.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
//let trimmed_value_string = trim_quotes(&trimmed_value_string);
|
||||
command_args.push((trimmed_value_string, is_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|(arg, _is_literal)| {
|
||||
let arg = nu_path::expand_tilde_string(Cow::Borrowed(arg));
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if !_is_literal {
|
||||
let escaped = escape_double_quotes(&arg);
|
||||
add_double_quotes(&escaped)
|
||||
} else {
|
||||
let trimmed = trim_double_quotes(&arg);
|
||||
if trimmed != arg {
|
||||
escape_where_needed(&trimmed)
|
||||
} else {
|
||||
trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg.to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(
|
||||
&command,
|
||||
&path,
|
||||
&process_args[..],
|
||||
input,
|
||||
external_redirection,
|
||||
&context.scope,
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
command: &ExternalCommand,
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
let mut process = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut process = Command::new("cmd");
|
||||
process.arg("/c");
|
||||
process.arg(&command.name);
|
||||
for arg in args {
|
||||
// Clean the args before we use them:
|
||||
// https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe
|
||||
// cmd.exe needs to have a caret to escape a pipe
|
||||
let arg = arg.replace("|", "^|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
|
||||
let mut process = Command::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
};
|
||||
|
||||
process.current_dir(path);
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.get_env_vars());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
match external_redirection {
|
||||
ExternalRedirection::Stdout => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
}
|
||||
ExternalRedirection::Stderr => {
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
ExternalRedirection::StdoutAndStderr => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
if !input.is_empty() {
|
||||
process.stdin(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||
}
|
||||
|
||||
trace!(target: "nu::run::external", "built command {:?}", process);
|
||||
|
||||
// TODO Switch to async_std::process once it's stabilized
|
||||
match process.spawn() {
|
||||
Ok(mut child) => {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
|
||||
let mut stdin = child.stdin.take();
|
||||
|
||||
let stdin_write_tx = tx.clone();
|
||||
let stdout_read_tx = tx;
|
||||
let stdin_name_tag = command.name_tag.clone();
|
||||
let stdout_name_tag = command.name_tag;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !input.is_empty() {
|
||||
let mut stdin_write = stdin
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
|
||||
for value in input {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
if stdin_write.write(s.as_bytes()).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if stdin_write.write(b).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
println!("Unsupported: {:?}", unsupported);
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
"Received unexpected type from pipeline ({})",
|
||||
unsupported.type_name()
|
||||
),
|
||||
format!(
|
||||
"expected a string, got {} as input",
|
||||
unsupported.type_name()
|
||||
),
|
||||
stdin_name_tag.clone(),
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if external_redirection == ExternalRedirection::Stdout
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||
stdout
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stdout for external command",
|
||||
"can't redirect stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stdout);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_read = BufReader::new(stdout);
|
||||
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(
|
||||
s.clone(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(b) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(
|
||||
b.into_iter().collect(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if external_redirection == ExternalRedirection::Stderr
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stderr);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_reader = BufReader::new(stderr);
|
||||
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
// than what other shells will do.
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
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 {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"External command failed",
|
||||
"command failed",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::nothing(),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ChannelReceiver::new(rx);
|
||||
Ok(stream.into_input_stream())
|
||||
}
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
format!("{}", e),
|
||||
"failed to spawn",
|
||||
&command.name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelReceiver {
|
||||
rx: Arc<Mutex<mpsc::Receiver<Result<Value, ShellError>>>>,
|
||||
}
|
||||
|
||||
impl ChannelReceiver {
|
||||
pub fn new(rx: mpsc::Receiver<Result<Value, ShellError>>) -> Self {
|
||||
Self {
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ChannelReceiver {
|
||||
type Item = Result<Value, ShellError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rx = self.rx.lock();
|
||||
match rx.recv() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
(argument.starts_with('"') && argument.ends_with('"'))
|
||||
|| (argument.starts_with('\'') && argument.ends_with('\''))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_double_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||
// allocate new string only if required
|
||||
if argument.contains('"') {
|
||||
Cow::Owned(argument.replace('"', r#"\""#))
|
||||
} else {
|
||||
Cow::Borrowed(argument)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = argument.len();
|
||||
|
||||
Some(&argument[1..size - 1])
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
let mut original_paths = vec![];
|
||||
|
||||
if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) {
|
||||
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
original_paths
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{add_double_quotes, argument_is_quoted, escape_double_quotes, remove_quotes};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
// fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next() {
|
||||
// Ok(val) => {
|
||||
// if let Some(val) = val {
|
||||
// val.raw_value()
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// Err(_) => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
fn non_existent_run() {
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx = EvaluationContext::basic();
|
||||
|
||||
assert!(run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout).is_err());
|
||||
}
|
||||
|
||||
// fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = crate::cli::EvaluationContext::basic().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// ?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
||||
// match read(stream.into()) {
|
||||
// Some(Value {
|
||||
// value: UntaggedValue::Error(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
// None | _ => panic!("Command didn't fail."),
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn identifies_command_failed() -> Result<(), ShellError> {
|
||||
// block_on(failure_run())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
#[test]
|
||||
fn identifies_command_not_found() {
|
||||
non_existent_run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_escape_double_quotes() {
|
||||
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_quotes_from_argument_to_be_passed_in() {
|
||||
assert!(!argument_is_quoted(""));
|
||||
|
||||
assert!(!argument_is_quoted("'"));
|
||||
assert!(!argument_is_quoted("'a"));
|
||||
assert!(!argument_is_quoted("a"));
|
||||
assert!(!argument_is_quoted("a'"));
|
||||
assert!(argument_is_quoted("''"));
|
||||
|
||||
assert!(!argument_is_quoted(r#"""#));
|
||||
assert!(!argument_is_quoted(r#""a"#));
|
||||
assert!(!argument_is_quoted(r#"a"#));
|
||||
assert!(!argument_is_quoted(r#"a""#));
|
||||
assert!(argument_is_quoted(r#""""#));
|
||||
|
||||
assert!(!argument_is_quoted("'andrés"));
|
||||
assert!(!argument_is_quoted("andrés'"));
|
||||
assert!(!argument_is_quoted(r#""andrés"#));
|
||||
assert!(!argument_is_quoted(r#"andrés""#));
|
||||
assert!(argument_is_quoted("'andrés'"));
|
||||
assert!(argument_is_quoted(r#""andrés""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(remove_quotes(""), None);
|
||||
|
||||
assert_eq!(remove_quotes("'"), None);
|
||||
assert_eq!(remove_quotes("'a"), None);
|
||||
assert_eq!(remove_quotes("a"), None);
|
||||
assert_eq!(remove_quotes("a'"), None);
|
||||
assert_eq!(remove_quotes("''"), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes(r#"""#), None);
|
||||
assert_eq!(remove_quotes(r#""a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a""#), None);
|
||||
assert_eq!(remove_quotes(r#""""#), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes("'andrés"), None);
|
||||
assert_eq!(remove_quotes("andrés'"), None);
|
||||
assert_eq!(remove_quotes(r#""andrés"#), None);
|
||||
assert_eq!(remove_quotes(r#"andrés""#), None);
|
||||
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
||||
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
||||
}
|
||||
}
|
@ -1,371 +0,0 @@
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod all;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod any;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub mod autoenv;
|
||||
pub(crate) mod autoenv_trust;
|
||||
pub(crate) mod autoenv_untrust;
|
||||
pub(crate) mod autoview;
|
||||
pub(crate) mod benchmark;
|
||||
pub(crate) mod build_string;
|
||||
pub(crate) mod cal;
|
||||
pub(crate) mod cd;
|
||||
pub(crate) mod char_;
|
||||
pub(crate) mod chart;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub(crate) mod compact;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod constants;
|
||||
pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod def;
|
||||
pub(crate) mod default;
|
||||
pub mod default_context;
|
||||
pub(crate) mod describe;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
pub(crate) mod echo;
|
||||
pub(crate) mod empty;
|
||||
pub(crate) mod enter;
|
||||
pub(crate) mod every;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod first;
|
||||
pub(crate) mod flatten;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod from;
|
||||
pub(crate) mod from_csv;
|
||||
pub(crate) mod from_eml;
|
||||
pub(crate) mod from_ics;
|
||||
pub(crate) mod from_ini;
|
||||
pub(crate) mod from_json;
|
||||
pub(crate) mod from_ods;
|
||||
pub(crate) mod from_ssv;
|
||||
pub(crate) mod from_toml;
|
||||
pub(crate) mod from_tsv;
|
||||
pub(crate) mod from_url;
|
||||
pub(crate) mod from_vcf;
|
||||
pub(crate) mod from_xlsx;
|
||||
pub(crate) mod from_xml;
|
||||
pub(crate) mod from_yaml;
|
||||
pub(crate) mod get;
|
||||
pub(crate) mod group_by;
|
||||
pub(crate) mod group_by_date;
|
||||
pub(crate) mod hash_;
|
||||
pub(crate) mod headers;
|
||||
pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod length;
|
||||
pub(crate) mod let_;
|
||||
pub(crate) mod let_env;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
pub(crate) mod math;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod move_;
|
||||
pub(crate) mod next;
|
||||
pub(crate) mod nth;
|
||||
pub(crate) mod nu;
|
||||
pub(crate) mod open;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod pivot;
|
||||
pub(crate) mod prepend;
|
||||
pub(crate) mod prev;
|
||||
pub(crate) mod pwd;
|
||||
pub(crate) mod random;
|
||||
pub(crate) mod range;
|
||||
pub(crate) mod reduce;
|
||||
pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
pub(crate) mod rm;
|
||||
pub(crate) mod roll;
|
||||
pub(crate) mod rotate;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
pub(crate) mod seq;
|
||||
pub(crate) mod seq_dates;
|
||||
pub(crate) mod shells;
|
||||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod sleep;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod source;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod split_by;
|
||||
pub(crate) mod str_;
|
||||
pub(crate) mod table;
|
||||
pub(crate) mod tags;
|
||||
pub(crate) mod termsize;
|
||||
pub(crate) mod to;
|
||||
pub(crate) mod to_csv;
|
||||
pub(crate) mod to_html;
|
||||
pub(crate) mod to_json;
|
||||
pub(crate) mod to_md;
|
||||
pub(crate) mod to_toml;
|
||||
pub(crate) mod to_tsv;
|
||||
pub(crate) mod to_url;
|
||||
pub(crate) mod to_xml;
|
||||
pub(crate) mod to_yaml;
|
||||
pub(crate) mod uniq;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod url_;
|
||||
pub(crate) mod version;
|
||||
pub(crate) mod where_;
|
||||
pub(crate) mod which_;
|
||||
pub(crate) mod with_env;
|
||||
pub(crate) mod wrap;
|
||||
|
||||
pub(crate) use autoview::Autoview;
|
||||
pub(crate) use cd::Cd;
|
||||
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use ansi::AnsiStrip;
|
||||
pub(crate) use append::Command as Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||
pub(crate) use benchmark::Benchmark;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
pub(crate) use char_::Char;
|
||||
pub(crate) use chart::Chart;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::{
|
||||
Config, ConfigClear, ConfigGet, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
};
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use def::Def;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use describe::Describe;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::{Drop, DropColumn};
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use each::EachGroup;
|
||||
pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use empty::Command as Empty;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use into::Into;
|
||||
pub(crate) use into::IntoInt;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use all::Command as All;
|
||||
pub(crate) use any::Command as Any;
|
||||
pub(crate) use enter::Enter;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
pub(crate) use exit::Exit;
|
||||
pub(crate) use first::First;
|
||||
pub(crate) use flatten::Command as Flatten;
|
||||
pub(crate) use format::{FileSize, Format};
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_csv::FromCsv;
|
||||
pub(crate) use from_eml::FromEml;
|
||||
pub(crate) use from_ics::FromIcs;
|
||||
pub(crate) use from_ini::FromIni;
|
||||
pub(crate) use from_json::FromJson;
|
||||
pub(crate) use from_ods::FromOds;
|
||||
pub(crate) use from_ssv::FromSsv;
|
||||
pub(crate) use from_toml::FromToml;
|
||||
pub(crate) use from_tsv::FromTsv;
|
||||
pub(crate) use from_url::FromUrl;
|
||||
pub(crate) use from_vcf::FromVcf;
|
||||
pub(crate) use from_xlsx::FromXlsx;
|
||||
pub(crate) use from_xml::FromXml;
|
||||
pub(crate) use from_yaml::FromYaml;
|
||||
pub(crate) use from_yaml::FromYml;
|
||||
pub(crate) use get::Command as Get;
|
||||
pub(crate) use group_by::Command as GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use hash_::{Hash, HashBase64, HashMd5};
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Command as Insert;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use length::Length;
|
||||
pub(crate) use let_::Let;
|
||||
pub(crate) use let_env::LetEnv;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
|
||||
MathMinimum, MathMode, MathProduct, MathRound, MathSqrt, MathStddev, MathSummation,
|
||||
MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use move_::{Move, Mv};
|
||||
pub(crate) use next::Next;
|
||||
pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use path::{
|
||||
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathJoin, PathParse, PathSplit,
|
||||
PathType,
|
||||
};
|
||||
pub(crate) use pivot::Pivot;
|
||||
pub(crate) use prepend::Prepend;
|
||||
pub(crate) use prev::Previous;
|
||||
pub(crate) use pwd::Pwd;
|
||||
#[cfg(feature = "uuid_crate")]
|
||||
pub(crate) use random::RandomUUID;
|
||||
pub(crate) use random::{
|
||||
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
|
||||
};
|
||||
pub(crate) use range::Range;
|
||||
pub(crate) use reduce::Reduce;
|
||||
pub(crate) use reject::Reject;
|
||||
pub(crate) use rename::Rename;
|
||||
pub(crate) use reverse::Reverse;
|
||||
pub(crate) use rm::Remove;
|
||||
pub(crate) use roll::{Roll, RollColumn, RollUp};
|
||||
pub(crate) use rotate::{Rotate, RotateCounterClockwise};
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Command as Select;
|
||||
pub(crate) use seq::Seq;
|
||||
pub(crate) use seq_dates::SeqDates;
|
||||
pub(crate) use shells::Shells;
|
||||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::{Skip, SkipUntil, SkipWhile};
|
||||
pub(crate) use sleep::Sleep;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use source::Source;
|
||||
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
|
||||
pub(crate) use split_by::SplitBy;
|
||||
pub(crate) use str_::{
|
||||
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
|
||||
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLPad, StrLength, StrPascalCase, StrRPad,
|
||||
StrReverse, StrScreamingSnakeCase, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
|
||||
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
|
||||
};
|
||||
pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
pub(crate) use termsize::TermSize;
|
||||
pub(crate) use to::To;
|
||||
pub(crate) use to_csv::ToCsv;
|
||||
pub(crate) use to_html::ToHtml;
|
||||
pub(crate) use to_json::ToJson;
|
||||
pub(crate) use to_md::Command as ToMarkdown;
|
||||
pub(crate) use to_toml::ToToml;
|
||||
pub(crate) use to_tsv::ToTsv;
|
||||
pub(crate) use to_url::ToUrl;
|
||||
pub(crate) use to_xml::ToXml;
|
||||
pub(crate) use to_yaml::ToYaml;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
pub(crate) use where_::Command as Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::examples::{test_anchors, test_examples};
|
||||
use nu_engine::{whole_stream_command, Command};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
fn full_tests() -> Vec<Command> {
|
||||
vec![
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Empty),
|
||||
// whole_stream_command(Select),
|
||||
// whole_stream_command(Get),
|
||||
// Str Command Suite
|
||||
whole_stream_command(Str),
|
||||
whole_stream_command(StrToDecimal),
|
||||
whole_stream_command(StrToInteger),
|
||||
whole_stream_command(StrDowncase),
|
||||
whole_stream_command(StrUpcase),
|
||||
whole_stream_command(StrCapitalize),
|
||||
whole_stream_command(StrFindReplace),
|
||||
whole_stream_command(StrFrom),
|
||||
whole_stream_command(StrSubstring),
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrContains),
|
||||
whole_stream_command(StrIndexOf),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(StrTrimLeft),
|
||||
whole_stream_command(StrTrimRight),
|
||||
whole_stream_command(StrStartsWith),
|
||||
whole_stream_command(StrEndsWith),
|
||||
//whole_stream_command(StrCollect),
|
||||
whole_stream_command(StrLength),
|
||||
whole_stream_command(StrLPad),
|
||||
whole_stream_command(StrReverse),
|
||||
whole_stream_command(StrRPad),
|
||||
whole_stream_command(StrCamelCase),
|
||||
whole_stream_command(StrPascalCase),
|
||||
whole_stream_command(StrKebabCase),
|
||||
whole_stream_command(StrSnakeCase),
|
||||
whole_stream_command(StrScreamingSnakeCase),
|
||||
whole_stream_command(ToMarkdown),
|
||||
]
|
||||
}
|
||||
|
||||
fn only_examples() -> Vec<Command> {
|
||||
let mut commands = full_tests();
|
||||
commands.extend(vec![whole_stream_command(Flatten)]);
|
||||
commands
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
for cmd in only_examples() {
|
||||
test_examples(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracks_metadata() -> Result<(), ShellError> {
|
||||
for cmd in full_tests() {
|
||||
test_anchors(cmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::evaluate_baseline_expr;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
|
||||
};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
block: CapturedBlock,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"any?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("any?").required(
|
||||
"condition",
|
||||
SyntaxShape::RowCondition,
|
||||
"the condition that must match",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find if the table rows matches the condition."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
any(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_protocol::Value;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Find if a service is not running",
|
||||
example: "echo [[status]; [UP] [DOWN] [UP]] | any? status == DOWN",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
Example {
|
||||
description: "Check if any of the values is odd",
|
||||
example: "echo [2 4 1 6 8] | any? $(= $it mod 2) == 1",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn any(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = Arc::new(EvaluationContext::from_args(&args));
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (Arguments { block }, input) = args.process()?;
|
||||
|
||||
let condition = {
|
||||
if block.block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block.block[0].pipelines.get(0) {
|
||||
Some(item) => match item.list.get(0) {
|
||||
Some(ClassifiedCommand::Expr(expr)) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cond = Ok(InputStream::one(
|
||||
UntaggedValue::boolean(false).into_value(&tag),
|
||||
));
|
||||
|
||||
Ok(input
|
||||
.fold(cond, move |cond, row| {
|
||||
let condition = condition.clone();
|
||||
let ctx = ctx.clone();
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_vars(&block.captured.entries);
|
||||
ctx.scope.add_var("$it", row);
|
||||
|
||||
let condition = evaluate_baseline_expr(&condition, &*ctx);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let curr = cond?.drain_vec();
|
||||
let curr = curr
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to check with"))?;
|
||||
let cond = curr.as_bool()?;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => Ok(InputStream::one(
|
||||
UntaggedValue::boolean(cond || b).into_value(&curr.tag),
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
})?
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{FromValue, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Char;
|
||||
|
||||
impl WholeStreamCommand for Char {
|
||||
fn name(&self) -> &str {
|
||||
"char"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("char")
|
||||
.required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
.rest(SyntaxShape::String, "multiple Unicode bytes")
|
||||
.switch("unicode", "Unicode string i.e. 1f378", Some('u'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output special characters (eg. 'newline')."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Output newline",
|
||||
example: r#"char newline"#,
|
||||
result: Some(vec![Value::from("\n")]),
|
||||
},
|
||||
Example {
|
||||
description: "Output prompt character, newline and a hamburger character",
|
||||
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("\u{25b6}").into(),
|
||||
UntaggedValue::string("\n").into(),
|
||||
UntaggedValue::string("\u{2261}").into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Output Unicode character",
|
||||
example: r#"char -u 1f378"#,
|
||||
result: Some(vec![Value::from("\u{1f378}")]),
|
||||
},
|
||||
Example {
|
||||
description: "Output multi-byte Unicode character",
|
||||
example: r#"char -u 1F468 200D 1F466 200D 1F466"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
|
||||
let name: Tagged<String> = args.req(0)?;
|
||||
let rest: Vec<Value> = args.rest(1)?;
|
||||
let unicode = args.has_flag("unicode");
|
||||
|
||||
if unicode {
|
||||
if !rest.is_empty() {
|
||||
// Setup a new buffer to put all the Unicode bytes in
|
||||
let mut multi_byte = String::new();
|
||||
// Get the first byte
|
||||
let decoded_char = string_to_unicode_char(&name.item, &name.tag);
|
||||
match decoded_char {
|
||||
Ok(ch) => multi_byte.push(ch),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
// Get the rest of the bytes
|
||||
for byte_part in rest {
|
||||
let byte_part: Tagged<String> = FromValue::from_value(&byte_part)?;
|
||||
let decoded_char = string_to_unicode_char(&byte_part, &byte_part.tag);
|
||||
match decoded_char {
|
||||
Ok(ch) => multi_byte.push(ch),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(multi_byte).into_value(name.tag),
|
||||
)))
|
||||
} else {
|
||||
let decoded_char = string_to_unicode_char(&name.item, &name.tag);
|
||||
if let Ok(ch) = decoded_char {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(ch).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding Unicode character",
|
||||
"error decoding Unicode character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let special_character = str_to_character(&name.item);
|
||||
if let Some(output) = special_character {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error finding named character",
|
||||
"error finding named character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str, t: &Tag) -> Result<char, ShellError> {
|
||||
let decoded_char = u32::from_str_radix(s, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32);
|
||||
|
||||
if let Some(ch) = decoded_char {
|
||||
Ok(ch)
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding Unicode character",
|
||||
"error decoding Unicode character",
|
||||
t,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
"sp" | "space" => Some(" ".into()),
|
||||
// Unicode names came from https://www.compart.com/en/unicode
|
||||
// Private Use Area (U+E000-U+F8FF)
|
||||
// Unicode can't be mixed with Ansi or it will break width calculation
|
||||
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||
|
||||
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
|
||||
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
|
||||
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
|
||||
|
||||
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
|
||||
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
|
||||
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
|
||||
|
||||
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
|
||||
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_string()), // ⨯
|
||||
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||
|
||||
// Weather symbols
|
||||
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||
"moon" => Some("🌛".to_string()),
|
||||
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||
"snowy" | "snow" => Some("❄️".to_string()),
|
||||
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Char {})
|
||||
}
|
||||
}
|
@ -20,14 +20,14 @@ impl WholeStreamCommand for Chart {
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
if args.scope.get_command("chart bar").is_none() {
|
||||
if args.scope().get_command("chart bar").is_none() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"nu_plugin_chart not installed.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ActionStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_full_help(&Chart, &args.scope)).into_value(Tag::unknown()),
|
||||
UntaggedValue::string(get_full_help(&Chart, args.scope())).into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
@ -59,27 +59,16 @@ impl WholeStreamCommand for Histogram {
|
||||
|
||||
pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (input, args) = args.evaluate_once()?.parts();
|
||||
|
||||
let values: Vec<Value> = input.collect();
|
||||
|
||||
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 mut columns = args.rest::<ColumnPath>(0)?;
|
||||
let evaluate_with = args.get_flag::<ColumnPath>("use")?.map(evaluator);
|
||||
let values: Vec<Value> = args.input.collect();
|
||||
|
||||
let column_grouper = if !columns.is_empty() {
|
||||
match columns.remove(0).split_last() {
|
||||
Some((key, _)) => Some(key.as_string().tagged(&name)),
|
||||
None => None,
|
||||
}
|
||||
columns
|
||||
.remove(0)
|
||||
.split_last()
|
||||
.map(|(key, _)| key.as_string().tagged(&name))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -160,16 +149,19 @@ pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::str_::from::action(&percentage, &name, Some(2), true)?
|
||||
.as_string()?
|
||||
crate::commands::conversions::into::string::action(
|
||||
&percentage,
|
||||
&name,
|
||||
Some(2),
|
||||
true
|
||||
)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
|
||||
|
||||
let string = std::iter::repeat("*")
|
||||
.take(percentage.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
let string = "*".repeat(percentage.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize);
|
||||
|
||||
fact.insert_untagged(&frequency_column_name, UntaggedValue::string(string));
|
||||
|
||||
@ -177,7 +169,7 @@ pub fn histogram(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
|
||||
ReturnSuccess::value(fact.into_value())
|
||||
})
|
||||
.to_action_stream())
|
||||
.into_action_stream())
|
||||
}
|
||||
|
||||
fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
@ -209,7 +201,7 @@ fn splitter(
|
||||
)),
|
||||
}
|
||||
}),
|
||||
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(&row)),
|
||||
None => Box::new(move |_, row: &Value| nu_value_ext::as_string(row)),
|
||||
}
|
||||
}
|
||||
|
5
crates/nu-command/src/commands/charting/mod.rs
Normal file
5
crates/nu-command/src/commands/charting/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod chart;
|
||||
mod histogram;
|
||||
|
||||
pub use chart::Chart;
|
||||
pub use histogram::Histogram;
|
@ -1,668 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{evaluate_baseline_expr, BufCodecReader};
|
||||
use nu_engine::{MaybeTextCodec, StringOrBinary};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::{borrow::Cow, io::BufReader};
|
||||
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub(crate) fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
context.sync_path_to_env();
|
||||
if !context.host.lock().is_external_cmd(&command.name) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Command not found",
|
||||
format!("command {} not found", &command.name),
|
||||
&command.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, external_redirection)
|
||||
}
|
||||
|
||||
fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, context)?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
// what value we would put in its place.
|
||||
if value.value.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do the cleanup that we need to do on any argument going out:
|
||||
match &value.value {
|
||||
UntaggedValue::Table(table) => {
|
||||
for t in table {
|
||||
match &t.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
command_args.push((
|
||||
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||
is_literal,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Could not convert to positional arguments",
|
||||
"could not convert to positional arguments",
|
||||
value.tag(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||
command_args.push((trimmed_value_string, is_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let process_args = command_args
|
||||
.iter()
|
||||
.map(|(arg, _is_literal)| {
|
||||
let home_dir;
|
||||
|
||||
#[cfg(feature = "dirs")]
|
||||
{
|
||||
home_dir = dirs_next::home_dir;
|
||||
}
|
||||
#[cfg(not(feature = "dirs"))]
|
||||
{
|
||||
home_dir = || Some(std::path::PathBuf::from("/"));
|
||||
}
|
||||
|
||||
let arg = expand_tilde(arg.deref(), home_dir);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if !_is_literal {
|
||||
let escaped = escape_double_quotes(&arg);
|
||||
add_double_quotes(&escaped)
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(unquoted) = remove_quotes(&arg) {
|
||||
unquoted.to_string()
|
||||
} else {
|
||||
arg.as_ref().to_string()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
spawn(
|
||||
&command,
|
||||
&path,
|
||||
&process_args[..],
|
||||
input,
|
||||
external_redirection,
|
||||
&context.scope,
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
command: &ExternalCommand,
|
||||
path: &str,
|
||||
args: &[String],
|
||||
input: InputStream,
|
||||
external_redirection: ExternalRedirection,
|
||||
scope: &Scope,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let command = command.clone();
|
||||
|
||||
let mut process = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut process = Command::new("cmd");
|
||||
process.arg("/c");
|
||||
process.arg(&command.name);
|
||||
for arg in args {
|
||||
// Clean the args before we use them:
|
||||
let arg = arg.replace("|", "\\|");
|
||||
process.arg(&arg);
|
||||
}
|
||||
process
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
|
||||
let mut process = Command::new("sh");
|
||||
process.arg("-c").arg(cmd_with_args);
|
||||
process
|
||||
}
|
||||
};
|
||||
|
||||
process.current_dir(path);
|
||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||
|
||||
process.env_clear();
|
||||
process.envs(scope.get_env_vars());
|
||||
|
||||
// We want stdout regardless of what
|
||||
// we are doing ($it case or pipe stdin)
|
||||
match external_redirection {
|
||||
ExternalRedirection::Stdout => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
}
|
||||
ExternalRedirection::Stderr => {
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
ExternalRedirection::StdoutAndStderr => {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
if !input.is_empty() {
|
||||
process.stdin(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdin pipe");
|
||||
}
|
||||
|
||||
trace!(target: "nu::run::external", "built command {:?}", process);
|
||||
|
||||
// TODO Switch to async_std::process once it's stabilized
|
||||
if let Ok(mut child) = process.spawn() {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
|
||||
let mut stdin = child.stdin.take();
|
||||
|
||||
let stdin_write_tx = tx.clone();
|
||||
let stdout_read_tx = tx;
|
||||
let stdin_name_tag = command.name_tag.clone();
|
||||
let stdout_name_tag = command.name_tag;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if !input.is_empty() {
|
||||
let mut stdin_write = stdin
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
|
||||
for value in input {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
if stdin_write.write(s.as_bytes()).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||
if stdin_write.write(b).is_err() {
|
||||
// Other side has closed, so exit
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
unsupported => {
|
||||
println!("Unsupported: {:?}", unsupported);
|
||||
let _ = stdin_write_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!(
|
||||
"Received unexpected type from pipeline ({})",
|
||||
unsupported.type_name()
|
||||
),
|
||||
"expected a string",
|
||||
stdin_name_tag.clone(),
|
||||
)),
|
||||
tag: stdin_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if external_redirection == ExternalRedirection::Stdout
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stdout = if let Some(stdout) = child.stdout.take() {
|
||||
stdout
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stdout for external command",
|
||||
"can't redirect stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stdout);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_read = BufReader::new(stdout);
|
||||
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s.clone())),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(b) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(
|
||||
b.into_iter().collect(),
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if external_redirection == ExternalRedirection::Stderr
|
||||
|| external_redirection == ExternalRedirection::StdoutAndStderr
|
||||
{
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// let file = futures::io::AllowStdIo::new(stderr);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_reader = BufReader::new(stderr);
|
||||
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
||||
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error("<binary stderr>"),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stdout ({})", e),
|
||||
"unable to read from stdout",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
// than what other shells will do.
|
||||
let external_failed = match child.wait() {
|
||||
Err(_) => true,
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
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 {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"External command failed",
|
||||
"command failed",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::external_non_zero()),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ChannelReceiver::new(rx);
|
||||
Ok(stream.to_input_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Failed to spawn process",
|
||||
"failed to spawn",
|
||||
&command.name_tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelReceiver {
|
||||
rx: Arc<Mutex<mpsc::Receiver<Result<Value, ShellError>>>>,
|
||||
}
|
||||
|
||||
impl ChannelReceiver {
|
||||
pub fn new(rx: mpsc::Receiver<Result<Value, ShellError>>) -> Self {
|
||||
Self {
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ChannelReceiver {
|
||||
type Item = Result<Value, ShellError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rx = self.rx.lock();
|
||||
match rx.recv() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
|
||||
where
|
||||
SI: AsRef<str>,
|
||||
P: AsRef<std::path::Path>,
|
||||
HD: FnOnce() -> Option<P>,
|
||||
{
|
||||
shellexpand::tilde_with_context(input, home_dir)
|
||||
}
|
||||
|
||||
fn argument_is_quoted(argument: &str) -> bool {
|
||||
if argument.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
(argument.starts_with('"') && argument.ends_with('"'))
|
||||
|| (argument.starts_with('\'') && argument.ends_with('\''))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn add_double_quotes(argument: &str) -> String {
|
||||
format!("\"{}\"", argument)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||
// allocate new string only if required
|
||||
if argument.contains('"') {
|
||||
Cow::Owned(argument.replace('"', r#"\""#))
|
||||
} else {
|
||||
Cow::Borrowed(argument)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||
if !argument_is_quoted(argument) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let size = argument.len();
|
||||
|
||||
Some(&argument[1..size - 1])
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
let mut original_paths = vec![];
|
||||
|
||||
if let Some(paths) = std::env::var_os("PATH") {
|
||||
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
original_paths
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
||||
};
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_engine::basic_evaluation_context;
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
// fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next() {
|
||||
// Ok(val) => {
|
||||
// if let Some(val) = val {
|
||||
// val.raw_value()
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// Err(_) => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
fn non_existent_run() {
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
let input = InputStream::empty();
|
||||
let mut ctx =
|
||||
basic_evaluation_context().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout).is_err());
|
||||
}
|
||||
|
||||
// fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = crate::cli::basic_evaluation_context().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// ?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
||||
// match read(stream.into()) {
|
||||
// Some(Value {
|
||||
// value: UntaggedValue::Error(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
// None | _ => panic!("Command didn't fail."),
|
||||
// }
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn identifies_command_failed() -> Result<(), ShellError> {
|
||||
// block_on(failure_run())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
#[test]
|
||||
fn identifies_command_not_found() {
|
||||
non_existent_run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_escape_double_quotes() {
|
||||
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(argument_is_quoted(""), false);
|
||||
|
||||
assert_eq!(argument_is_quoted("'"), false);
|
||||
assert_eq!(argument_is_quoted("'a"), false);
|
||||
assert_eq!(argument_is_quoted("a"), false);
|
||||
assert_eq!(argument_is_quoted("a'"), false);
|
||||
assert_eq!(argument_is_quoted("''"), true);
|
||||
|
||||
assert_eq!(argument_is_quoted(r#"""#), false);
|
||||
assert_eq!(argument_is_quoted(r#""a"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"a"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"a""#), false);
|
||||
assert_eq!(argument_is_quoted(r#""""#), true);
|
||||
|
||||
assert_eq!(argument_is_quoted("'andrés"), false);
|
||||
assert_eq!(argument_is_quoted("andrés'"), false);
|
||||
assert_eq!(argument_is_quoted(r#""andrés"#), false);
|
||||
assert_eq!(argument_is_quoted(r#"andrés""#), false);
|
||||
assert_eq!(argument_is_quoted("'andrés'"), true);
|
||||
assert_eq!(argument_is_quoted(r#""andrés""#), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_quotes_from_argument_to_be_passed_in() {
|
||||
assert_eq!(remove_quotes(""), None);
|
||||
|
||||
assert_eq!(remove_quotes("'"), None);
|
||||
assert_eq!(remove_quotes("'a"), None);
|
||||
assert_eq!(remove_quotes("a"), None);
|
||||
assert_eq!(remove_quotes("a'"), None);
|
||||
assert_eq!(remove_quotes("''"), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes(r#"""#), None);
|
||||
assert_eq!(remove_quotes(r#""a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a"#), None);
|
||||
assert_eq!(remove_quotes(r#"a""#), None);
|
||||
assert_eq!(remove_quotes(r#""""#), Some(""));
|
||||
|
||||
assert_eq!(remove_quotes("'andrés"), None);
|
||||
assert_eq!(remove_quotes("andrés'"), None);
|
||||
assert_eq!(remove_quotes(r#""andrés"#), None);
|
||||
assert_eq!(remove_quotes(r#"andrés""#), None);
|
||||
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
||||
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expands_tilde_if_starts_with_tilde_character() {
|
||||
assert_eq!(
|
||||
expand_tilde("~", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
||||
"the_path_to_nu_light"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
|
||||
assert_eq!(
|
||||
expand_tilde("1~1", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
||||
"1~1"
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -18,7 +18,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clear(args)
|
||||
}
|
||||
|
||||
@ -31,22 +31,22 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
pub fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
|
||||
let result = if let Some(global_cfg) = &mut args.configs.lock().global_config {
|
||||
let result = if let Some(global_cfg) = &mut args.configs().lock().global_config {
|
||||
global_cfg.vars.clear();
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(global_cfg.vars.clone().into()).into_value(args.call_info.name_tag),
|
||||
)))
|
||||
|
||||
let value = UntaggedValue::Row(global_cfg.vars.clone().into()).into_value(name);
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
|
@ -2,8 +2,7 @@ use crate::prelude::*;
|
||||
use nu_engine::CommandArgs;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_stream::ActionStream;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
@ -20,22 +19,19 @@ impl WholeStreamCommand for Command {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag;
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
if let Some(global_cfg) = &args.configs.lock().global_config {
|
||||
if let Some(global_cfg) = &args.configs().lock().global_config {
|
||||
let result = global_cfg.vars.clone();
|
||||
Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Row(result.into()).into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,10 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
column_path: ColumnPath,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config get"
|
||||
@ -27,7 +22,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args)
|
||||
}
|
||||
|
||||
@ -40,28 +35,27 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let ctx = &args.context;
|
||||
|
||||
let (Arguments { column_path }, _) = args.process()?;
|
||||
let column_path = args.req(0)?;
|
||||
|
||||
let result = if let Some(global_cfg) = &ctx.configs.lock().global_config {
|
||||
let result = if let Some(global_cfg) = &ctx.configs().lock().global_config {
|
||||
let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
|
||||
let value = crate::commands::get::get_column_path(&column_path, &result)?;
|
||||
let value = crate::commands::filters::get::get_column_path(&column_path, &result)?;
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => list.into_iter().to_action_stream(),
|
||||
x => ActionStream::one(ReturnSuccess::value(x)),
|
||||
} => OutputStream::from_stream(list.into_iter()),
|
||||
x => OutputStream::one(x),
|
||||
})
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_protocol::{Primitive, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -18,7 +18,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
path(args)
|
||||
}
|
||||
|
||||
@ -31,16 +31,18 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
if let Some(global_cfg) = &mut args.configs.lock().global_config {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::FilePath(global_cfg.file_path.clone())),
|
||||
)))
|
||||
pub fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
if let Some(global_cfg) = &mut args.configs().lock().global_config {
|
||||
let value = UntaggedValue::Primitive(Primitive::FilePath(global_cfg.file_path.clone()))
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
remove: Tagged<String>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config remove"
|
||||
@ -28,7 +23,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove(args)
|
||||
}
|
||||
|
||||
@ -41,22 +36,22 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (Arguments { remove }, _) = args.process()?;
|
||||
pub fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
let remove: Tagged<String> = args.req(0)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
|
||||
if global_cfg.vars.contains_key(&key) {
|
||||
global_cfg.vars.swap_remove(&key);
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(remove.tag()),
|
||||
)]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
|
||||
let value: Value = UntaggedValue::row(global_cfg.vars.clone()).into_value(remove.tag);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
@ -65,11 +60,10 @@ pub fn remove(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
|
@ -1,16 +1,10 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
column_path: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set"
|
||||
@ -26,7 +20,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args)
|
||||
}
|
||||
|
||||
@ -56,18 +50,14 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
pub fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (
|
||||
Arguments {
|
||||
column_path,
|
||||
mut value,
|
||||
},
|
||||
_,
|
||||
) = args.process()?;
|
||||
let ctx = &args.context;
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
let column_path = args.req(0)?;
|
||||
let mut value: Value = args.req(1)?;
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
|
||||
let configuration = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
|
||||
|
||||
if let UntaggedValue::Table(rows) = &value.value {
|
||||
@ -85,19 +75,17 @@ pub fn set(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(name),
|
||||
)))
|
||||
let value = UntaggedValue::row(global_cfg.vars.clone()).into_value(name);
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
Ok(_) => Ok(ActionStream::empty()),
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
|
@ -1,16 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
set_into: Tagged<String>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set_into"
|
||||
@ -28,7 +23,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set_into(args)
|
||||
}
|
||||
|
||||
@ -41,20 +36,21 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_into(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
pub fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (Arguments { set_into: v }, input) = args.process()?;
|
||||
let ctx = &args.context;
|
||||
|
||||
let rows: Vec<Value> = input.collect();
|
||||
let key = v.to_string();
|
||||
let set_into: Tagged<String> = args.req(0)?;
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
let rows: Vec<Value> = args.input.collect();
|
||||
let key = set_into.to_string();
|
||||
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs().lock().global_config {
|
||||
if rows.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No values given for set_into",
|
||||
"needs value(s) from pipeline",
|
||||
v.tag(),
|
||||
set_into.tag(),
|
||||
));
|
||||
} else if rows.len() == 1 {
|
||||
// A single value
|
||||
@ -71,15 +67,14 @@ pub fn set_into(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(name),
|
||||
)))
|
||||
let value = UntaggedValue::row(global_cfg.vars.clone()).into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present())
|
||||
.into_value(name);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
};
|
||||
|
||||
result
|
||||
|
269
crates/nu-command/src/commands/conversions/into/binary.rs
Normal file
269
crates/nu-command/src/commands/conversions/into/binary.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use num_bigint::{BigInt, ToBigInt};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"into binary"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into binary")
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"column paths to convert to binary (for table input)",
|
||||
)
|
||||
.named(
|
||||
"skip",
|
||||
SyntaxShape::Int,
|
||||
"skip x number of bytes",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"bytes",
|
||||
SyntaxShape::Int,
|
||||
"show y number of bytes",
|
||||
Some('b'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert value to a binary primitive"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
into_binary(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "convert string to a nushell binary primitive",
|
||||
example:
|
||||
"echo 'This is a string that is exactly 52 characters long.' | into binary",
|
||||
result: Some(vec![UntaggedValue::binary(
|
||||
"This is a string that is exactly 52 characters long."
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert string to a nushell binary primitive",
|
||||
example:
|
||||
"echo 'This is a string that is exactly 52 characters long.' | into binary --skip 10",
|
||||
result: Some(vec![UntaggedValue::binary(
|
||||
"string that is exactly 52 characters long."
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert string to a nushell binary primitive",
|
||||
example:
|
||||
"echo 'This is a string that is exactly 52 characters long.' | into binary --skip 10 --bytes 10",
|
||||
result: Some(vec![UntaggedValue::binary(
|
||||
"string tha"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
.into()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert a number to a nushell binary primitive",
|
||||
example: "echo 1 | into binary",
|
||||
result: Some(vec![
|
||||
UntaggedValue::binary(i64::from(1).to_le_bytes().to_vec()).into()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "convert a boolean to a nushell binary primitive",
|
||||
example: "echo $true | into binary",
|
||||
result: Some(vec![
|
||||
UntaggedValue::binary(i64::from(1).to_le_bytes().to_vec()).into()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "convert a filesize to a nushell binary primitive",
|
||||
example: "ls | where name == LICENSE | get size | into binary",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "convert a filepath to a nushell binary primitive",
|
||||
example: "ls | where name == LICENSE | get name | path expand | into binary",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "convert a decimal to a nushell binary primitive",
|
||||
example: "echo 1.234 | into binary",
|
||||
result: Some(vec![
|
||||
UntaggedValue::binary(BigInt::from(1).to_bytes_le().1).into()
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn into_binary(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let skip: Option<Value> = args.get_flag("skip")?;
|
||||
let bytes: Option<Value> = args.get_flag("bytes")?;
|
||||
let column_paths: Vec<ColumnPath> = args.rest(0)?;
|
||||
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, v.tag(), &skip, &bytes)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let skip_clone = skip.clone();
|
||||
let bytes_clone = bytes.clone();
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag(), &skip_clone, &bytes_clone)),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
})
|
||||
.into_input_stream())
|
||||
}
|
||||
|
||||
fn int_to_endian(n: i64) -> Vec<u8> {
|
||||
if cfg!(target_endian = "little") {
|
||||
// eprintln!("Little Endian");
|
||||
n.to_le_bytes().to_vec()
|
||||
} else {
|
||||
// eprintln!("Big Endian");
|
||||
n.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
fn bigint_to_endian(n: &BigInt) -> Vec<u8> {
|
||||
if cfg!(target_endian = "little") {
|
||||
// eprintln!("Little Endian");
|
||||
n.to_bytes_le().1
|
||||
} else {
|
||||
// eprintln!("Big Endian");
|
||||
n.to_bytes_be().1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(
|
||||
input: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
skip: &Option<Value>,
|
||||
bytes: &Option<Value>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let skip_bytes = match skip {
|
||||
Some(s) => s.as_usize().unwrap_or(0),
|
||||
None => 0usize,
|
||||
};
|
||||
|
||||
let num_bytes = match bytes {
|
||||
Some(b) => b.as_usize().unwrap_or(0),
|
||||
None => 0usize,
|
||||
};
|
||||
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::binary(match prim {
|
||||
Primitive::Binary(b) => {
|
||||
if num_bytes == 0usize {
|
||||
b.to_vec().into_iter().skip(skip_bytes).collect()
|
||||
} else {
|
||||
b.to_vec()
|
||||
.into_iter()
|
||||
.skip(skip_bytes)
|
||||
.take(num_bytes)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Primitive::Int(n_ref) => int_to_endian(*n_ref),
|
||||
Primitive::BigInt(n_ref) => bigint_to_endian(n_ref),
|
||||
Primitive::Decimal(dec) => match dec.to_bigint() {
|
||||
Some(n) => bigint_to_endian(&n),
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert decimal to int",
|
||||
));
|
||||
}
|
||||
},
|
||||
Primitive::Filesize(a_filesize) => match a_filesize.to_bigint() {
|
||||
Some(n) => bigint_to_endian(&n),
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert filesize to bigint",
|
||||
));
|
||||
}
|
||||
},
|
||||
Primitive::String(a_string) => {
|
||||
// a_string.as_bytes().to_vec()
|
||||
if num_bytes == 0usize {
|
||||
a_string
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.skip(skip_bytes)
|
||||
.collect()
|
||||
} else {
|
||||
a_string
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.skip(skip_bytes)
|
||||
.take(num_bytes)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Primitive::Boolean(a_bool) => match a_bool {
|
||||
false => int_to_endian(0),
|
||||
true => int_to_endian(1),
|
||||
},
|
||||
Primitive::Date(a_date) => a_date.format("%c").to_string().as_bytes().to_vec(),
|
||||
Primitive::FilePath(a_filepath) => a_filepath
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
_ => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"'into binary' for non-numeric primitives",
|
||||
))
|
||||
}
|
||||
})
|
||||
.into_value(&tag)),
|
||||
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
|
||||
"specify column name to use, with 'into binary COLUMN'",
|
||||
"found table",
|
||||
tag,
|
||||
)),
|
||||
_ => Err(ShellError::unimplemented(
|
||||
"'into binary' for unsupported type",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
@ -18,10 +18,10 @@ impl WholeStreamCommand for Command {
|
||||
"Apply into function."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
|
||||
)))
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Command, args.scope())).into_value(Tag::unknown()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use num_bigint::{BigInt, ToBigInt};
|
||||
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
pub rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"into int"
|
||||
@ -29,7 +22,7 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Convert value to integer"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
into_int(args)
|
||||
}
|
||||
|
||||
@ -85,13 +78,14 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_int(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (Arguments { rest: column_paths }, input) = args.process()?;
|
||||
fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let column_paths: Vec<ColumnPath> = args.rest(0)?;
|
||||
|
||||
Ok(input
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag())?)
|
||||
action(&v, v.tag())
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
@ -101,10 +95,10 @@ fn into_int(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
Ok(ret)
|
||||
}
|
||||
})
|
||||
.to_action_stream())
|
||||
.into_input_stream())
|
||||
}
|
||||
|
||||
pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
@ -118,20 +112,34 @@ pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
}
|
||||
},
|
||||
Primitive::Decimal(dec) => match dec.to_bigint() {
|
||||
Some(n) => n,
|
||||
Some(n) => match n.to_i64() {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert decimal to int",
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert decimal to int",
|
||||
));
|
||||
}
|
||||
},
|
||||
Primitive::Int(n_ref) => n_ref.to_owned(),
|
||||
Primitive::Int(n_ref) => *n_ref,
|
||||
Primitive::Boolean(a_bool) => match a_bool {
|
||||
false => BigInt::from(0),
|
||||
true => BigInt::from(1),
|
||||
false => 0,
|
||||
true => 1,
|
||||
},
|
||||
Primitive::Filesize(a_filesize) => match a_filesize.to_bigint() {
|
||||
Some(n) => n,
|
||||
Some(n) => match n.to_i64() {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert filesize to bigint",
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"failed to convert filesize to bigint",
|
||||
@ -154,13 +162,17 @@ pub fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_from_string(a_string: &str, tag: &Tag) -> Result<BigInt, ShellError> {
|
||||
match a_string.parse::<BigInt>() {
|
||||
fn int_from_string(a_string: &str, tag: &Tag) -> Result<i64, ShellError> {
|
||||
match a_string.parse::<i64>() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(_) => match a_string.parse::<f64>() {
|
||||
Ok(res_float) => match res_float.to_bigint() {
|
||||
Some(n) => Ok(n),
|
||||
None => Err(ShellError::unimplemented("failed to convert f64 to int")),
|
||||
Ok(f) => match f.to_i64() {
|
||||
Some(i) => Ok(i),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Could not convert string value to int",
|
||||
"original value",
|
||||
tag.clone(),
|
||||
)),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"Could not convert string value to int",
|
9
crates/nu-command/src/commands/conversions/into/mod.rs
Normal file
9
crates/nu-command/src/commands/conversions/into/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod binary;
|
||||
mod command;
|
||||
mod int;
|
||||
pub mod string;
|
||||
|
||||
pub use binary::SubCommand as IntoBinary;
|
||||
pub use command::Command as Into;
|
||||
pub use int::SubCommand as IntoInt;
|
||||
pub use string::SubCommand as IntoString;
|
@ -1,9 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use num_bigint::{BigInt, BigUint, ToBigInt};
|
||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
||||
@ -14,22 +12,16 @@ use std::iter;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
struct Arguments {
|
||||
decimals: Option<Tagged<u64>>,
|
||||
group_digits: bool,
|
||||
column_paths: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str from"
|
||||
"into string"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str from")
|
||||
Signature::build("into string")
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert to string by column paths",
|
||||
"column paths to convert to string (for table input)",
|
||||
)
|
||||
.named(
|
||||
"decimals",
|
||||
@ -40,66 +32,81 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts numeric types to strings. Trims trailing zeros unless decimals parameter is specified."
|
||||
"Convert value to string"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
operate(args)
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
into_string(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "round to nearest integer",
|
||||
example: "echo 1.7 | str from -d 0",
|
||||
description: "convert decimal to string and round to nearest integer",
|
||||
example: "echo 1.7 | into string -d 0",
|
||||
result: Some(vec![UntaggedValue::string("2").into_untagged_value()]),
|
||||
},
|
||||
/*
|
||||
FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this
|
||||
Example {
|
||||
description: "format large number with localized digit grouping",
|
||||
example: "= 1000000.2 | str from -g",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("1,000,000.2").into_untagged_value()
|
||||
]),
|
||||
description: "convert decimal to string",
|
||||
example: "echo 4.3 | into string",
|
||||
result: Some(vec![UntaggedValue::string("4.3").into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert string to string",
|
||||
example: "echo '1234' | into string",
|
||||
result: Some(vec![UntaggedValue::string("1234").into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert boolean to string",
|
||||
example: "echo $true | into string",
|
||||
result: Some(vec![UntaggedValue::string("true").into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "convert date to string",
|
||||
example: "date now | into string",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "convert filepath to string",
|
||||
example: "ls Cargo.toml | get name | into string",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "convert filesize to string",
|
||||
example: "ls Cargo.toml | get size | into string",
|
||||
result: None,
|
||||
},
|
||||
*/
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (options, input) = args.extract(|params| {
|
||||
Ok(Arguments {
|
||||
decimals: params.get_flag("decimals")?,
|
||||
group_digits: false,
|
||||
column_paths: params.rest_args()?,
|
||||
})
|
||||
})?;
|
||||
fn into_string(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let decimals: Option<Tagged<u64>> = args.get_flag("decimals")?;
|
||||
let column_paths: Vec<ColumnPath> = args.rest(0)?;
|
||||
|
||||
let digits = options.decimals.as_ref().map(|tagged| tagged.item);
|
||||
let group_digits = options.group_digits;
|
||||
let digits = decimals.as_ref().map(|tagged| tagged.item);
|
||||
let group_digits = false;
|
||||
|
||||
Ok(input
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |v| {
|
||||
if options.column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag(), digits, group_digits)?)
|
||||
if column_paths.is_empty() {
|
||||
action(&v, v.tag(), digits, group_digits)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &options.column_paths {
|
||||
for path in &column_paths {
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag(), digits, group_digits)),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
Ok(ret)
|
||||
}
|
||||
})
|
||||
.to_action_stream())
|
||||
.into_input_stream())
|
||||
}
|
||||
|
||||
// TODO If you're using the with-system-locale feature and you're on Windows, Clang 3.9 or higher is also required.
|
||||
pub fn action(
|
||||
input: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
@ -109,6 +116,13 @@ pub fn action(
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(prim) => Ok(UntaggedValue::string(match prim {
|
||||
Primitive::Int(int) => {
|
||||
if group_digits {
|
||||
format_int(*int) // int.to_formatted_string(*locale)
|
||||
} else {
|
||||
int.to_string()
|
||||
}
|
||||
}
|
||||
Primitive::BigInt(int) => {
|
||||
if group_digits {
|
||||
format_bigint(int) // int.to_formatted_string(*locale)
|
||||
} else {
|
||||
@ -121,27 +135,45 @@ pub fn action(
|
||||
Primitive::Date(a_date) => a_date.format("%c").to_string(),
|
||||
Primitive::FilePath(a_filepath) => a_filepath.as_path().display().to_string(),
|
||||
Primitive::Filesize(a_filesize) => {
|
||||
let byte_string = InlineShape::format_bytes(a_filesize, None);
|
||||
let byte_string = InlineShape::format_bytes(*a_filesize, None);
|
||||
byte_string.1
|
||||
}
|
||||
Primitive::Nothing => "nothing".to_string(),
|
||||
_ => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"str from for non-numeric primitives",
|
||||
))
|
||||
return Err(ShellError::unimplemented(&format!(
|
||||
"into string for primitive: {:?}",
|
||||
prim
|
||||
)))
|
||||
}
|
||||
})
|
||||
.into_value(tag)),
|
||||
UntaggedValue::Row(_) => Err(ShellError::labeled_error(
|
||||
"specify column to use 'str from'",
|
||||
"specify column to use 'into string'",
|
||||
"found table",
|
||||
input.tag.clone(),
|
||||
)),
|
||||
_ => Err(ShellError::unimplemented(
|
||||
"str from for non-primitive, non-table types",
|
||||
)),
|
||||
UntaggedValue::Table(_) => Err(ShellError::unimplemented("into string for table")),
|
||||
_ => Err(ShellError::unimplemented("into string for non-primitive")),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_int(int: i64) -> String {
|
||||
format!("{}", int)
|
||||
|
||||
// TODO once platform-specific dependencies are stable (see Cargo.toml)
|
||||
// #[cfg(windows)]
|
||||
// {
|
||||
// int.to_formatted_string(&Locale::en)
|
||||
// }
|
||||
// #[cfg(not(windows))]
|
||||
// {
|
||||
// match SystemLocale::default() {
|
||||
// Ok(locale) => int.to_formatted_string(&locale),
|
||||
// Err(_) => int.to_formatted_string(&Locale::en),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn format_bigint(int: &BigInt) -> String {
|
||||
format!("{}", int)
|
||||
|
3
crates/nu-command/src/commands/conversions/mod.rs
Normal file
3
crates/nu-command/src/commands/conversions/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) mod into;
|
||||
|
||||
pub use into::*;
|
40
crates/nu-command/src/commands/core_commands/alias.rs
Normal file
40
crates/nu-command/src/commands/core_commands/alias.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Alias;
|
||||
|
||||
impl WholeStreamCommand for Alias {
|
||||
fn name(&self) -> &str {
|
||||
"alias"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "the name of the alias")
|
||||
.required("equals", SyntaxShape::String, "the equals sign")
|
||||
.rest(SyntaxShape::Any, "the expansion for the alias")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command to an expansion."
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
alias(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Alias ll to ls -l",
|
||||
example: "alias ll = ls -l",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alias(_: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
@ -5,11 +5,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Debug;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DebugArgs {
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Debug {
|
||||
fn name(&self) -> &str {
|
||||
"debug"
|
||||
@ -29,7 +24,9 @@ impl WholeStreamCommand for Debug {
|
||||
}
|
||||
|
||||
fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (DebugArgs { raw }, input) = args.process()?;
|
||||
let raw = args.has_flag("raw");
|
||||
let input = args.input;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if raw {
|
||||
@ -40,7 +37,7 @@ fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
ReturnSuccess::debug_value(v)
|
||||
}
|
||||
})
|
||||
.to_action_stream())
|
||||
.into_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
@ -36,7 +36,7 @@ pub fn describe(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)),
|
||||
)
|
||||
})
|
||||
.to_action_stream())
|
||||
.into_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
@ -2,14 +2,16 @@ use crate::prelude::*;
|
||||
use nu_engine::run_block;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, hir::ExternalRedirection, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DoArgs {
|
||||
block: CapturedBlock,
|
||||
ignore_errors: bool,
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Do {
|
||||
@ -21,17 +23,18 @@ impl WholeStreamCommand for Do {
|
||||
Signature::build("do")
|
||||
.required("block", SyntaxShape::Block, "the block to run ")
|
||||
.switch(
|
||||
"ignore_errors",
|
||||
"ignore-errors",
|
||||
"ignore errors as the block runs",
|
||||
Some('i'),
|
||||
)
|
||||
.rest(SyntaxShape::Any, "the parameter(s) for the block")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block, optionally ignoring errors."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
do_(args)
|
||||
}
|
||||
|
||||
@ -47,32 +50,35 @@ impl WholeStreamCommand for Do {
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: Some(vec![]),
|
||||
},
|
||||
Example {
|
||||
description: "Run the block with a parameter",
|
||||
example: r#"do { |x| $x + 100 } 55"#,
|
||||
result: Some(vec![UntaggedValue::int(155).into()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn do_(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||
fn do_(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let external_redirection = args.call_info.args.external_redirection;
|
||||
|
||||
let context = EvaluationContext::from_args(&raw_args);
|
||||
let (
|
||||
DoArgs {
|
||||
ignore_errors,
|
||||
mut block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process()?;
|
||||
let context = args.context().clone();
|
||||
let do_args = DoArgs {
|
||||
block: args.req(0)?,
|
||||
ignore_errors: args.has_flag("ignore-errors"),
|
||||
rest: args.rest(1)?,
|
||||
};
|
||||
|
||||
let block_redirection = match external_redirection {
|
||||
ExternalRedirection::None => {
|
||||
if ignore_errors {
|
||||
if do_args.ignore_errors {
|
||||
ExternalRedirection::Stderr
|
||||
} else {
|
||||
ExternalRedirection::None
|
||||
}
|
||||
}
|
||||
ExternalRedirection::Stdout => {
|
||||
if ignore_errors {
|
||||
if do_args.ignore_errors {
|
||||
ExternalRedirection::StdoutAndStderr
|
||||
} else {
|
||||
ExternalRedirection::Stdout
|
||||
@ -81,14 +87,30 @@ fn do_(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
x => x,
|
||||
};
|
||||
|
||||
if let Some(block) = std::sync::Arc::<nu_protocol::hir::Block>::get_mut(&mut block.block) {
|
||||
block.set_redirect(block_redirection);
|
||||
}
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&block.block, &context, input);
|
||||
|
||||
context.scope.add_vars(&do_args.block.captured.entries);
|
||||
|
||||
for (param, value) in do_args
|
||||
.block
|
||||
.block
|
||||
.params
|
||||
.positional
|
||||
.iter()
|
||||
.zip(do_args.rest)
|
||||
{
|
||||
context.scope.add_var(param.0.name(), value.clone());
|
||||
}
|
||||
|
||||
let result = run_block(
|
||||
&do_args.block.block,
|
||||
&context,
|
||||
args.input,
|
||||
block_redirection,
|
||||
);
|
||||
context.scope.exit_scope();
|
||||
|
||||
if ignore_errors {
|
||||
if do_args.ignore_errors {
|
||||
// To properly ignore errors we need to redirect stderr, consume it, and remove
|
||||
// any errors we see in the process.
|
||||
|
||||
@ -96,12 +118,12 @@ fn do_(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec();
|
||||
context.clear_errors();
|
||||
Ok(output.into_iter().to_action_stream())
|
||||
Ok(output.into_iter().into_output_stream())
|
||||
}
|
||||
Err(_) => Ok(ActionStream::empty()),
|
||||
Err(_) => Ok(OutputStream::empty()),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_action_stream())
|
||||
result.map(|x| x.into_output_stream())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::Zero;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
@ -40,23 +39,26 @@ impl WholeStreamCommand for Echo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_value_to_stream(v: Value) -> InputStream {
|
||||
match v {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => InputStream::from_stream(table.into_iter()),
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag,
|
||||
} => InputStream::from_stream(RangeIterator::new(*range, tag)),
|
||||
x => InputStream::one(x),
|
||||
}
|
||||
}
|
||||
|
||||
fn echo(args: CommandArgs) -> Result<InputStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let rest: Vec<Value> = args.rest(0)?;
|
||||
|
||||
let stream = rest.into_iter().map(|i| match i.as_string() {
|
||||
Ok(s) => InputStream::one(UntaggedValue::string(s).into_value(i.tag.clone())),
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => InputStream::from_stream(table.into_iter()),
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag,
|
||||
} => InputStream::from_stream(RangeIterator::new(*range, tag)),
|
||||
x => InputStream::one(x),
|
||||
},
|
||||
Ok(s) => InputStream::one(UntaggedValue::string(s).into_value(i.tag)),
|
||||
_ => expand_value_to_stream(i),
|
||||
});
|
||||
|
||||
Ok(InputStream::from_stream(stream.flatten()))
|
||||
@ -81,7 +83,7 @@ impl RangeIterator {
|
||||
};
|
||||
|
||||
let end = match range.to.0.item {
|
||||
Primitive::Nothing => Primitive::Int(u64::MAX.into()),
|
||||
Primitive::Nothing => Primitive::Int(i64::MAX),
|
||||
x => x,
|
||||
};
|
||||
|
||||
@ -121,11 +123,11 @@ impl Iterator for RangeIterator {
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Decimal(x)),
|
||||
UntaggedValue::Primitive(Primitive::Int(y)),
|
||||
) => x.cmp(&(BigDecimal::zero() + y)),
|
||||
) => x.cmp(&(BigDecimal::from(*y))),
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Int(x)),
|
||||
UntaggedValue::Primitive(Primitive::Decimal(y)),
|
||||
) => (BigDecimal::zero() + x).cmp(y),
|
||||
) => (BigDecimal::from(*x)).cmp(y),
|
||||
_ => {
|
||||
self.done = true;
|
||||
return Some(
|
@ -13,11 +13,6 @@ use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct Help;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HelpArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
@ -38,8 +33,9 @@ impl WholeStreamCommand for Help {
|
||||
|
||||
fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let scope = args.scope.clone();
|
||||
let (HelpArgs { rest }, ..) = args.process()?;
|
||||
let scope = args.scope().clone();
|
||||
|
||||
let rest: Vec<Tagged<String>> = args.rest(0)?;
|
||||
|
||||
if !rest.is_empty() {
|
||||
if rest[0].item == "commands" {
|
||||
@ -51,7 +47,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
// Internal only commands shouldn't be displayed
|
||||
.filter(|cmd_name| {
|
||||
scope
|
||||
.get_command(&cmd_name)
|
||||
.get_command(cmd_name)
|
||||
.filter(|command| !command.is_internal())
|
||||
.is_some()
|
||||
})
|
||||
@ -66,7 +62,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
) -> Result<(), ShellError> {
|
||||
let document_tag = rest[0].tag.clone();
|
||||
let value = command_dict(
|
||||
scope.get_command(&cmd_name).ok_or_else(|| {
|
||||
scope.get_command(cmd_name).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Could not load {}", cmd_name),
|
||||
"could not load command",
|
||||
@ -154,7 +150,7 @@ fn help(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
ReturnSuccess::value(short_desc.into_value())
|
||||
});
|
||||
|
||||
Ok(iterator.to_action_stream())
|
||||
Ok(iterator.into_action_stream())
|
||||
} else if rest[0].item == "generate_docs" {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(generate_docs(
|
||||
&scope,
|
74
crates/nu-command/src/commands/core_commands/history.rs
Normal file
74
crates/nu-command/src/commands/core_commands/history.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub struct History;
|
||||
|
||||
impl WholeStreamCommand for History {
|
||||
fn name(&self) -> &str {
|
||||
"history"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("history").switch("clear", "Clears out the history entries", Some('c'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display command history."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
history(args)
|
||||
}
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctx = &args.context;
|
||||
|
||||
let clear = args.has_flag("clear");
|
||||
|
||||
let path = if let Some(global_cfg) = &ctx.configs().lock().global_config {
|
||||
nu_data::config::path::history_path_or_default(global_cfg)
|
||||
} else {
|
||||
nu_data::config::path::default_history_path()
|
||||
};
|
||||
|
||||
if clear {
|
||||
// This is a NOOP, the logic to clear is handled in cli.rs
|
||||
Ok(ActionStream::empty())
|
||||
} else if let Ok(file) = File::open(path) {
|
||||
let reader = BufReader::new(file);
|
||||
// Skips the first line, which is a Rustyline internal
|
||||
let output = reader.lines().skip(1).filter_map(move |line| match line {
|
||||
Ok(line) => Some(ReturnSuccess::value(
|
||||
UntaggedValue::string(line).into_value(tag.clone()),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
|
||||
Ok(output.into_action_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Could not open history",
|
||||
"history file could not be opened",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::History;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(History {})
|
||||
}
|
||||
}
|
@ -57,11 +57,11 @@ impl WholeStreamCommand for If {
|
||||
]
|
||||
}
|
||||
}
|
||||
fn if_command(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let context = Arc::new(EvaluationContext::from_args(&raw_args));
|
||||
fn if_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let external_redirection = args.call_info.args.external_redirection;
|
||||
let context = Arc::new(args.context.clone());
|
||||
|
||||
let args = raw_args.evaluate_once()?;
|
||||
let condition: CapturedBlock = args.req(0)?;
|
||||
let then_case: CapturedBlock = args.req(1)?;
|
||||
let else_case: CapturedBlock = args.req(2)?;
|
||||
@ -100,14 +100,14 @@ fn if_command(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
context.scope.add_vars(&condition.captured.entries);
|
||||
|
||||
//FIXME: should we use the scope that's brought in as well?
|
||||
let condition = evaluate_baseline_expr(&cond, &*context);
|
||||
let condition = evaluate_baseline_expr(cond, &*context);
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => {
|
||||
let result = if b {
|
||||
run_block(&then_case.block, &*context, input)
|
||||
run_block(&then_case.block, &*context, input, external_redirection)
|
||||
} else {
|
||||
run_block(&else_case.block, &*context, input)
|
||||
run_block(&else_case.block, &*context, input, external_redirection)
|
||||
};
|
||||
context.scope.exit_scope();
|
||||
|
49
crates/nu-command/src/commands/core_commands/ignore.rs
Normal file
49
crates/nu-command/src/commands/core_commands/ignore.rs
Normal file
@ -0,0 +1,49 @@
|
||||
extern crate unicode_segmentation;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Ignore;
|
||||
|
||||
impl WholeStreamCommand for Ignore {
|
||||
fn name(&self) -> &str {
|
||||
"ignore"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ignore")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Ignore the output of the previous command in the pipeline"
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let _: Vec<_> = args.input.collect();
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Ignore the output of an echo command",
|
||||
example: r#"echo done | ignore"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ignore;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ignore {})
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{evaluate_baseline_expr, WholeStreamCommand};
|
||||
use nu_engine::{evaluate_baseline_expr, FromValue, WholeStreamCommand};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use nu_protocol::{
|
||||
hir::{CapturedBlock, ClassifiedCommand},
|
||||
Signature, SyntaxShape, UntaggedValue,
|
||||
};
|
||||
|
||||
pub struct Let;
|
||||
|
||||
@ -32,20 +34,41 @@ impl WholeStreamCommand for Let {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
vec![
|
||||
Example {
|
||||
description: "Assign a simple value to a variable",
|
||||
example: "let x = 3",
|
||||
result: Some(vec![]),
|
||||
},
|
||||
Example {
|
||||
description: "Assign the result of an expression to a variable",
|
||||
example: "let result = (3 + 7); echo $result",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Create a variable using the full name",
|
||||
example: "let $three = 3",
|
||||
result: Some(vec![]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn letcmd(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let args = args.evaluate_once()?;
|
||||
let ctx = &args.context;
|
||||
let positional = args
|
||||
.call_info
|
||||
.args
|
||||
.positional
|
||||
.expect("Internal error: type checker should require args");
|
||||
|
||||
//let (LetArgs { name, rhs, .. }, _) = args.process()?;
|
||||
let name: Tagged<String> = args.req(0)?;
|
||||
let rhs: CapturedBlock = args.req(2)?;
|
||||
let var_name = positional[0].var_name()?;
|
||||
let rhs_raw = evaluate_baseline_expr(&positional[2], &ctx)?;
|
||||
let tag: Tag = positional[2].span.into();
|
||||
|
||||
let (expr, captured) = {
|
||||
let rhs: CapturedBlock = FromValue::from_value(&rhs_raw)?;
|
||||
|
||||
let (expr, _) = {
|
||||
if rhs.block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a value",
|
||||
@ -75,24 +98,15 @@ pub fn letcmd(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
};
|
||||
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_vars(&captured.entries);
|
||||
|
||||
let value = evaluate_baseline_expr(&expr, &ctx);
|
||||
|
||||
let value = evaluate_baseline_expr(expr, &ctx);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let value = value?;
|
||||
|
||||
let name = if name.item.starts_with('$') {
|
||||
name.item
|
||||
} else {
|
||||
format!("${}", name.item)
|
||||
};
|
||||
|
||||
// Note: this is a special case for setting the context from a command
|
||||
// In this case, if we don't set it now, we'll lose the scope that this
|
||||
// variable should be set into.
|
||||
ctx.scope.add_var(name, value);
|
||||
ctx.scope.add_var(var_name, value);
|
||||
|
||||
Ok(ActionStream::empty())
|
||||
}
|
35
crates/nu-command/src/commands/core_commands/mod.rs
Normal file
35
crates/nu-command/src/commands/core_commands/mod.rs
Normal file
@ -0,0 +1,35 @@
|
||||
mod alias;
|
||||
mod debug;
|
||||
mod def;
|
||||
mod describe;
|
||||
mod do_;
|
||||
pub(crate) mod echo;
|
||||
mod help;
|
||||
mod history;
|
||||
mod if_;
|
||||
mod ignore;
|
||||
mod let_;
|
||||
mod nu_plugin;
|
||||
mod nu_signature;
|
||||
mod source;
|
||||
mod tags;
|
||||
mod version;
|
||||
|
||||
pub use self::nu_plugin::SubCommand as NuPlugin;
|
||||
pub use self::nu_signature::{
|
||||
loglevels, testbins, version as core_version, Command as NuSignature,
|
||||
};
|
||||
pub use alias::Alias;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
pub use describe::Describe;
|
||||
pub use do_::Do;
|
||||
pub use echo::Echo;
|
||||
pub use help::Help;
|
||||
pub use history::History;
|
||||
pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use let_::Let;
|
||||
pub use source::Source;
|
||||
pub use tags::Tags;
|
||||
pub use version::{version, Version};
|
@ -1,10 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_engine::filesystem::path::canonicalize;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_path::canonicalize;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
@ -46,9 +46,10 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let scope = args.scope.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (Arguments { load_path }, _) = args.process()?;
|
||||
let scope = args.scope().clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
|
||||
let load_path: Option<Tagged<PathBuf>> = args.get_flag("load")?;
|
||||
|
||||
if let Some(Tagged {
|
||||
item: load_path,
|
69
crates/nu-command/src/commands/core_commands/nu_signature.rs
Normal file
69
crates/nu-command/src/commands/core_commands/nu_signature.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"nu"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu")
|
||||
.switch("version", "Display Nu version", Some('v'))
|
||||
.switch("stdin", "redirect stdin", None)
|
||||
.switch("skip-plugins", "do not load plugins", None)
|
||||
.switch("no-history", "don't save history", None)
|
||||
.named(
|
||||
"commands",
|
||||
SyntaxShape::String,
|
||||
"commands to run",
|
||||
Some('c'),
|
||||
)
|
||||
.named(
|
||||
"testbin",
|
||||
SyntaxShape::String,
|
||||
"test bin: echo_env, cococo, iecho, fail, nonu, chop, repeater, meow",
|
||||
None,
|
||||
)
|
||||
.named("develop", SyntaxShape::String, "trace mode", None)
|
||||
.named("debug", SyntaxShape::String, "debug mode", None)
|
||||
.named(
|
||||
"loglevel",
|
||||
SyntaxShape::String,
|
||||
"LEVEL: error, warn, info, debug, trace",
|
||||
Some('l'),
|
||||
)
|
||||
.named(
|
||||
"config-file",
|
||||
SyntaxShape::FilePath,
|
||||
"custom configuration source file",
|
||||
None,
|
||||
)
|
||||
.rest(SyntaxShape::String, "source file(s) to run")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Nu - A new type of shell."
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
|
||||
pub fn testbins() -> Vec<String> {
|
||||
vec![
|
||||
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
|
||||
]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn loglevels() -> Vec<String> {
|
||||
vec!["error", "warn", "info", "debug", "trace"]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
@ -2,10 +2,12 @@ use crate::prelude::*;
|
||||
use nu_engine::{script, WholeStreamCommand};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::expand_path;
|
||||
use nu_path::expand_path;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
pub struct Source;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -21,7 +23,7 @@ impl WholeStreamCommand for Source {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("source").required(
|
||||
"filename",
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::FilePath,
|
||||
"the filepath to the script file to source",
|
||||
)
|
||||
}
|
||||
@ -40,19 +42,19 @@ impl WholeStreamCommand for Source {
|
||||
}
|
||||
|
||||
pub fn source(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (SourceArgs { filename }, _) = args.process()?;
|
||||
let ctx = &args.context;
|
||||
let filename: Tagged<String> = args.req(0)?;
|
||||
|
||||
// Note: this is a special case for setting the context from a command
|
||||
// In this case, if we don't set it now, we'll lose the scope that this
|
||||
// variable should be set into.
|
||||
let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned());
|
||||
let contents = std::fs::read_to_string(&expand_path(Cow::Borrowed(Path::new(&filename.item))));
|
||||
match contents {
|
||||
Ok(contents) => {
|
||||
let result = script::run_script_standalone(contents, true, &ctx, false);
|
||||
|
||||
if let Err(err) = result {
|
||||
ctx.error(err.into());
|
||||
ctx.error(err);
|
||||
}
|
||||
Ok(ActionStream::empty())
|
||||
}
|
@ -48,7 +48,7 @@ fn tags(args: CommandArgs) -> ActionStream {
|
||||
|
||||
tags.into_value()
|
||||
})
|
||||
.to_action_stream()
|
||||
.into_action_stream()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
@ -23,7 +23,7 @@ impl WholeStreamCommand for Version {
|
||||
"Display Nu version."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
version(args)
|
||||
}
|
||||
|
||||
@ -36,14 +36,14 @@ impl WholeStreamCommand for Version {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
pub fn version(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.args.span;
|
||||
|
||||
let mut indexmap = IndexMap::with_capacity(4);
|
||||
|
||||
indexmap.insert(
|
||||
"version".to_string(),
|
||||
UntaggedValue::string(clap::crate_version!()).into_value(&tag),
|
||||
UntaggedValue::string(super::nu_signature::version()).into_value(&tag),
|
||||
);
|
||||
|
||||
let branch: Option<&str> = Some(shadow::BRANCH).filter(|x| !x.is_empty());
|
||||
@ -140,8 +140,45 @@ pub fn version(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
features_enabled().join(", ").to_string_value_create_tag(),
|
||||
);
|
||||
|
||||
// Manually create a list of all possible plugin names
|
||||
let all_plugins = vec![
|
||||
"fetch",
|
||||
"inc",
|
||||
"match",
|
||||
"post",
|
||||
"ps",
|
||||
"sys",
|
||||
"textview",
|
||||
"binaryview",
|
||||
"chart bar",
|
||||
"chart line",
|
||||
"from bson",
|
||||
"from sqlite",
|
||||
"query json",
|
||||
"s3",
|
||||
"selector",
|
||||
"start",
|
||||
"to bson",
|
||||
"to sqlite",
|
||||
"tree",
|
||||
"xpath",
|
||||
];
|
||||
|
||||
// Get a list of command names and check for plugins
|
||||
let installed_plugins = args
|
||||
.scope()
|
||||
.get_command_names()
|
||||
.into_iter()
|
||||
.filter(|cmd| all_plugins.contains(&cmd.as_str()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
indexmap.insert(
|
||||
"installed_plugins".to_string(),
|
||||
installed_plugins.join(", ").to_string_value_create_tag(),
|
||||
);
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
Ok(ActionStream::one(value))
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
fn features_enabled() -> Vec<String> {
|
||||
@ -152,16 +189,6 @@ fn features_enabled() -> Vec<String> {
|
||||
names.push("ctrlc".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "dirs")]
|
||||
{
|
||||
names.push("dirs".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "directories")]
|
||||
{
|
||||
names.push("directories".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ptree")]
|
||||
{
|
||||
names.push("ptree".to_string());
|
||||
@ -192,11 +219,6 @@ fn features_enabled() -> Vec<String> {
|
||||
names.push("which".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ichwh")]
|
||||
{
|
||||
names.push("ichwh".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "zip")]
|
||||
{
|
||||
names.push("zip".to_string());
|
409
crates/nu-command/src/commands/dataframe/aggregate.rs
Normal file
409
crates/nu-command/src/commands/dataframe/aggregate.rs
Normal file
@ -0,0 +1,409 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, PolarsData},
|
||||
Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use polars::{
|
||||
frame::groupby::GroupBy,
|
||||
prelude::{DataType, PolarsError, Series},
|
||||
};
|
||||
|
||||
use super::utils::convert_columns;
|
||||
|
||||
enum Operation {
|
||||
Mean,
|
||||
Sum,
|
||||
Min,
|
||||
Max,
|
||||
First,
|
||||
Last,
|
||||
Nunique,
|
||||
Quantile(f64),
|
||||
Median,
|
||||
Var,
|
||||
Std,
|
||||
Count,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn from_tagged(
|
||||
name: &Tagged<String>,
|
||||
quantile: Option<Tagged<f64>>,
|
||||
) -> Result<Operation, ShellError> {
|
||||
match name.item.as_ref() {
|
||||
"mean" => Ok(Operation::Mean),
|
||||
"sum" => Ok(Operation::Sum),
|
||||
"min" => Ok(Operation::Min),
|
||||
"max" => Ok(Operation::Max),
|
||||
"first" => Ok(Operation::First),
|
||||
"last" => Ok(Operation::Last),
|
||||
"nunique" => Ok(Operation::Nunique),
|
||||
"quantile" => {
|
||||
match quantile {
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Quantile value not fount",
|
||||
"Quantile operation requires quantile value",
|
||||
&name.tag,
|
||||
)),
|
||||
Some(value ) => {
|
||||
if (value.item < 0.0) | (value.item > 1.0) {
|
||||
Err(ShellError::labeled_error(
|
||||
"Inappropriate quantile",
|
||||
"Quantile value should be between 0.0 and 1.0",
|
||||
&value.tag,
|
||||
))
|
||||
} else {
|
||||
Ok(Operation::Quantile(value.item))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"median" => Ok(Operation::Median),
|
||||
"var" => Ok(Operation::Var),
|
||||
"std" => Ok(Operation::Std),
|
||||
"count" => Ok(Operation::Count),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Operation not fount",
|
||||
"Operation does not exist",
|
||||
&name.tag,
|
||||
"Perhaps you want: mean, sum, min, max, first, last, nunique, quantile, median, var, std, or count",
|
||||
&name.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe aggregate"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs an aggregation operation on a dataframe or groupby object"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe aggregate")
|
||||
.required("operation", SyntaxShape::String, "aggregate operation")
|
||||
.optional(
|
||||
"selection",
|
||||
SyntaxShape::Table,
|
||||
"columns to perform aggregation",
|
||||
)
|
||||
.named(
|
||||
"quantile",
|
||||
SyntaxShape::Number,
|
||||
"quantile value for quantile operation",
|
||||
Some('q'),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Aggregate sum by grouping by column a and summing on col b",
|
||||
example:
|
||||
"[[a b]; [one 1] [one 2]] | dataframe to-df | dataframe group-by [a] | dataframe aggregate sum",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Aggregate sum in dataframe columns",
|
||||
example: "[[a b]; [4 1] [5 2]] | dataframe to-df | dataframe aggregate sum",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Aggregate sum in series",
|
||||
example: "[4 1 5 6] | dataframe to-series | dataframe aggregate sum",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let quantile: Option<Tagged<f64>> = args.get_flag("quantile")?;
|
||||
let operation: Tagged<String> = args.req(0)?;
|
||||
let op = Operation::from_tagged(&operation, quantile)?;
|
||||
|
||||
// Extracting the selection columns of the columns to perform the aggregation
|
||||
let agg_cols: Option<Vec<Value>> = args.opt(1)?;
|
||||
let (selection, agg_span) = match agg_cols {
|
||||
Some(cols) => {
|
||||
let (agg_string, agg_span) = convert_columns(&cols, &tag)?;
|
||||
(Some(agg_string), agg_span)
|
||||
}
|
||||
None => (None, Span::unknown()),
|
||||
};
|
||||
|
||||
let value = args.input.next().ok_or_else(|| {
|
||||
ShellError::labeled_error("Empty stream", "No value found in the stream", &tag)
|
||||
})?;
|
||||
|
||||
match value.value {
|
||||
UntaggedValue::DataFrame(PolarsData::GroupBy(nu_groupby)) => {
|
||||
let groupby = nu_groupby.to_groupby()?;
|
||||
|
||||
let groupby = match &selection {
|
||||
Some(cols) => groupby.select(cols),
|
||||
None => groupby,
|
||||
};
|
||||
|
||||
let res = perform_groupby_aggregation(groupby, op, &operation.tag, &agg_span)?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
|
||||
let df = df.as_ref();
|
||||
|
||||
let res = match &selection {
|
||||
Some(cols) => {
|
||||
let df = df
|
||||
.select(cols)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &agg_span, None))?;
|
||||
|
||||
perform_dataframe_aggregation(&df, op, &operation.tag)
|
||||
}
|
||||
None => perform_dataframe_aggregation(&df, op, &operation.tag),
|
||||
}?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
|
||||
let value = perform_series_aggregation(series.as_ref(), op, &operation.tag)?;
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"No groupby or dataframe",
|
||||
"no groupby or found in input stream",
|
||||
&value.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_groupby_aggregation(
|
||||
groupby: GroupBy,
|
||||
operation: Operation,
|
||||
operation_tag: &Tag,
|
||||
agg_span: &Span,
|
||||
) -> Result<polars::prelude::DataFrame, ShellError> {
|
||||
match operation {
|
||||
Operation::Mean => groupby.mean(),
|
||||
Operation::Sum => groupby.sum(),
|
||||
Operation::Min => groupby.min(),
|
||||
Operation::Max => groupby.max(),
|
||||
Operation::First => groupby.first(),
|
||||
Operation::Last => groupby.last(),
|
||||
Operation::Nunique => groupby.n_unique(),
|
||||
Operation::Quantile(quantile) => groupby.quantile(quantile),
|
||||
Operation::Median => groupby.median(),
|
||||
Operation::Var => groupby.var(),
|
||||
Operation::Std => groupby.std(),
|
||||
Operation::Count => groupby.count(),
|
||||
}
|
||||
.map_err(|e| {
|
||||
let span = match &e {
|
||||
PolarsError::NotFound(_) => agg_span,
|
||||
_ => &operation_tag.span,
|
||||
};
|
||||
|
||||
parse_polars_error::<&str>(&e, span, None)
|
||||
})
|
||||
}
|
||||
|
||||
fn perform_dataframe_aggregation(
|
||||
dataframe: &polars::prelude::DataFrame,
|
||||
operation: Operation,
|
||||
operation_tag: &Tag,
|
||||
) -> Result<polars::prelude::DataFrame, ShellError> {
|
||||
match operation {
|
||||
Operation::Mean => Ok(dataframe.mean()),
|
||||
Operation::Sum => Ok(dataframe.sum()),
|
||||
Operation::Min => Ok(dataframe.min()),
|
||||
Operation::Max => Ok(dataframe.max()),
|
||||
Operation::Quantile(quantile) => dataframe
|
||||
.quantile(quantile)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &operation_tag.span, None)),
|
||||
Operation::Median => Ok(dataframe.median()),
|
||||
Operation::Var => Ok(dataframe.var()),
|
||||
Operation::Std => Ok(dataframe.std()),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Not valid operation",
|
||||
"operation not valid for dataframe",
|
||||
&operation_tag.span,
|
||||
"Perhaps you want: mean, sum, min, max, quantile, median, var, or std",
|
||||
&operation_tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_series_aggregation(
|
||||
series: &Series,
|
||||
operation: Operation,
|
||||
operation_tag: &Tag,
|
||||
) -> Result<Value, ShellError> {
|
||||
match operation {
|
||||
Operation::Mean => {
|
||||
let res = match series.mean() {
|
||||
Some(val) => UntaggedValue::Primitive(val.into()),
|
||||
None => UntaggedValue::Primitive(0.into()),
|
||||
};
|
||||
|
||||
let value = Value {
|
||||
value: res,
|
||||
tag: operation_tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(operation_tag.clone());
|
||||
data.insert_value("mean", value);
|
||||
|
||||
Ok(data.into_value())
|
||||
}
|
||||
Operation::Median => {
|
||||
let res = match series.median() {
|
||||
Some(val) => UntaggedValue::Primitive(val.into()),
|
||||
None => UntaggedValue::Primitive(0.into()),
|
||||
};
|
||||
|
||||
let value = Value {
|
||||
value: res,
|
||||
tag: operation_tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(operation_tag.clone());
|
||||
data.insert_value("median", value);
|
||||
|
||||
Ok(data.into_value())
|
||||
}
|
||||
Operation::Sum => {
|
||||
let untagged = match series.dtype() {
|
||||
DataType::Int8
|
||||
| DataType::Int16
|
||||
| DataType::Int32
|
||||
| DataType::Int64
|
||||
| DataType::UInt8
|
||||
| DataType::UInt16
|
||||
| DataType::UInt32
|
||||
| DataType::UInt64 => {
|
||||
let res: i64 = series.sum().unwrap_or(0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
DataType::Float32 | DataType::Float64 => {
|
||||
let res: f64 = series.sum().unwrap_or(0.0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Not valid type",
|
||||
format!(
|
||||
"this operation can not be performed with series of type {}",
|
||||
series.dtype()
|
||||
),
|
||||
&operation_tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
let value = Value {
|
||||
value: untagged,
|
||||
tag: operation_tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(operation_tag.clone());
|
||||
data.insert_value("sum", value);
|
||||
|
||||
Ok(data.into_value())
|
||||
}
|
||||
Operation::Max => {
|
||||
let untagged = match series.dtype() {
|
||||
DataType::Int8
|
||||
| DataType::Int16
|
||||
| DataType::Int32
|
||||
| DataType::Int64
|
||||
| DataType::UInt8
|
||||
| DataType::UInt16
|
||||
| DataType::UInt32
|
||||
| DataType::UInt64 => {
|
||||
let res: i64 = series.max().unwrap_or(0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
DataType::Float32 | DataType::Float64 => {
|
||||
let res: f64 = series.max().unwrap_or(0.0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Not valid type",
|
||||
format!(
|
||||
"this operation can not be performed with series of type {}",
|
||||
series.dtype()
|
||||
),
|
||||
&operation_tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
let value = Value {
|
||||
value: untagged,
|
||||
tag: operation_tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(operation_tag.clone());
|
||||
data.insert_value("max", value);
|
||||
|
||||
Ok(data.into_value())
|
||||
}
|
||||
Operation::Min => {
|
||||
let untagged = match series.dtype() {
|
||||
DataType::Int8
|
||||
| DataType::Int16
|
||||
| DataType::Int32
|
||||
| DataType::Int64
|
||||
| DataType::UInt8
|
||||
| DataType::UInt16
|
||||
| DataType::UInt32
|
||||
| DataType::UInt64 => {
|
||||
let res: i64 = series.min().unwrap_or(0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
DataType::Float32 | DataType::Float64 => {
|
||||
let res: f64 = series.min().unwrap_or(0.0);
|
||||
Ok(UntaggedValue::Primitive(res.into()))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Not valid type",
|
||||
format!(
|
||||
"this operation can not be performed with series of type {}",
|
||||
series.dtype()
|
||||
),
|
||||
&operation_tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
let value = Value {
|
||||
value: untagged,
|
||||
tag: operation_tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(operation_tag.clone());
|
||||
data.insert_value("min", value);
|
||||
|
||||
Ok(data.into_value())
|
||||
}
|
||||
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Not valid operation",
|
||||
"operation not valid for series",
|
||||
&operation_tag.span,
|
||||
"Perhaps you want: mean, median, sum, max, min",
|
||||
&operation_tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
55
crates/nu-command/src/commands/dataframe/column.rs
Normal file
55
crates/nu-command/src/commands/dataframe/column.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, NuSeries},
|
||||
Signature, SyntaxShape,
|
||||
};
|
||||
|
||||
use nu_source::Tagged;
|
||||
|
||||
use super::utils::parse_polars_error;
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe column"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns the selected column as Series"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe column").required("column", SyntaxShape::String, "column name")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Returns the selected column as series",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe column a",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let column: Tagged<String> = args.req(0)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.column(column.item.as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &column.tag.span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuSeries::series_to_value(
|
||||
res.clone(),
|
||||
tag,
|
||||
)))
|
||||
}
|
26
crates/nu-command/src/commands/dataframe/command.rs
Normal file
26
crates/nu-command/src/commands/dataframe/command.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Commands to work with polars dataframes"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Command, args.scope())).into_value(Tag::unknown()),
|
||||
))
|
||||
}
|
||||
}
|
69
crates/nu-command/src/commands/dataframe/drop.rs
Normal file
69
crates/nu-command/src/commands/dataframe/drop.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe drop"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new dataframe by dropping the selected columns"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe drop").required(
|
||||
"columns",
|
||||
SyntaxShape::Table,
|
||||
"column names to be dropped",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "drop column a",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe drop [a]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let columns: Vec<Value> = args.req(0)?;
|
||||
let (col_string, col_span) = convert_columns(&columns, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let new_df = match col_string.get(0) {
|
||||
Some(col) => df
|
||||
.as_ref()
|
||||
.drop(col)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None)),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Empty names list",
|
||||
"No column names where found",
|
||||
&col_span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
// If there are more columns in the drop selection list, these
|
||||
// are added from the resulting dataframe
|
||||
let res = col_string.iter().skip(1).try_fold(new_df, |new_df, col| {
|
||||
new_df
|
||||
.drop(col)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))
|
||||
})?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
65
crates/nu-command/src/commands/dataframe/drop_duplicates.rs
Normal file
65
crates/nu-command/src/commands/dataframe/drop_duplicates.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe drop-duplicates"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Drops duplicate values in dataframe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe drop-duplicates")
|
||||
.optional(
|
||||
"subset",
|
||||
SyntaxShape::Table,
|
||||
"subset of columns to drop duplicates",
|
||||
)
|
||||
.switch("maintain", "maintain order", Some('m'))
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "drop duplicates",
|
||||
example: "[[a b]; [1 2] [3 4] [1 2]] | dataframe to-df | dataframe drop-duplicates",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
// Extracting the selection columns of the columns to perform the aggregation
|
||||
let columns: Option<Vec<Value>> = args.opt(0)?;
|
||||
let (subset, col_span) = match columns {
|
||||
Some(cols) => {
|
||||
let (agg_string, col_span) = convert_columns(&cols, &tag)?;
|
||||
(Some(agg_string), col_span)
|
||||
}
|
||||
None => (None, Span::unknown()),
|
||||
};
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.drop_duplicates(args.has_flag("maintain"), subset_slice)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
93
crates/nu-command/src/commands/dataframe/drop_nulls.rs
Normal file
93
crates/nu-command/src/commands/dataframe/drop_nulls.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, NuSeries, PolarsData},
|
||||
Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe drop-nulls"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Drops null values in dataframe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe drop-nulls").optional(
|
||||
"subset",
|
||||
SyntaxShape::Table,
|
||||
"subset of columns to drop duplicates",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "drop null values in dataframe",
|
||||
example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | dataframe to-df);
|
||||
let res = ($df.b / $df.b);
|
||||
let df = ($df | dataframe with-column $res as res);
|
||||
$df | dataframe drop-nulls
|
||||
"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "drop null values in dataframe",
|
||||
example: r#"let s = ([1 2 0 0 3 4] | dataframe to-series);
|
||||
($s / $s) | dataframe drop-nulls"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let value = args.input.next().ok_or_else(|| {
|
||||
ShellError::labeled_error("Empty stream", "No value found in stream", &tag.span)
|
||||
})?;
|
||||
|
||||
match value.value {
|
||||
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
|
||||
// Extracting the selection columns of the columns to perform the aggregation
|
||||
let columns: Option<Vec<Value>> = args.opt(0)?;
|
||||
let (subset, col_span) = match columns {
|
||||
Some(cols) => {
|
||||
let (agg_string, col_span) = convert_columns(&cols, &tag)?;
|
||||
(Some(agg_string), col_span)
|
||||
}
|
||||
None => (None, Span::unknown()),
|
||||
};
|
||||
|
||||
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.drop_nulls(subset_slice)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
|
||||
let res = series.as_ref().drop_nulls();
|
||||
Ok(OutputStream::one(NuSeries::series_to_value(res, tag)))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Incorrect type",
|
||||
"drop nulls cannot be done with this value",
|
||||
&value.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
60
crates/nu-command/src/commands/dataframe/dtypes.rs
Normal file
60
crates/nu-command/src/commands/dataframe/dtypes.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, TaggedDictBuilder};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe dtypes"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show dataframe data types"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe dtypes")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "drop column a",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe dtypes",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
let col_names = df
|
||||
.as_ref()
|
||||
.get_column_names()
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let values = df
|
||||
.as_ref()
|
||||
.dtypes()
|
||||
.into_iter()
|
||||
.zip(col_names.into_iter())
|
||||
.map(move |(dtype, name)| {
|
||||
let mut data = TaggedDictBuilder::new(tag.clone());
|
||||
data.insert_value("column", name.as_ref());
|
||||
data.insert_value("dtype", format!("{}", dtype));
|
||||
|
||||
data.into_value()
|
||||
});
|
||||
|
||||
Ok(OutputStream::from_stream(values))
|
||||
}
|
82
crates/nu-command/src/commands/dataframe/dummies.rs
Normal file
82
crates/nu-command/src/commands/dataframe/dummies.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, PolarsData},
|
||||
Signature, UntaggedValue,
|
||||
};
|
||||
|
||||
use super::utils::parse_polars_error;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe to-dummies"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new dataframe with dummy variables"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe to-dummies")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create new dataframe with dummy variables from a dataframe",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe to-dummies",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create new dataframe with dummy variables from a series",
|
||||
example: "[1 2 2 3 3] | dataframe to-series | dataframe to-dummies",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let value = args.input.next().ok_or_else(|| {
|
||||
ShellError::labeled_error("Empty stream", "No value found in stream", &tag.span)
|
||||
})?;
|
||||
|
||||
match value.value {
|
||||
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) => {
|
||||
let res = df.as_ref().to_dummies().map_err(|e| {
|
||||
parse_polars_error(
|
||||
&e,
|
||||
&tag.span,
|
||||
Some("The only allowed column types for dummies are String or Int"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
UntaggedValue::DataFrame(PolarsData::Series(series)) => {
|
||||
let res = series.as_ref().to_dummies().map_err(|e| {
|
||||
parse_polars_error(
|
||||
&e,
|
||||
&tag.span,
|
||||
Some("The only allowed column types for dummies are String or Int"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Incorrect type",
|
||||
"dummies cannot be done with this value",
|
||||
&value.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
79
crates/nu-command/src/commands/dataframe/filter.rs
Normal file
79
crates/nu-command/src/commands/dataframe/filter.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, PolarsData},
|
||||
Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use super::utils::parse_polars_error;
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe filter"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Filters dataframe using a mask as reference"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe filter")
|
||||
.required("with", SyntaxShape::String, "the word 'with'")
|
||||
.required("mask", SyntaxShape::Any, "boolean mask used to filter data")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter dataframe using a bool mask",
|
||||
example: r#"let mask = ([$true $false] | dataframe to-series);
|
||||
[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe filter with $mask"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Filter dataframe by creating a mask from operation",
|
||||
example: r#"let mask = (([5 6] | dataframe to-series) > 5);
|
||||
[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe filter with $mask"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let value: Value = args.req(1)?;
|
||||
|
||||
let series_span = value.tag.span;
|
||||
let series = match value.value {
|
||||
UntaggedValue::DataFrame(PolarsData::Series(series)) => Ok(series),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Incorrect type",
|
||||
"can only add a series to a dataframe",
|
||||
value.tag.span,
|
||||
)),
|
||||
}?;
|
||||
|
||||
let casted = series.as_ref().bool().map_err(|e| {
|
||||
parse_polars_error(
|
||||
&e,
|
||||
&&series_span,
|
||||
Some("Perhaps you want to use a series with booleans as mask"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.filter(&casted)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
53
crates/nu-command/src/commands/dataframe/get.rs
Normal file
53
crates/nu-command/src/commands/dataframe/get.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe get"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates dataframe with the selected columns"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe get").required(
|
||||
"columns",
|
||||
SyntaxShape::Table,
|
||||
"column names to sort dataframe",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates dataframe with selected columns",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe get [a]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let columns: Vec<Value> = args.req(0)?;
|
||||
|
||||
let (col_string, col_span) = convert_columns(&columns, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.select(&col_string)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
72
crates/nu-command/src/commands/dataframe/groupby.rs
Normal file
72
crates/nu-command/src/commands/dataframe/groupby.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, NuGroupBy, PolarsData},
|
||||
Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use super::utils::convert_columns;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe group-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a groupby object that can be used for other aggregations"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe group-by").required(
|
||||
"by columns",
|
||||
SyntaxShape::Table,
|
||||
"groupby columns",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Grouping by column a",
|
||||
example: "[[a b]; [one 1] [one 2]] | dataframe to-df | dataframe group-by [a]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
// Extracting the names of the columns to perform the groupby
|
||||
let by_columns: Vec<Value> = args.req(0)?;
|
||||
let (columns_string, col_span) = convert_columns(&by_columns, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
// This is the expensive part of the groupby; to create the
|
||||
// groups that will be used for grouping the data in the
|
||||
// dataframe. Once it has been done these values can be stored
|
||||
// in a NuGroupBy
|
||||
let groupby = df
|
||||
.as_ref()
|
||||
.groupby(&columns_string)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
let groups = groupby.get_groups().to_vec();
|
||||
let groupby = Value {
|
||||
tag,
|
||||
value: UntaggedValue::DataFrame(PolarsData::GroupBy(NuGroupBy::new(
|
||||
NuDataFrame::new(df.as_ref().clone()),
|
||||
columns_string,
|
||||
groups,
|
||||
))),
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(groupby))
|
||||
}
|
53
crates/nu-command/src/commands/dataframe/head.rs
Normal file
53
crates/nu-command/src/commands/dataframe/head.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape};
|
||||
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe head"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates new dataframe with head rows"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe select").optional(
|
||||
"rows",
|
||||
SyntaxShape::Number,
|
||||
"Number of rows for head",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create new dataframe with head rows",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe head",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let rows: Option<Tagged<usize>> = args.opt(0)?;
|
||||
|
||||
let rows = match rows {
|
||||
Some(val) => val.item,
|
||||
None => 5,
|
||||
};
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
let res = df.as_ref().head(Some(rows));
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
170
crates/nu-command/src/commands/dataframe/join.rs
Normal file
170
crates/nu-command/src/commands/dataframe/join.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, PolarsData},
|
||||
Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
|
||||
use polars::prelude::JoinType;
|
||||
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe join"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Joins a dataframe using columns as reference"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe join")
|
||||
.required("dataframe", SyntaxShape::Any, "right dataframe to join")
|
||||
.required(
|
||||
"l_columns",
|
||||
SyntaxShape::Table,
|
||||
"left column names to perform join",
|
||||
)
|
||||
.required(
|
||||
"r_columns",
|
||||
SyntaxShape::Table,
|
||||
"right column names to perform join",
|
||||
)
|
||||
.named(
|
||||
"type",
|
||||
SyntaxShape::String,
|
||||
"type of join. Inner by default",
|
||||
Some('t'),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "inner join dataframe",
|
||||
example: "echo [[a b]; [1 2] [3 4]] | dataframe to-df | dataframe join $right [a] [a]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "right join dataframe",
|
||||
example:
|
||||
"[[a b]; [1 2] [3 4] [5 6]] | dataframe to-df | dataframe join $right [b] [b] -t right",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let r_df: Value = args.req(0)?;
|
||||
let l_col: Vec<Value> = args.req(1)?;
|
||||
let r_col: Vec<Value> = args.req(2)?;
|
||||
let join_type_op: Option<Tagged<String>> = args.get_flag("type")?;
|
||||
|
||||
let join_type = match join_type_op {
|
||||
None => JoinType::Inner,
|
||||
Some(val) => match val.item.as_ref() {
|
||||
"inner" => JoinType::Inner,
|
||||
"outer" => JoinType::Outer,
|
||||
"left" => JoinType::Left,
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Incorrect join type",
|
||||
"Invalid join type",
|
||||
&val.tag,
|
||||
"Perhaps you mean: inner, outer or left",
|
||||
&val.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let (l_col_string, l_col_span) = convert_columns(&l_col, &tag)?;
|
||||
let (r_col_string, r_col_span) = convert_columns(&r_col, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = match r_df.value {
|
||||
UntaggedValue::DataFrame(PolarsData::EagerDataFrame(r_df)) => {
|
||||
// Checking the column types before performing the join
|
||||
check_column_datatypes(
|
||||
df.as_ref(),
|
||||
&l_col_string,
|
||||
&l_col_span,
|
||||
&r_col_string,
|
||||
&r_col_span,
|
||||
)?;
|
||||
|
||||
df.as_ref()
|
||||
.join(r_df.as_ref(), &l_col_string, &r_col_string, join_type)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &l_col_span, None))
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Not a dataframe",
|
||||
"not a dataframe type value",
|
||||
&r_df.tag,
|
||||
)),
|
||||
}?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
|
||||
fn check_column_datatypes<T: AsRef<str>>(
|
||||
df: &polars::prelude::DataFrame,
|
||||
l_cols: &[T],
|
||||
l_col_span: &Span,
|
||||
r_cols: &[T],
|
||||
r_col_span: &Span,
|
||||
) -> Result<(), ShellError> {
|
||||
if l_cols.len() != r_cols.len() {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Mismatched number of column names",
|
||||
format!(
|
||||
"found {} left names vs {} right names",
|
||||
l_cols.len(),
|
||||
r_cols.len()
|
||||
),
|
||||
l_col_span,
|
||||
"perhaps you need to change the number of columns to join",
|
||||
r_col_span,
|
||||
));
|
||||
}
|
||||
|
||||
for (l, r) in l_cols.iter().zip(r_cols.iter()) {
|
||||
let l_series = df
|
||||
.column(l.as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &l_col_span, None))?;
|
||||
|
||||
let r_series = df
|
||||
.column(r.as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &r_col_span, None))?;
|
||||
|
||||
if l_series.dtype() != r_series.dtype() {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Mismatched datatypes",
|
||||
format!(
|
||||
"left column type '{}' doesn't match '{}' right column match",
|
||||
l_series.dtype(),
|
||||
r_series.dtype()
|
||||
),
|
||||
l_col_span,
|
||||
"perhaps you need to select other column to match",
|
||||
r_col_span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
64
crates/nu-command/src/commands/dataframe/list.rs
Normal file
64
crates/nu-command/src/commands/dataframe/list.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::PolarsData, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe list"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Lists stored dataframes"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe list")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let values = args
|
||||
.context
|
||||
.scope
|
||||
.get_vars()
|
||||
.into_iter()
|
||||
.filter_map(|(name, value)| {
|
||||
if let UntaggedValue::DataFrame(PolarsData::EagerDataFrame(df)) = &value.value {
|
||||
let mut data = TaggedDictBuilder::new(value.tag.clone());
|
||||
|
||||
let rows = df.as_ref().height();
|
||||
let cols = df.as_ref().width();
|
||||
|
||||
data.insert_value("name", name.as_ref());
|
||||
data.insert_value("rows", format!("{}", rows));
|
||||
data.insert_value("columns", format!("{}", cols));
|
||||
|
||||
match value.tag.anchor {
|
||||
Some(AnchorLocation::File(name)) => data.insert_value("location", name),
|
||||
Some(AnchorLocation::Url(name)) => data.insert_value("location", name),
|
||||
Some(AnchorLocation::Source(text)) => {
|
||||
let loc_name = text.slice(0..text.end);
|
||||
data.insert_value("location", loc_name.text)
|
||||
}
|
||||
None => data.insert_value("location", "stream"),
|
||||
}
|
||||
|
||||
Some(data.into_value())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Ok(OutputStream::from_stream(values))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Lists loaded dataframes in current scope",
|
||||
example: "dataframe list",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
214
crates/nu-command/src/commands/dataframe/load.rs
Normal file
214
crates/nu-command/src/commands/dataframe/load.rs
Normal file
@ -0,0 +1,214 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::NuDataFrame, Primitive, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
use nu_source::Tagged;
|
||||
use polars::prelude::{CsvReader, JsonReader, ParquetReader, SerReader};
|
||||
use std::fs::File;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe load"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Loads dataframe form csv file"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe load")
|
||||
.required(
|
||||
"file",
|
||||
SyntaxShape::FilePath,
|
||||
"file path to load values from",
|
||||
)
|
||||
.named(
|
||||
"delimiter",
|
||||
SyntaxShape::String,
|
||||
"file delimiter character. CSV file",
|
||||
Some('d'),
|
||||
)
|
||||
.switch(
|
||||
"no_header",
|
||||
"Indicates if file doesn't have header. CSV file",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"infer_schema",
|
||||
SyntaxShape::Number,
|
||||
"Set number of row to infer the schema of the file. CSV file",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"skip_rows",
|
||||
SyntaxShape::Number,
|
||||
"Number of rows to skip from file. CSV file",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"columns",
|
||||
SyntaxShape::Table,
|
||||
"Columns to be selected from csv file. CSV file",
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Takes a file name and creates a dataframe",
|
||||
example: "dataframe load test.csv",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let file: Tagged<PathBuf> = args.req(0)?;
|
||||
|
||||
let df = match file.item().extension() {
|
||||
Some(e) => match e.to_str() {
|
||||
Some("csv") => from_csv(args),
|
||||
Some("parquet") => from_parquet(args),
|
||||
Some("json") => from_json(args),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Error with file",
|
||||
"Not a csv, parquet or json file",
|
||||
&file.tag,
|
||||
)),
|
||||
},
|
||||
None => Err(ShellError::labeled_error(
|
||||
"Error with file",
|
||||
"File without extension",
|
||||
&file.tag,
|
||||
)),
|
||||
}?;
|
||||
|
||||
let file_name = match file.item.into_os_string().into_string() {
|
||||
Ok(name) => name,
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"File Name Error",
|
||||
format!("{:?}", e),
|
||||
&file.tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let df_tag = Tag {
|
||||
anchor: Some(AnchorLocation::File(file_name)),
|
||||
span: tag.span,
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(
|
||||
df, df_tag,
|
||||
)))
|
||||
}
|
||||
|
||||
fn from_parquet(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
|
||||
let file: Tagged<PathBuf> = args.req(0)?;
|
||||
|
||||
let r = File::open(&file.item)
|
||||
.map_err(|e| ShellError::labeled_error("Error with file", format!("{:?}", e), &file.tag))?;
|
||||
|
||||
let reader = ParquetReader::new(r);
|
||||
|
||||
reader
|
||||
.finish()
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))
|
||||
}
|
||||
|
||||
fn from_json(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
|
||||
let file: Tagged<PathBuf> = args.req(0)?;
|
||||
|
||||
let r = File::open(&file.item)
|
||||
.map_err(|e| ShellError::labeled_error("Error with file", format!("{:?}", e), &file.tag))?;
|
||||
|
||||
let reader = JsonReader::new(r);
|
||||
|
||||
reader
|
||||
.finish()
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))
|
||||
}
|
||||
|
||||
fn from_csv(args: CommandArgs) -> Result<polars::prelude::DataFrame, ShellError> {
|
||||
let file: Tagged<PathBuf> = args.req(0)?;
|
||||
let delimiter: Option<Tagged<String>> = args.get_flag("delimiter")?;
|
||||
let no_header: bool = args.has_flag("no_header");
|
||||
let infer_schema: Option<Tagged<usize>> = args.get_flag("infer_schema")?;
|
||||
let skip_rows: Option<Tagged<usize>> = args.get_flag("skip_rows")?;
|
||||
let columns: Option<Vec<Value>> = args.get_flag("columns")?;
|
||||
|
||||
let csv_reader = CsvReader::from_path(&file.item)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &file.tag.span, None))?;
|
||||
|
||||
let csv_reader = match delimiter {
|
||||
None => csv_reader,
|
||||
Some(d) => {
|
||||
if d.item.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Incorrect delimiter",
|
||||
"Delimiter has to be one char",
|
||||
&d.tag,
|
||||
));
|
||||
} else {
|
||||
let delimiter = match d.item.chars().next() {
|
||||
Some(d) => d as u8,
|
||||
None => unreachable!(),
|
||||
};
|
||||
csv_reader.with_delimiter(delimiter)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let csv_reader = if no_header {
|
||||
csv_reader.has_header(false)
|
||||
} else {
|
||||
csv_reader.has_header(true)
|
||||
};
|
||||
|
||||
let csv_reader = match infer_schema {
|
||||
None => csv_reader.infer_schema(None),
|
||||
Some(r) => csv_reader.infer_schema(Some(r.item)),
|
||||
};
|
||||
|
||||
let csv_reader = match skip_rows {
|
||||
None => csv_reader,
|
||||
Some(r) => csv_reader.with_skip_rows(r.item),
|
||||
};
|
||||
|
||||
let csv_reader = match columns {
|
||||
None => csv_reader,
|
||||
Some(c) => {
|
||||
let columns = c
|
||||
.into_iter()
|
||||
.map(|value| match value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => Ok(s),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Incorrect type for column",
|
||||
"Only string as columns",
|
||||
&value.tag,
|
||||
)),
|
||||
})
|
||||
.collect::<Result<Vec<String>, ShellError>>();
|
||||
|
||||
csv_reader.with_columns(Some(columns?))
|
||||
}
|
||||
};
|
||||
|
||||
match csv_reader.finish() {
|
||||
Ok(csv_reader) => Ok(csv_reader),
|
||||
Err(e) => Err(parse_polars_error::<&str>(&e, &file.tag.span, None)),
|
||||
}
|
||||
}
|
105
crates/nu-command/src/commands/dataframe/melt.rs
Normal file
105
crates/nu-command/src/commands/dataframe/melt.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
|
||||
|
||||
use super::utils::convert_columns;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe melt"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Unpivot a DataFrame from wide to long format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe melt")
|
||||
.required("id_columns", SyntaxShape::Table, "Id columns for melting")
|
||||
.required(
|
||||
"value_columns",
|
||||
SyntaxShape::Table,
|
||||
"columns used as value columns",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "melt dataframe",
|
||||
example: "[[a b]; [a 2] [b 4] [a 6]] | dataframe to-df | dataframe melt [a] [b]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let id_col: Vec<Value> = args.req(0)?;
|
||||
let val_col: Vec<Value> = args.req(1)?;
|
||||
|
||||
let (id_col_string, id_col_span) = convert_columns(&id_col, &tag)?;
|
||||
let (val_col_string, val_col_span) = convert_columns(&val_col, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
check_column_datatypes(df.as_ref(), &id_col_string, &id_col_span)?;
|
||||
check_column_datatypes(df.as_ref(), &val_col_string, &val_col_span)?;
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.melt(&id_col_string, &val_col_string)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
|
||||
fn check_column_datatypes<T: AsRef<str>>(
|
||||
df: &polars::prelude::DataFrame,
|
||||
cols: &[T],
|
||||
col_span: &Span,
|
||||
) -> Result<(), ShellError> {
|
||||
if cols.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Merge error",
|
||||
"empty column list",
|
||||
col_span,
|
||||
));
|
||||
}
|
||||
|
||||
// Checking if they are same type
|
||||
if cols.len() > 1 {
|
||||
for w in cols.windows(2) {
|
||||
let l_series = df
|
||||
.column(w[0].as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
let r_series = df
|
||||
.column(w[1].as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
if l_series.dtype() != r_series.dtype() {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Merge error",
|
||||
"found different column types in list",
|
||||
col_span,
|
||||
format!(
|
||||
"datatypes {} and {} are incompatible",
|
||||
l_series.dtype(),
|
||||
r_series.dtype()
|
||||
),
|
||||
col_span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
81
crates/nu-command/src/commands/dataframe/mod.rs
Normal file
81
crates/nu-command/src/commands/dataframe/mod.rs
Normal file
@ -0,0 +1,81 @@
|
||||
pub mod aggregate;
|
||||
pub mod column;
|
||||
pub mod command;
|
||||
pub mod drop;
|
||||
pub mod drop_duplicates;
|
||||
pub mod drop_nulls;
|
||||
pub mod dtypes;
|
||||
pub mod dummies;
|
||||
pub mod filter;
|
||||
pub mod get;
|
||||
pub mod groupby;
|
||||
pub mod head;
|
||||
pub mod join;
|
||||
pub mod list;
|
||||
pub mod load;
|
||||
pub mod melt;
|
||||
pub mod pivot;
|
||||
pub mod sample;
|
||||
pub mod select;
|
||||
pub mod show;
|
||||
pub mod slice;
|
||||
pub mod sort;
|
||||
pub mod tail;
|
||||
pub mod to_csv;
|
||||
pub mod to_df;
|
||||
pub mod to_parquet;
|
||||
pub mod to_series;
|
||||
pub(crate) mod utils;
|
||||
pub mod where_;
|
||||
pub mod with_column;
|
||||
|
||||
pub use aggregate::DataFrame as DataFrameAggregate;
|
||||
pub use column::DataFrame as DataFrameColumn;
|
||||
pub use command::Command as DataFrame;
|
||||
pub use drop::DataFrame as DataFrameDrop;
|
||||
pub use drop_duplicates::DataFrame as DataFrameDropDuplicates;
|
||||
pub use drop_nulls::DataFrame as DataFrameDropNulls;
|
||||
pub use dtypes::DataFrame as DataFrameDTypes;
|
||||
pub use dummies::DataFrame as DataFrameDummies;
|
||||
pub use filter::DataFrame as DataFrameFilter;
|
||||
pub use get::DataFrame as DataFrameGet;
|
||||
pub use groupby::DataFrame as DataFrameGroupBy;
|
||||
pub use head::DataFrame as DataFrameHead;
|
||||
pub use join::DataFrame as DataFrameJoin;
|
||||
pub use list::DataFrame as DataFrameList;
|
||||
pub use load::DataFrame as DataFrameLoad;
|
||||
pub use melt::DataFrame as DataFrameMelt;
|
||||
pub use pivot::DataFrame as DataFramePivot;
|
||||
pub use sample::DataFrame as DataFrameSample;
|
||||
pub use select::DataFrame as DataFrameSelect;
|
||||
pub use show::DataFrame as DataFrameShow;
|
||||
pub use slice::DataFrame as DataFrameSlice;
|
||||
pub use sort::DataFrame as DataFrameSort;
|
||||
pub use tail::DataFrame as DataFrameTail;
|
||||
pub use to_csv::DataFrame as DataFrameToCsv;
|
||||
pub use to_df::DataFrame as DataFrameToDF;
|
||||
pub use to_parquet::DataFrame as DataFrameToParquet;
|
||||
pub use to_series::DataFrame as DataFrameToSeries;
|
||||
pub use where_::DataFrame as DataFrameWhere;
|
||||
pub use with_column::DataFrame as DataFrameWithColumn;
|
||||
|
||||
pub mod series;
|
||||
pub use series::DataFrameAllFalse;
|
||||
pub use series::DataFrameAllTrue;
|
||||
pub use series::DataFrameArgMax;
|
||||
pub use series::DataFrameArgMin;
|
||||
pub use series::DataFrameArgSort;
|
||||
pub use series::DataFrameArgTrue;
|
||||
pub use series::DataFrameArgUnique;
|
||||
pub use series::DataFrameIsDuplicated;
|
||||
pub use series::DataFrameIsIn;
|
||||
pub use series::DataFrameIsNotNull;
|
||||
pub use series::DataFrameIsNull;
|
||||
pub use series::DataFrameIsUnique;
|
||||
pub use series::DataFrameNNull;
|
||||
pub use series::DataFrameNUnique;
|
||||
pub use series::DataFrameSeriesRename;
|
||||
pub use series::DataFrameSet;
|
||||
pub use series::DataFrameShift;
|
||||
pub use series::DataFrameUnique;
|
||||
pub use series::DataFrameValueCounts;
|
169
crates/nu-command/src/commands/dataframe/pivot.rs
Normal file
169
crates/nu-command/src/commands/dataframe/pivot.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::{NuDataFrame, NuGroupBy},
|
||||
Signature, SyntaxShape,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
use polars::prelude::DataType;
|
||||
|
||||
enum Operation {
|
||||
First,
|
||||
Sum,
|
||||
Min,
|
||||
Max,
|
||||
Mean,
|
||||
Median,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn from_tagged(name: &Tagged<String>) -> Result<Operation, ShellError> {
|
||||
match name.item.as_ref() {
|
||||
"first" => Ok(Operation::First),
|
||||
"sum" => Ok(Operation::Sum),
|
||||
"min" => Ok(Operation::Min),
|
||||
"max" => Ok(Operation::Max),
|
||||
"mean" => Ok(Operation::Mean),
|
||||
"median" => Ok(Operation::Median),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Operation not fount",
|
||||
"Operation does not exist for pivot",
|
||||
&name.tag,
|
||||
"Perhaps you want: first, sum, min, max, mean, median",
|
||||
&name.tag,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe pivot"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Performs a pivot operation on a groupby object"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe pivot")
|
||||
.required(
|
||||
"pivot column",
|
||||
SyntaxShape::String,
|
||||
"pivot column to perform pivot",
|
||||
)
|
||||
.required(
|
||||
"value column",
|
||||
SyntaxShape::String,
|
||||
"value column to perform pivot",
|
||||
)
|
||||
.required("operation", SyntaxShape::String, "aggregate operation")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Pivot a dataframe on b and aggregation on col c",
|
||||
example:
|
||||
"[[a b c]; [one x 1] [two y 2]] | dataframe to-df | dataframe group-by [a] | dataframe pivot b c sum",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
// Extracting the pivot col from arguments
|
||||
let pivot_col: Tagged<String> = args.req(0)?;
|
||||
|
||||
// Extracting the value col from arguments
|
||||
let value_col: Tagged<String> = args.req(1)?;
|
||||
|
||||
let operation: Tagged<String> = args.req(2)?;
|
||||
let op = Operation::from_tagged(&operation)?;
|
||||
|
||||
// The operation is only done in one groupby. Only one input is
|
||||
// expected from the InputStream
|
||||
let nu_groupby = NuGroupBy::try_from_stream(&mut args.input, &tag.span)?;
|
||||
let df_ref = nu_groupby.as_ref();
|
||||
|
||||
check_pivot_column(df_ref, &pivot_col)?;
|
||||
check_value_column(df_ref, &value_col)?;
|
||||
|
||||
let mut groupby = nu_groupby.to_groupby()?;
|
||||
|
||||
let pivot = groupby.pivot(pivot_col.item.as_ref(), value_col.item.as_ref());
|
||||
|
||||
let res = match op {
|
||||
Operation::Mean => pivot.mean(),
|
||||
Operation::Sum => pivot.sum(),
|
||||
Operation::Min => pivot.min(),
|
||||
Operation::Max => pivot.max(),
|
||||
Operation::First => pivot.first(),
|
||||
Operation::Median => pivot.median(),
|
||||
}
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &tag.span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
||||
|
||||
fn check_pivot_column(
|
||||
df: &polars::prelude::DataFrame,
|
||||
col: &Tagged<String>,
|
||||
) -> Result<(), ShellError> {
|
||||
let series = df
|
||||
.column(col.item.as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col.tag.span, None))?;
|
||||
|
||||
match series.dtype() {
|
||||
DataType::UInt8
|
||||
| DataType::UInt16
|
||||
| DataType::UInt32
|
||||
| DataType::UInt64
|
||||
| DataType::Int8
|
||||
| DataType::Int16
|
||||
| DataType::Int32
|
||||
| DataType::Int64
|
||||
| DataType::Utf8 => Ok(()),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Pivot error",
|
||||
format!("Unsupported datatype {}", series.dtype()),
|
||||
col.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_value_column(
|
||||
df: &polars::prelude::DataFrame,
|
||||
col: &Tagged<String>,
|
||||
) -> Result<(), ShellError> {
|
||||
let series = df
|
||||
.column(col.item.as_ref())
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col.tag.span, None))?;
|
||||
|
||||
match series.dtype() {
|
||||
DataType::UInt8
|
||||
| DataType::UInt16
|
||||
| DataType::UInt32
|
||||
| DataType::UInt64
|
||||
| DataType::Int8
|
||||
| DataType::Int16
|
||||
| DataType::Int32
|
||||
| DataType::Int64
|
||||
| DataType::Float32
|
||||
| DataType::Float64 => Ok(()),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Pivot error",
|
||||
format!("Unsupported datatype {}", series.dtype()),
|
||||
col.tag.span,
|
||||
)),
|
||||
}
|
||||
}
|
90
crates/nu-command/src/commands/dataframe/sample.rs
Normal file
90
crates/nu-command/src/commands/dataframe/sample.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape};
|
||||
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe sample"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create sample dataframe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe load")
|
||||
.named(
|
||||
"n_rows",
|
||||
SyntaxShape::Number,
|
||||
"number of rows to be taken from dataframe",
|
||||
Some('n'),
|
||||
)
|
||||
.named(
|
||||
"fraction",
|
||||
SyntaxShape::Number,
|
||||
"fraction of dataframe to be taken",
|
||||
Some('f'),
|
||||
)
|
||||
.switch("replace", "sample with replace", Some('e'))
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sample rows from dataframe",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe sample -r 1",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Shows sample row using fraction and replace",
|
||||
example:
|
||||
"[[a b]; [1 2] [3 4] [5 6]] | dataframe to-df | dataframe sample -f 0.5 -e",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let rows: Option<Tagged<usize>> = args.get_flag("n_rows")?;
|
||||
let fraction: Option<Tagged<f64>> = args.get_flag("fraction")?;
|
||||
let replace: bool = args.has_flag("replace");
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = match (rows, fraction) {
|
||||
(Some(rows), None) => df
|
||||
.as_ref()
|
||||
.sample_n(rows.item, replace)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &rows.tag.span, None)),
|
||||
(None, Some(frac)) => df
|
||||
.as_ref()
|
||||
.sample_frac(frac.item, replace)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &frac.tag.span, None)),
|
||||
(Some(_), Some(_)) => Err(ShellError::labeled_error(
|
||||
"Incompatible flags",
|
||||
"Only one selection criterion allowed",
|
||||
&tag,
|
||||
)),
|
||||
(None, None) => Err(ShellError::labeled_error_with_secondary(
|
||||
"No selection",
|
||||
"No selection criterion was found",
|
||||
&tag,
|
||||
"Perhaps you want to use the flag -n or -f",
|
||||
&tag,
|
||||
)),
|
||||
}?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
55
crates/nu-command/src/commands/dataframe/select.rs
Normal file
55
crates/nu-command/src/commands/dataframe/select.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuDataFrame, Signature, SyntaxShape, Value};
|
||||
|
||||
use super::utils::{convert_columns, parse_polars_error};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe select"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a new dataframe with the selected columns"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe select").required(
|
||||
"columns",
|
||||
SyntaxShape::Table,
|
||||
"selected column names",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create new dataframe with column a",
|
||||
example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe select [a]",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let columns: Vec<Value> = args.req(0)?;
|
||||
|
||||
let (col_string, col_span) = convert_columns(&columns, &tag)?;
|
||||
|
||||
let df = NuDataFrame::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = df
|
||||
.as_ref()
|
||||
.select(&col_string)
|
||||
.map_err(|e| parse_polars_error::<&str>(&e, &col_span, None))?;
|
||||
|
||||
Ok(OutputStream::one(NuDataFrame::dataframe_to_value(res, tag)))
|
||||
}
|
67
crates/nu-command/src/commands/dataframe/series/all_false.rs
Normal file
67
crates/nu-command/src/commands/dataframe/series/all_false.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuSeries, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe all-false"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns true if all values are false"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe all-false")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Returns true if all values are false",
|
||||
example: "[$false $false $false] | dataframe to-series | dataframe all-false",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Checks the result from a comparison",
|
||||
example: r#"let s = ([5 6 2 8] | dataframe to-series);
|
||||
let res = ($s > 9);
|
||||
$res | dataframe all-false"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let bool = series.as_ref().bool().map_err(|e| {
|
||||
parse_polars_error::<&str>(
|
||||
&e,
|
||||
&tag.span,
|
||||
Some("all-false only works with series of type bool"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let res = bool.all_false();
|
||||
|
||||
let value = Value {
|
||||
value: UntaggedValue::Primitive(res.into()),
|
||||
tag: tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(tag);
|
||||
data.insert_value("all_false", value);
|
||||
|
||||
Ok(OutputStream::one(data.into_value()))
|
||||
}
|
67
crates/nu-command/src/commands/dataframe/series/all_true.rs
Normal file
67
crates/nu-command/src/commands/dataframe/series/all_true.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::{commands::dataframe::utils::parse_polars_error, prelude::*};
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuSeries, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe all-true"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns true if all values are true"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe all-true")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Returns true if all values are true",
|
||||
example: "[$true $true $true] | dataframe to-series | dataframe all-true",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Checks the result from a comparison",
|
||||
example: r#"let s = ([5 6 2 8] | dataframe to-series);
|
||||
let res = ($s > 9);
|
||||
$res | dataframe all-true"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let bool = series.as_ref().bool().map_err(|e| {
|
||||
parse_polars_error::<&str>(
|
||||
&e,
|
||||
&tag.span,
|
||||
Some("all-true only works with series of type bool"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let res = bool.all_true();
|
||||
|
||||
let value = Value {
|
||||
value: UntaggedValue::Primitive(res.into()),
|
||||
tag: tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(tag);
|
||||
data.insert_value("all_true", value);
|
||||
|
||||
Ok(OutputStream::one(data.into_value()))
|
||||
}
|
57
crates/nu-command/src/commands/dataframe/series/arg_max.rs
Normal file
57
crates/nu-command/src/commands/dataframe/series/arg_max.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::NuSeries, Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe arg-max"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return index for max value in series"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe arg-max")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Returns index for max value",
|
||||
example: "[1 3 2] | dataframe to-series | dataframe arg-max",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = series.as_ref().arg_max();
|
||||
|
||||
let value = match res {
|
||||
Some(index) => UntaggedValue::Primitive(Primitive::Int(index as i64)),
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing),
|
||||
};
|
||||
|
||||
let value = Value {
|
||||
value,
|
||||
tag: tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(tag);
|
||||
data.insert_value("arg-max", value);
|
||||
|
||||
Ok(OutputStream::one(data.into_value()))
|
||||
}
|
57
crates/nu-command/src/commands/dataframe/series/arg_min.rs
Normal file
57
crates/nu-command/src/commands/dataframe/series/arg_min.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
dataframe::NuSeries, Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe arg-min"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return index for min value in series"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe arg-min")
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Returns index for min value",
|
||||
example: "[1 3 2] | dataframe to-series | dataframe arg-min",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = series.as_ref().arg_min();
|
||||
|
||||
let value = match res {
|
||||
Some(index) => UntaggedValue::Primitive(Primitive::Int(index as i64)),
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing),
|
||||
};
|
||||
|
||||
let value = Value {
|
||||
value,
|
||||
tag: tag.clone(),
|
||||
};
|
||||
|
||||
let mut data = TaggedDictBuilder::new(tag);
|
||||
data.insert_value("arg-min", value);
|
||||
|
||||
Ok(OutputStream::one(data.into_value()))
|
||||
}
|
47
crates/nu-command/src/commands/dataframe/series/arg_sort.rs
Normal file
47
crates/nu-command/src/commands/dataframe/series/arg_sort.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{dataframe::NuSeries, Signature};
|
||||
use polars::prelude::IntoSeries;
|
||||
|
||||
pub struct DataFrame;
|
||||
|
||||
impl WholeStreamCommand for DataFrame {
|
||||
fn name(&self) -> &str {
|
||||
"dataframe arg-sort"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns indexes for a sorted series"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("dataframe arg-sort").switch("reverse", "reverse order", Some('r'))
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Returns indexes for a sorted series",
|
||||
example: "[1 2 2 3 3] | dataframe to-series | dataframe arg-sort",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn command(mut args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let reverse = args.has_flag("reverse");
|
||||
|
||||
let series = NuSeries::try_from_stream(&mut args.input, &tag.span)?;
|
||||
|
||||
let res = series.as_ref().argsort(reverse);
|
||||
|
||||
Ok(OutputStream::one(NuSeries::series_to_value(
|
||||
res.into_series(),
|
||||
tag,
|
||||
)))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user