forked from extern/nushell
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
a30837298d | |||
f377a3a7b4 | |||
83c874666a | |||
e000ed47cd | |||
af2f064f42 | |||
9c7b25134b | |||
2d15df9e6c | |||
d2ab287756 | |||
e73278990c | |||
12bc92df35 | |||
f19a801022 | |||
b193303aa3 | |||
e299e76fcf | |||
c857e18c4a | |||
5fb3df4054 | |||
8b597187fc | |||
930f9f0063 | |||
63d4df9810 | |||
13ba533fc4 | |||
6d60bab2fd | |||
5be774b2e5 | |||
b412ff92c0 | |||
5a75e11b0e | |||
e66bf70589 | |||
3924e9d50a | |||
8df748463d | |||
0113661c81 | |||
0ee054b14d | |||
80b39454ff | |||
97f3671e2c | |||
b674cee9d2 | |||
cb8491cfee | |||
8196b031f8 | |||
50dd56d3c4 | |||
0f7e1d4d01 | |||
ec77c572b9 | |||
f97561c416 | |||
5faa82e323 | |||
4e17292a12 | |||
666fbbb0d1 | |||
c6fe58467b | |||
46d1938f5c | |||
8229af7591 | |||
ee76523507 | |||
c283db373b | |||
1b0ed30516 | |||
a6fdee4a51 | |||
6951fb440c | |||
502c9ea706 | |||
22f67be461 | |||
77ffd06715 | |||
1d833ef972 | |||
0d8064ed2d | |||
cc06ea4d87 | |||
3cf7652e86 | |||
1eb28c6cb6 | |||
db590369a8 | |||
f4d654d2a2 | |||
5725e55abb | |||
b6d19cc9fa | |||
bc6c884a14 | |||
cb78bf8fd6 | |||
400bc97e35 | |||
2fd464bf7b | |||
e626522b3a | |||
791e07650d | |||
bf2363947b | |||
a2cc2259e7 | |||
808fe496a6 | |||
2fb48bd6ac |
6
.gitpod.Dockerfile
vendored
6
.gitpod.Dockerfile
vendored
@ -1,5 +1,9 @@
|
|||||||
FROM gitpod/workspace-full
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
|
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
|
||||||
|
# To force a rebuild, simply increase this counter:
|
||||||
|
ENV TRIGGER_REBUILD 1
|
||||||
|
|
||||||
USER gitpod
|
USER gitpod
|
||||||
|
|
||||||
RUN sudo apt-get update && \
|
RUN sudo apt-get update && \
|
||||||
@ -11,4 +15,4 @@ RUN sudo apt-get update && \
|
|||||||
rust-lldb \
|
rust-lldb \
|
||||||
&& sudo rm -rf /var/lib/apt/lists/*
|
&& sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV RUST_LLDB=/usr/bin/lldb-8
|
ENV RUST_LLDB=/usr/bin/lldb-11
|
||||||
|
2166
Cargo.lock
generated
2166
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
92
Cargo.toml
92
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
|||||||
name = "nu"
|
name = "nu"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
version = "0.21.0"
|
version = "0.24.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*/"]
|
members = ["crates/*/"]
|
||||||
@ -18,32 +18,33 @@ members = ["crates/*/"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = {version = "0.21.0", path = "./crates/nu-cli"}
|
nu-cli = {version = "0.24.0", path = "./crates/nu-cli"}
|
||||||
nu-data = {version = "0.21.0", path = "./crates/nu-data"}
|
nu-data = {version = "0.24.0", path = "./crates/nu-data"}
|
||||||
nu-errors = {version = "0.21.0", path = "./crates/nu-errors"}
|
nu-errors = {version = "0.24.0", path = "./crates/nu-errors"}
|
||||||
nu-parser = {version = "0.21.0", path = "./crates/nu-parser"}
|
nu-parser = {version = "0.24.0", path = "./crates/nu-parser"}
|
||||||
nu-plugin = {version = "0.21.0", path = "./crates/nu-plugin"}
|
nu-plugin = {version = "0.24.0", path = "./crates/nu-plugin"}
|
||||||
nu-protocol = {version = "0.21.0", path = "./crates/nu-protocol"}
|
nu-protocol = {version = "0.24.0", path = "./crates/nu-protocol"}
|
||||||
nu-source = {version = "0.21.0", path = "./crates/nu-source"}
|
nu-source = {version = "0.24.0", path = "./crates/nu-source"}
|
||||||
nu-value-ext = {version = "0.21.0", path = "./crates/nu-value-ext"}
|
nu-value-ext = {version = "0.24.0", path = "./crates/nu-value-ext"}
|
||||||
|
|
||||||
nu_plugin_binaryview = {version = "0.21.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
nu_plugin_binaryview = {version = "0.24.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||||
nu_plugin_chart = {version = "0.21.0", path = "./crates/nu_plugin_chart", optional = true}
|
nu_plugin_chart = {version = "0.24.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||||
nu_plugin_fetch = {version = "0.21.0", path = "./crates/nu_plugin_fetch", optional = true}
|
nu_plugin_fetch = {version = "0.24.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||||
nu_plugin_from_bson = {version = "0.21.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
nu_plugin_from_bson = {version = "0.24.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||||
nu_plugin_from_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
nu_plugin_from_sqlite = {version = "0.24.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||||
nu_plugin_inc = {version = "0.21.0", path = "./crates/nu_plugin_inc", optional = true}
|
nu_plugin_inc = {version = "0.24.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||||
nu_plugin_match = {version = "0.21.0", path = "./crates/nu_plugin_match", optional = true}
|
nu_plugin_match = {version = "0.24.0", path = "./crates/nu_plugin_match", optional = true}
|
||||||
nu_plugin_post = {version = "0.21.0", path = "./crates/nu_plugin_post", optional = true}
|
nu_plugin_post = {version = "0.24.0", path = "./crates/nu_plugin_post", optional = true}
|
||||||
nu_plugin_ps = {version = "0.21.0", path = "./crates/nu_plugin_ps", optional = true}
|
nu_plugin_ps = {version = "0.24.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||||
nu_plugin_s3 = {version = "0.21.0", path = "./crates/nu_plugin_s3", optional = true}
|
nu_plugin_s3 = {version = "0.24.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||||
nu_plugin_start = {version = "0.21.0", path = "./crates/nu_plugin_start", optional = true}
|
nu_plugin_start = {version = "0.24.0", path = "./crates/nu_plugin_start", optional = true}
|
||||||
nu_plugin_sys = {version = "0.21.0", path = "./crates/nu_plugin_sys", optional = true}
|
nu_plugin_sys = {version = "0.24.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||||
nu_plugin_textview = {version = "0.21.0", path = "./crates/nu_plugin_textview", optional = true}
|
nu_plugin_textview = {version = "0.24.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||||
nu_plugin_to_bson = {version = "0.21.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
nu_plugin_to_bson = {version = "0.24.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
nu_plugin_to_sqlite = {version = "0.21.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
nu_plugin_to_sqlite = {version = "0.24.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||||
nu_plugin_tree = {version = "0.21.0", path = "./crates/nu_plugin_tree", optional = true}
|
nu_plugin_tree = {version = "0.24.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||||
nu_plugin_xpath = {version = "0.21.0", path = "./crates/nu_plugin_xpath", optional = true}
|
nu_plugin_xpath = {version = "0.24.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||||
|
nu_plugin_selector = {version = "0.24.0", path = "./crates/nu_plugin_selector", optional = true}
|
||||||
|
|
||||||
# Required to bootstrap the main binary
|
# Required to bootstrap the main binary
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
@ -51,10 +52,11 @@ ctrlc = {version = "3.1.6", optional = true}
|
|||||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
itertools = "0.9.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
nu-test-support = {version = "0.21.0", path = "./crates/nu-test-support"}
|
nu-test-support = {version = "0.24.0", path = "./crates/nu-test-support"}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
||||||
@ -86,8 +88,9 @@ default = [
|
|||||||
"post",
|
"post",
|
||||||
"fetch",
|
"fetch",
|
||||||
"rich-benchmark",
|
"rich-benchmark",
|
||||||
|
"zip-support"
|
||||||
]
|
]
|
||||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"]
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
|
|
||||||
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||||
@ -102,6 +105,7 @@ post = ["nu_plugin_post"]
|
|||||||
ps = ["nu_plugin_ps"]
|
ps = ["nu_plugin_ps"]
|
||||||
sys = ["nu_plugin_sys"]
|
sys = ["nu_plugin_sys"]
|
||||||
textview = ["nu_plugin_textview"]
|
textview = ["nu_plugin_textview"]
|
||||||
|
zip-support = ["nu-cli/zip"]
|
||||||
|
|
||||||
# Extra
|
# Extra
|
||||||
binaryview = ["nu_plugin_binaryview"]
|
binaryview = ["nu_plugin_binaryview"]
|
||||||
@ -114,6 +118,13 @@ start = ["nu_plugin_start"]
|
|||||||
trash-support = ["nu-cli/trash-support"]
|
trash-support = ["nu-cli/trash-support"]
|
||||||
tree = ["nu_plugin_tree"]
|
tree = ["nu_plugin_tree"]
|
||||||
xpath = ["nu_plugin_xpath"]
|
xpath = ["nu_plugin_xpath"]
|
||||||
|
selector = ["nu_plugin_selector"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
#strip = "symbols" #Couldn't get working +nightly
|
||||||
|
opt-level = 'z' #Optimize for size
|
||||||
|
lto = true #Link Time Optimization
|
||||||
|
codegen-units = 1 #Reduce parallel codegen units
|
||||||
|
|
||||||
# Core plugins that ship with `cargo install nu` by default
|
# Core plugins that ship with `cargo install nu` by default
|
||||||
# Currently, Cargo limits us to installing only one binary
|
# Currently, Cargo limits us to installing only one binary
|
||||||
@ -190,6 +201,31 @@ name = "nu_plugin_extra_xpath"
|
|||||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||||
required-features = ["xpath"]
|
required-features = ["xpath"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_selector"
|
||||||
|
path = "src/plugins/nu_plugin_extra_selector.rs"
|
||||||
|
required-features = ["selector"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_from_bson"
|
||||||
|
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||||
|
required-features = ["bson"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_to_bson"
|
||||||
|
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||||
|
required-features = ["bson"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_from_sqlite"
|
||||||
|
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||||
|
required-features = ["sqlite"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_to_sqlite"
|
||||||
|
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||||
|
required-features = ["sqlite"]
|
||||||
|
|
||||||
# Main nu binary
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
|
16
README.md
16
README.md
@ -7,7 +7,7 @@
|
|||||||
[](https://changelog.com/podcast/363)
|
[](https://changelog.com/podcast/363)
|
||||||
[](https://twitter.com/nu_shell)
|
[](https://twitter.com/nu_shell)
|
||||||
|
|
||||||
## Nu Shell
|
## Nushell
|
||||||
|
|
||||||
A new type of shell.
|
A new type of shell.
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ There are also [good first issues](https://github.com/nushell/nushell/issues?q=i
|
|||||||
|
|
||||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
||||||
|
|
||||||
You can also find more learning resources in our [documentation](https://www.nushell.sh/documentation.html) site.
|
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
Try it in Gitpod.
|
Try it in Gitpod.
|
||||||
|
|
||||||
@ -44,9 +44,9 @@ Try it in Gitpod.
|
|||||||
|
|
||||||
### Local
|
### Local
|
||||||
|
|
||||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/en/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||||
|
|
||||||
To build Nu, you will need to use the **latest stable (1.41 or later)** version of the compiler.
|
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||||
|
|
||||||
Required dependencies:
|
Required dependencies:
|
||||||
|
|
||||||
@ -219,15 +219,15 @@ We can pipeline this into a command that gets the contents of one of the columns
|
|||||||
name │ nu
|
name │ nu
|
||||||
readme │ README.md
|
readme │ README.md
|
||||||
repository │ https://github.com/nushell/nushell
|
repository │ https://github.com/nushell/nushell
|
||||||
version │ 0.15.1
|
version │ 0.21.0
|
||||||
───────────────┴────────────────────────────────────
|
───────────────┴────────────────────────────────────
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we can use commands outside of Nu once we have the data we want:
|
Finally, we can use commands outside of Nu once we have the data we want:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version | echo $it
|
> open Cargo.toml | get package.version
|
||||||
0.15.1
|
0.21.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||||
@ -307,7 +307,7 @@ Nu is in heavy development, and will naturally change as it matures and people u
|
|||||||
|
|
||||||
## Current Roadmap
|
## Current Roadmap
|
||||||
|
|
||||||
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in NuShell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
We've added a `Roadmap Board` to help collaboratively capture the direction we're going for the current release as well as capture some important issues we'd like to see in Nushell. You can find the Roadmap [here](https://github.com/nushell/nushell/projects/2).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -4,32 +4,36 @@ description = "CLI for nushell"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.21.0"
|
version = "0.24.0"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-data = {version = "0.21.0", path = "../nu-data"}
|
nu-data = {version = "0.24.0", path = "../nu-data"}
|
||||||
nu-errors = {version = "0.21.0", path = "../nu-errors"}
|
nu-errors = {version = "0.24.0", path = "../nu-errors"}
|
||||||
nu-parser = {version = "0.21.0", path = "../nu-parser"}
|
nu-json = {version = "0.24.0", path = "../nu-json"}
|
||||||
nu-plugin = {version = "0.21.0", path = "../nu-plugin"}
|
nu-parser = {version = "0.24.0", path = "../nu-parser"}
|
||||||
nu-protocol = {version = "0.21.0", path = "../nu-protocol"}
|
nu-plugin = {version = "0.24.0", path = "../nu-plugin"}
|
||||||
nu-source = {version = "0.21.0", path = "../nu-source"}
|
nu-protocol = {version = "0.24.0", path = "../nu-protocol"}
|
||||||
nu-table = {version = "0.21.0", path = "../nu-table"}
|
nu-source = {version = "0.24.0", path = "../nu-source"}
|
||||||
nu-test-support = {version = "0.21.0", path = "../nu-test-support"}
|
nu-table = {version = "0.24.0", path = "../nu-table"}
|
||||||
nu-value-ext = {version = "0.21.0", path = "../nu-value-ext"}
|
nu-test-support = {version = "0.24.0", path = "../nu-test-support"}
|
||||||
|
nu-value-ext = {version = "0.24.0", path = "../nu-value-ext"}
|
||||||
|
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
async-trait = "0.1.40"
|
async-trait = "0.1.40"
|
||||||
base64 = "0.12.3"
|
base64 = "0.13.0"
|
||||||
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
byte-unit = "4.0.9"
|
byte-unit = "4.0.9"
|
||||||
bytes = "0.5.6"
|
bytes = "0.5.6"
|
||||||
calamine = "0.16.1"
|
calamine = "0.16.1"
|
||||||
chrono = {version = "0.4.15", features = ["serde"]}
|
chrono = {version = "0.4.15", features = ["serde"]}
|
||||||
|
chrono-tz = "0.5.3"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
|
clipboard = {version = "0.5.0", optional = true}
|
||||||
codespan-reporting = "0.9.5"
|
codespan-reporting = "0.9.5"
|
||||||
csv = "1.1.3"
|
csv = "1.1.3"
|
||||||
ctrlc = {version = "3.1.6", optional = true}
|
ctrlc = {version = "3.1.6", optional = true}
|
||||||
@ -39,11 +43,12 @@ dirs = {version = "3.0.1", optional = true}
|
|||||||
dtparse = "1.2.0"
|
dtparse = "1.2.0"
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
|
encoding_rs = "0.8.24"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||||
futures-util = "0.3.5"
|
|
||||||
futures_codec = "0.4.1"
|
futures_codec = "0.4.1"
|
||||||
|
futures-util = "0.3.5"
|
||||||
getset = "0.1.1"
|
getset = "0.1.1"
|
||||||
git2 = {version = "0.13.11", default_features = false, optional = true}
|
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
@ -52,7 +57,9 @@ htmlescape = "0.3.1"
|
|||||||
ical = "0.6.0"
|
ical = "0.6.0"
|
||||||
ichwh = {version = "0.3.4", optional = true}
|
ichwh = {version = "0.3.4", optional = true}
|
||||||
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||||
|
Inflector = "0.11"
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
|
lazy_static = "1.*"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
meval = "0.2.0"
|
meval = "0.2.0"
|
||||||
num-bigint = {version = "0.3.0", features = ["serde"]}
|
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||||
@ -65,12 +72,12 @@ ptree = {version = "0.3.0", optional = true}
|
|||||||
query_interface = "0.3.5"
|
query_interface = "0.3.5"
|
||||||
quick-xml = "0.18.1"
|
quick-xml = "0.18.1"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
rayon = "1.4.0"
|
||||||
regex = "1.3.9"
|
regex = "1.3.9"
|
||||||
roxmltree = "0.13.0"
|
roxmltree = "0.13.0"
|
||||||
rust-embed = "5.6.0"
|
rust-embed = "5.6.0"
|
||||||
rustyline = {version = "6.3.0", optional = true}
|
rustyline = {version = "6.3.0", optional = true}
|
||||||
serde = {version = "1.0.115", features = ["derive"]}
|
serde = {version = "1.0.115", features = ["derive"]}
|
||||||
serde-hjson = "0.9.1"
|
|
||||||
serde_bytes = "0.11.5"
|
serde_bytes = "0.11.5"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
@ -85,20 +92,16 @@ tempfile = "3.1.0"
|
|||||||
term = {version = "0.6.1", optional = true}
|
term = {version = "0.6.1", optional = true}
|
||||||
term_size = "0.3.2"
|
term_size = "0.3.2"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
|
titlecase = "1.0"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
|
trash = {version = "1.2.0", optional = true}
|
||||||
unicode-segmentation = "1.6.0"
|
unicode-segmentation = "1.6.0"
|
||||||
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
uom = {version = "0.28.0", features = ["f64", "try-from"]}
|
||||||
|
url = "2.1.1"
|
||||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||||
which = {version = "4.0.2", optional = true}
|
which = {version = "4.0.2", optional = true}
|
||||||
zip = {version = "0.5.7", optional = true}
|
zip = {version = "0.5.7", optional = true}
|
||||||
|
|
||||||
Inflector = "0.11"
|
|
||||||
clipboard = {version = "0.5.0", optional = true}
|
|
||||||
encoding_rs = "0.8.24"
|
|
||||||
rayon = "1.4.0"
|
|
||||||
trash = {version = "1.1.1", optional = true}
|
|
||||||
url = "2.1.1"
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "1.0.0"
|
umask = "1.0.0"
|
||||||
users = "0.10.0"
|
users = "0.10.0"
|
||||||
@ -115,7 +118,7 @@ optional = true
|
|||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
git2 = {version = "0.13.11", optional = true}
|
shadow-rs = "0.3.20"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.9.2"
|
quickcheck = "0.9.2"
|
||||||
|
@ -1,36 +1,6 @@
|
|||||||
use std::path::Path;
|
fn main() -> shadow_rs::SdResult<()> {
|
||||||
use std::{env, fs, io};
|
let src_path = std::env::var("CARGO_MANIFEST_DIR")?;
|
||||||
|
let out_path = std::env::var("OUT_DIR")?;
|
||||||
fn main() -> Result<(), io::Error> {
|
shadow_rs::Shadow::build(src_path, out_path)?;
|
||||||
let out_dir = env::var_os("OUT_DIR").expect(
|
|
||||||
"\
|
|
||||||
OUT_DIR environment variable not found. \
|
|
||||||
OUT_DIR is guaranteed to to exist in a build script by cargo - see \
|
|
||||||
https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts\
|
|
||||||
");
|
|
||||||
|
|
||||||
let latest_commit_hash = latest_commit_hash(env::current_dir()?).unwrap_or_default();
|
|
||||||
|
|
||||||
let commit_hash_path = Path::new(&out_dir).join("git_commit_hash");
|
|
||||||
fs::write(commit_hash_path, latest_commit_hash)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn latest_commit_hash<P: AsRef<Path>>(dir: P) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
#[cfg(feature = "git2")]
|
|
||||||
{
|
|
||||||
use git2::Repository;
|
|
||||||
let dir = dir.as_ref();
|
|
||||||
Ok(Repository::discover(dir)?
|
|
||||||
.head()?
|
|
||||||
.peel_to_commit()?
|
|
||||||
.id()
|
|
||||||
.to_string())
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "git2"))]
|
|
||||||
{
|
|
||||||
Ok(String::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -86,8 +86,10 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(Touch),
|
whole_stream_command(Touch),
|
||||||
whole_stream_command(Cpy),
|
whole_stream_command(Cpy),
|
||||||
whole_stream_command(Date),
|
whole_stream_command(Date),
|
||||||
|
whole_stream_command(DateListTimeZone),
|
||||||
whole_stream_command(DateNow),
|
whole_stream_command(DateNow),
|
||||||
whole_stream_command(DateUTC),
|
whole_stream_command(DateToTable),
|
||||||
|
whole_stream_command(DateToTimeZone),
|
||||||
whole_stream_command(DateFormat),
|
whole_stream_command(DateFormat),
|
||||||
whole_stream_command(Cal),
|
whole_stream_command(Cal),
|
||||||
whole_stream_command(Mkdir),
|
whole_stream_command(Mkdir),
|
||||||
@ -120,6 +122,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(Autoview),
|
whole_stream_command(Autoview),
|
||||||
whole_stream_command(Table),
|
whole_stream_command(Table),
|
||||||
// Text manipulation
|
// Text manipulation
|
||||||
|
whole_stream_command(Hash),
|
||||||
|
whole_stream_command(HashBase64),
|
||||||
whole_stream_command(Split),
|
whole_stream_command(Split),
|
||||||
whole_stream_command(SplitColumn),
|
whole_stream_command(SplitColumn),
|
||||||
whole_stream_command(SplitRow),
|
whole_stream_command(SplitRow),
|
||||||
@ -159,7 +163,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(Ansi),
|
whole_stream_command(Ansi),
|
||||||
whole_stream_command(Char),
|
whole_stream_command(Char),
|
||||||
// Column manipulation
|
// Column manipulation
|
||||||
whole_stream_command(MoveColumn),
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Reject),
|
whole_stream_command(Reject),
|
||||||
whole_stream_command(Select),
|
whole_stream_command(Select),
|
||||||
whole_stream_command(Get),
|
whole_stream_command(Get),
|
||||||
@ -180,6 +184,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(Nth),
|
whole_stream_command(Nth),
|
||||||
whole_stream_command(Drop),
|
whole_stream_command(Drop),
|
||||||
whole_stream_command(Format),
|
whole_stream_command(Format),
|
||||||
|
whole_stream_command(FileSize),
|
||||||
whole_stream_command(Where),
|
whole_stream_command(Where),
|
||||||
whole_stream_command(If),
|
whole_stream_command(If),
|
||||||
whole_stream_command(Compact),
|
whole_stream_command(Compact),
|
||||||
@ -198,6 +203,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(EachWindow),
|
whole_stream_command(EachWindow),
|
||||||
whole_stream_command(Empty),
|
whole_stream_command(Empty),
|
||||||
// Table manipulation
|
// Table manipulation
|
||||||
|
whole_stream_command(Flatten),
|
||||||
whole_stream_command(Move),
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Merge),
|
whole_stream_command(Merge),
|
||||||
whole_stream_command(Shuffle),
|
whole_stream_command(Shuffle),
|
||||||
@ -211,6 +217,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(AutoenvTrust),
|
whole_stream_command(AutoenvTrust),
|
||||||
whole_stream_command(AutoenvUnTrust),
|
whole_stream_command(AutoenvUnTrust),
|
||||||
whole_stream_command(Math),
|
whole_stream_command(Math),
|
||||||
|
whole_stream_command(MathAbs),
|
||||||
whole_stream_command(MathAverage),
|
whole_stream_command(MathAverage),
|
||||||
whole_stream_command(MathEval),
|
whole_stream_command(MathEval),
|
||||||
whole_stream_command(MathMedian),
|
whole_stream_command(MathMedian),
|
||||||
@ -221,6 +228,9 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(MathSummation),
|
whole_stream_command(MathSummation),
|
||||||
whole_stream_command(MathVariance),
|
whole_stream_command(MathVariance),
|
||||||
whole_stream_command(MathProduct),
|
whole_stream_command(MathProduct),
|
||||||
|
whole_stream_command(MathRound),
|
||||||
|
whole_stream_command(MathFloor),
|
||||||
|
whole_stream_command(MathCeil),
|
||||||
// File format output
|
// File format output
|
||||||
whole_stream_command(To),
|
whole_stream_command(To),
|
||||||
whole_stream_command(ToCSV),
|
whole_stream_command(ToCSV),
|
||||||
@ -258,6 +268,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
whole_stream_command(RandomUUID),
|
whole_stream_command(RandomUUID),
|
||||||
whole_stream_command(RandomInteger),
|
whole_stream_command(RandomInteger),
|
||||||
|
whole_stream_command(RandomDecimal),
|
||||||
|
whole_stream_command(RandomChars),
|
||||||
// Path
|
// Path
|
||||||
whole_stream_command(PathBasename),
|
whole_stream_command(PathBasename),
|
||||||
whole_stream_command(PathCommand),
|
whole_stream_command(PathCommand),
|
||||||
@ -273,6 +285,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(UrlPath),
|
whole_stream_command(UrlPath),
|
||||||
whole_stream_command(UrlHost),
|
whole_stream_command(UrlHost),
|
||||||
whole_stream_command(UrlQuery),
|
whole_stream_command(UrlQuery),
|
||||||
|
whole_stream_command(Seq),
|
||||||
|
whole_stream_command(SeqDates),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[cfg(feature = "clipboard-cli")]
|
#[cfg(feature = "clipboard-cli")]
|
||||||
@ -317,6 +331,7 @@ pub async fn run_vec_of_pipelines(
|
|||||||
#[cfg(feature = "rustyline-support")]
|
#[cfg(feature = "rustyline-support")]
|
||||||
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||||
match input {
|
match input {
|
||||||
|
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||||
Ok(s) => LineResult::Success(s),
|
Ok(s) => LineResult::Success(s),
|
||||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||||
@ -386,55 +401,56 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
|||||||
if let Some(prompt) = configuration.var("prompt") {
|
if let Some(prompt) = configuration.var("prompt") {
|
||||||
let prompt_line = prompt.as_string()?;
|
let prompt_line = prompt.as_string()?;
|
||||||
|
|
||||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
let (result, err) = nu_parser::lite_parse(&prompt_line, 0);
|
||||||
Ok(result) => {
|
|
||||||
let mut prompt_block =
|
|
||||||
nu_parser::classify_block(&result, context.registry());
|
|
||||||
|
|
||||||
let env = context.get_env();
|
if err.is_some() {
|
||||||
|
use crate::git::current_branch;
|
||||||
|
format!(
|
||||||
|
"\x1b[32m{}{}\x1b[m> ",
|
||||||
|
cwd,
|
||||||
|
match current_branch() {
|
||||||
|
Some(s) => format!("({})", s),
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let prompt_block = nu_parser::classify_block(&result, context.registry());
|
||||||
|
|
||||||
prompt_block.block.expand_it_usage();
|
let env = context.get_env();
|
||||||
|
|
||||||
match run_block(
|
match run_block(
|
||||||
&prompt_block.block,
|
&prompt_block.block,
|
||||||
&mut context,
|
&mut context,
|
||||||
InputStream::empty(),
|
InputStream::empty(),
|
||||||
Scope::from_env(env),
|
Scope::from_env(env),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||||
Ok(string_result) => {
|
Ok(string_result) => {
|
||||||
let errors = context.get_errors();
|
let errors = context.get_errors();
|
||||||
context.maybe_print_errors(Text::from(prompt_line));
|
context.maybe_print_errors(Text::from(prompt_line));
|
||||||
context.clear_errors();
|
context.clear_errors();
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
"> ".to_string()
|
|
||||||
} else {
|
|
||||||
string_result.item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
|
||||||
context.clear_errors();
|
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
"> ".to_string()
|
"> ".to_string()
|
||||||
|
} else {
|
||||||
|
string_result.item
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||||
context.clear_errors();
|
context.clear_errors();
|
||||||
|
|
||||||
"> ".to_string()
|
"> ".to_string()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
Err(e) => {
|
||||||
Err(e) => {
|
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
context.clear_errors();
|
||||||
context.clear_errors();
|
|
||||||
|
|
||||||
"> ".to_string()
|
"> ".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -496,6 +512,11 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
|||||||
context.maybe_print_errors(Text::from(line));
|
context.maybe_print_errors(Text::from(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LineResult::ClearHistory => {
|
||||||
|
rl.clear_history();
|
||||||
|
let _ = rl.save_history(&history_path);
|
||||||
|
}
|
||||||
|
|
||||||
LineResult::Error(line, err) => {
|
LineResult::Error(line, err) => {
|
||||||
rl.add_history_entry(&line);
|
rl.add_history_entry(&line);
|
||||||
let _ = rl.save_history(&history_path);
|
let _ = rl.save_history(&history_path);
|
||||||
@ -831,8 +852,8 @@ fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hin
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn chomp_newline(s: &str) -> &str {
|
fn chomp_newline(s: &str) -> &str {
|
||||||
if s.ends_with('\n') {
|
if let Some(s) = s.strip_suffix('\n') {
|
||||||
&s[..s.len() - 1]
|
s
|
||||||
} else {
|
} else {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
@ -845,20 +866,23 @@ pub enum LineResult {
|
|||||||
Break,
|
Break,
|
||||||
CtrlC,
|
CtrlC,
|
||||||
CtrlD,
|
CtrlD,
|
||||||
|
ClearHistory,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||||
let line = if line.ends_with('\n') {
|
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||||
&line[..line.len() - 1]
|
s
|
||||||
} else {
|
} else {
|
||||||
line
|
line
|
||||||
};
|
};
|
||||||
|
|
||||||
let lite_result = nu_parser::lite_parse(&line, 0)?;
|
let (lite_result, err) = nu_parser::lite_parse(&line, 0);
|
||||||
|
if let Some(err) = err {
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||||
let mut classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
let classified_block = nu_parser::classify_block(&lite_result, ctx.registry());
|
||||||
classified_block.block.expand_it_usage();
|
|
||||||
|
|
||||||
let input_stream = InputStream::empty();
|
let input_stream = InputStream::empty();
|
||||||
let env = ctx.get_env();
|
let env = ctx.get_env();
|
||||||
@ -888,18 +912,16 @@ pub async fn process_line(
|
|||||||
let line = chomp_newline(line);
|
let line = chomp_newline(line);
|
||||||
ctx.raw_input = line.to_string();
|
ctx.raw_input = line.to_string();
|
||||||
|
|
||||||
let result = match nu_parser::lite_parse(&line, 0) {
|
let (result, err) = nu_parser::lite_parse(&line, 0);
|
||||||
Err(err) => {
|
|
||||||
return LineResult::Error(line.to_string(), err.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(val) => val,
|
if let Some(err) = err {
|
||||||
};
|
return LineResult::Error(line.to_string(), err.into());
|
||||||
|
}
|
||||||
|
|
||||||
debug!("=== Parsed ===");
|
debug!("=== Parsed ===");
|
||||||
debug!("{:#?}", result);
|
debug!("{:#?}", result);
|
||||||
|
|
||||||
let mut classified_block = nu_parser::classify_block(&result, ctx.registry());
|
let classified_block = nu_parser::classify_block(&result, ctx.registry());
|
||||||
|
|
||||||
debug!("{:#?}", classified_block);
|
debug!("{:#?}", classified_block);
|
||||||
//println!("{:#?}", pipeline);
|
//println!("{:#?}", pipeline);
|
||||||
@ -1016,8 +1038,6 @@ pub async fn process_line(
|
|||||||
InputStream::empty()
|
InputStream::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
classified_block.block.expand_it_usage();
|
|
||||||
|
|
||||||
trace!("{:#?}", classified_block);
|
trace!("{:#?}", classified_block);
|
||||||
let env = ctx.get_env();
|
let env = ctx.get_env();
|
||||||
match run_block(
|
match run_block(
|
||||||
@ -1093,7 +1113,8 @@ pub fn print_err(err: ShellError, source: &Text) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn quickcheck_parse(data: String) -> bool {
|
fn quickcheck_parse(data: String) -> bool {
|
||||||
if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) {
|
let (lite_block, err) = nu_parser::lite_parse(&data, 0);
|
||||||
|
if err.is_none() {
|
||||||
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||||
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
let _ = nu_parser::classify_block(&lite_block, context.registry());
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ pub(crate) mod every;
|
|||||||
pub(crate) mod exec;
|
pub(crate) mod exec;
|
||||||
pub(crate) mod exit;
|
pub(crate) mod exit;
|
||||||
pub(crate) mod first;
|
pub(crate) mod first;
|
||||||
|
pub(crate) mod flatten;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
pub(crate) mod from;
|
pub(crate) mod from;
|
||||||
pub(crate) mod from_csv;
|
pub(crate) mod from_csv;
|
||||||
@ -61,6 +62,7 @@ pub(crate) mod from_yaml;
|
|||||||
pub(crate) mod get;
|
pub(crate) mod get;
|
||||||
pub(crate) mod group_by;
|
pub(crate) mod group_by;
|
||||||
pub(crate) mod group_by_date;
|
pub(crate) mod group_by_date;
|
||||||
|
pub(crate) mod hash_;
|
||||||
pub(crate) mod headers;
|
pub(crate) mod headers;
|
||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
pub(crate) mod histogram;
|
pub(crate) mod histogram;
|
||||||
@ -97,6 +99,8 @@ pub(crate) mod run_alias;
|
|||||||
pub(crate) mod run_external;
|
pub(crate) mod run_external;
|
||||||
pub(crate) mod save;
|
pub(crate) mod save;
|
||||||
pub(crate) mod select;
|
pub(crate) mod select;
|
||||||
|
pub(crate) mod seq;
|
||||||
|
pub(crate) mod seq_dates;
|
||||||
pub(crate) mod shells;
|
pub(crate) mod shells;
|
||||||
pub(crate) mod shuffle;
|
pub(crate) mod shuffle;
|
||||||
pub(crate) mod size;
|
pub(crate) mod size;
|
||||||
@ -150,7 +154,7 @@ pub(crate) use config::{
|
|||||||
};
|
};
|
||||||
pub(crate) use count::Count;
|
pub(crate) use count::Count;
|
||||||
pub(crate) use cp::Cpy;
|
pub(crate) use cp::Cpy;
|
||||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||||
pub(crate) use debug::Debug;
|
pub(crate) use debug::Debug;
|
||||||
pub(crate) use default::Default;
|
pub(crate) use default::Default;
|
||||||
pub(crate) use describe::Describe;
|
pub(crate) use describe::Describe;
|
||||||
@ -175,7 +179,8 @@ pub(crate) use every::Every;
|
|||||||
pub(crate) use exec::Exec;
|
pub(crate) use exec::Exec;
|
||||||
pub(crate) use exit::Exit;
|
pub(crate) use exit::Exit;
|
||||||
pub(crate) use first::First;
|
pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use flatten::Command as Flatten;
|
||||||
|
pub(crate) use format::{FileSize, Format};
|
||||||
pub(crate) use from::From;
|
pub(crate) use from::From;
|
||||||
pub(crate) use from_csv::FromCSV;
|
pub(crate) use from_csv::FromCSV;
|
||||||
pub(crate) use from_eml::FromEML;
|
pub(crate) use from_eml::FromEML;
|
||||||
@ -195,6 +200,7 @@ pub(crate) use from_yaml::FromYML;
|
|||||||
pub(crate) use get::Get;
|
pub(crate) use get::Get;
|
||||||
pub(crate) use group_by::Command as GroupBy;
|
pub(crate) use group_by::Command as GroupBy;
|
||||||
pub(crate) use group_by_date::GroupByDate;
|
pub(crate) use group_by_date::GroupByDate;
|
||||||
|
pub(crate) use hash_::{Hash, HashBase64};
|
||||||
pub(crate) use headers::Headers;
|
pub(crate) use headers::Headers;
|
||||||
pub(crate) use help::Help;
|
pub(crate) use help::Help;
|
||||||
pub(crate) use histogram::Histogram;
|
pub(crate) use histogram::Histogram;
|
||||||
@ -206,12 +212,12 @@ pub(crate) use last::Last;
|
|||||||
pub(crate) use lines::Lines;
|
pub(crate) use lines::Lines;
|
||||||
pub(crate) use ls::Ls;
|
pub(crate) use ls::Ls;
|
||||||
pub(crate) use math::{
|
pub(crate) use math::{
|
||||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
|
||||||
MathStddev, MathSummation, MathVariance,
|
MathMinimum, MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||||
};
|
};
|
||||||
pub(crate) use merge::Merge;
|
pub(crate) use merge::Merge;
|
||||||
pub(crate) use mkdir::Mkdir;
|
pub(crate) use mkdir::Mkdir;
|
||||||
pub(crate) use move_::{Move, MoveColumn, Mv};
|
pub(crate) use move_::{Move, Mv};
|
||||||
pub(crate) use next::Next;
|
pub(crate) use next::Next;
|
||||||
pub(crate) use nth::Nth;
|
pub(crate) use nth::Nth;
|
||||||
pub(crate) use open::Open;
|
pub(crate) use open::Open;
|
||||||
@ -226,7 +232,9 @@ pub(crate) use prev::Previous;
|
|||||||
pub(crate) use pwd::Pwd;
|
pub(crate) use pwd::Pwd;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
pub(crate) use random::RandomUUID;
|
pub(crate) use random::RandomUUID;
|
||||||
pub(crate) use random::{Random, RandomBool, RandomDice, RandomInteger};
|
pub(crate) use random::{
|
||||||
|
Random, RandomBool, RandomChars, RandomDecimal, RandomDice, RandomInteger,
|
||||||
|
};
|
||||||
pub(crate) use range::Range;
|
pub(crate) use range::Range;
|
||||||
pub(crate) use reduce::Reduce;
|
pub(crate) use reduce::Reduce;
|
||||||
pub(crate) use reject::Reject;
|
pub(crate) use reject::Reject;
|
||||||
@ -236,6 +244,8 @@ pub(crate) use rm::Remove;
|
|||||||
pub(crate) use run_external::RunExternalCommand;
|
pub(crate) use run_external::RunExternalCommand;
|
||||||
pub(crate) use save::Save;
|
pub(crate) use save::Save;
|
||||||
pub(crate) use select::Select;
|
pub(crate) use select::Select;
|
||||||
|
pub(crate) use seq::Seq;
|
||||||
|
pub(crate) use seq_dates::SeqDates;
|
||||||
pub(crate) use shells::Shells;
|
pub(crate) use shells::Shells;
|
||||||
pub(crate) use shuffle::Shuffle;
|
pub(crate) use shuffle::Shuffle;
|
||||||
pub(crate) use size::Size;
|
pub(crate) use size::Size;
|
||||||
@ -278,19 +288,26 @@ mod tests {
|
|||||||
use crate::examples::{test_anchors, test_examples};
|
use crate::examples::{test_anchors, test_examples};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
fn commands() -> Vec<Command> {
|
fn full_tests() -> Vec<Command> {
|
||||||
vec![
|
vec![
|
||||||
whole_stream_command(Append),
|
whole_stream_command(Append),
|
||||||
whole_stream_command(GroupBy),
|
whole_stream_command(GroupBy),
|
||||||
whole_stream_command(Insert),
|
whole_stream_command(Insert),
|
||||||
|
whole_stream_command(Move),
|
||||||
whole_stream_command(Update),
|
whole_stream_command(Update),
|
||||||
whole_stream_command(Empty),
|
whole_stream_command(Empty),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn only_examples() -> Vec<Command> {
|
||||||
|
let mut commands = full_tests();
|
||||||
|
commands.extend(vec![whole_stream_command(Flatten)]);
|
||||||
|
commands
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
for cmd in commands() {
|
for cmd in only_examples() {
|
||||||
test_examples(cmd)?;
|
test_examples(cmd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +316,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tracks_metadata() -> Result<(), ShellError> {
|
fn tracks_metadata() -> Result<(), ShellError> {
|
||||||
for cmd in commands() {
|
for cmd in full_tests() {
|
||||||
test_anchors(cmd)?;
|
test_anchors(cmd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::command_registry::CommandRegistry;
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::types::deduction::{VarDeclaration, VarSyntaxShapeDeductor};
|
||||||
|
use deduction_to_signature::DeductionToSignature;
|
||||||
|
use log::trace;
|
||||||
use nu_data::config;
|
use nu_data::config;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_parser::SignatureRegistry;
|
|
||||||
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
UntaggedValue, Value,
|
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub struct Alias;
|
pub struct Alias;
|
||||||
|
|
||||||
@ -86,7 +86,6 @@ pub async fn alias(
|
|||||||
},
|
},
|
||||||
_ctx,
|
_ctx,
|
||||||
) = args.process(®istry).await?;
|
) = args.process(®istry).await?;
|
||||||
let mut processed_args: Vec<String> = vec![];
|
|
||||||
|
|
||||||
if let Some(true) = save {
|
if let Some(true) = save {
|
||||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||||
@ -110,7 +109,7 @@ pub async fn alias(
|
|||||||
let alias: Value = raw_input.trim().to_string().into();
|
let alias: Value = raw_input.trim().to_string().into();
|
||||||
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
||||||
|
|
||||||
// add to startup if alias doesn't exist and replce if it does
|
// add to startup if alias doesn't exist and replace if it does
|
||||||
match result.get_mut("startup") {
|
match result.get_mut("startup") {
|
||||||
Some(startup) => {
|
Some(startup) => {
|
||||||
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
||||||
@ -132,209 +131,41 @@ pub async fn alias(
|
|||||||
config::write(&result, &None)?;
|
config::write(&result, &None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in list.iter() {
|
let mut processed_args: Vec<VarDeclaration> = vec![];
|
||||||
if let Ok(string) = item.as_string() {
|
for (_, item) in list.iter().enumerate() {
|
||||||
processed_args.push(format!("${}", string));
|
match item.as_string() {
|
||||||
|
Ok(var_name) => {
|
||||||
|
let dollar_var_name = format!("${}", var_name);
|
||||||
|
processed_args.push(VarDeclaration {
|
||||||
|
name: dollar_var_name,
|
||||||
|
// type_decl: None,
|
||||||
|
span: item.tag.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Expected a string",
|
||||||
|
"expected a string",
|
||||||
|
item.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace!("Found vars: {:?}", processed_args);
|
||||||
|
|
||||||
|
let inferred_shapes = {
|
||||||
|
if let Some(true) = infer {
|
||||||
|
VarSyntaxShapeDeductor::infer_vars(&processed_args, &block, ®istry)?
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::labeled_error(
|
processed_args.into_iter().map(|arg| (arg, None)).collect()
|
||||||
"Expected a string",
|
|
||||||
"expected a string",
|
|
||||||
item.tag(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(true) = infer {
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
|
||||||
CommandAction::AddAlias(
|
|
||||||
name.to_string(),
|
|
||||||
to_arg_shapes(processed_args, &block, ®istry)?,
|
|
||||||
block,
|
|
||||||
),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::action(
|
|
||||||
CommandAction::AddAlias(
|
|
||||||
name.to_string(),
|
|
||||||
processed_args
|
|
||||||
.into_iter()
|
|
||||||
.map(|arg| (arg, SyntaxShape::Any))
|
|
||||||
.collect(),
|
|
||||||
block,
|
|
||||||
),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_arg_shapes(
|
|
||||||
args: Vec<String>,
|
|
||||||
block: &Block,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
|
|
||||||
match find_block_shapes(block, registry) {
|
|
||||||
Ok(found) => Ok(args
|
|
||||||
.iter()
|
|
||||||
.map(|arg| {
|
|
||||||
(
|
|
||||||
arg.clone(),
|
|
||||||
match found.get(arg) {
|
|
||||||
None | Some((_, None)) => SyntaxShape::Any,
|
|
||||||
Some((_, Some(shape))) => *shape,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()),
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
|
|
||||||
|
|
||||||
fn check_insert(
|
|
||||||
existing: &mut ShapeMap,
|
|
||||||
to_add: (String, (Span, Option<SyntaxShape>)),
|
|
||||||
) -> Result<(), ShellError> {
|
|
||||||
match (to_add.1).1 {
|
|
||||||
None => match existing.get(&to_add.0) {
|
|
||||||
None => {
|
|
||||||
existing.insert(to_add.0, to_add.1);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Some(_) => Ok(()),
|
|
||||||
},
|
|
||||||
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
|
|
||||||
None => Ok(()),
|
|
||||||
Some(exist) => match exist.1 {
|
|
||||||
None => Ok(()),
|
|
||||||
Some(shape) => match shape {
|
|
||||||
SyntaxShape::Any => Ok(()),
|
|
||||||
shape if shape == new => Ok(()),
|
|
||||||
_ => Err(ShellError::labeled_error_with_secondary(
|
|
||||||
"Type conflict in alias variable use",
|
|
||||||
format!("{:?}", new),
|
|
||||||
(to_add.1).0,
|
|
||||||
format!("{:?}", shape),
|
|
||||||
exist.0,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
|
|
||||||
for (k, v) in new.iter() {
|
|
||||||
check_insert(existing, (k.clone(), *v))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_expr_shapes(
|
|
||||||
spanned_expr: &SpannedExpression,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<ShapeMap, ShellError> {
|
|
||||||
match &spanned_expr.expr {
|
|
||||||
// TODO range will need similar if/when invocations can be parsed within range expression
|
|
||||||
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
|
|
||||||
find_expr_shapes(&bin.right, registry)
|
|
||||||
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
|
|
||||||
}),
|
|
||||||
Expression::Block(b) => find_block_shapes(&b, registry),
|
|
||||||
Expression::Path(path) => match &path.head.expr {
|
|
||||||
Expression::Invocation(b) => find_block_shapes(&b, registry),
|
|
||||||
Expression::Variable(Variable::Other(var, _)) => {
|
|
||||||
let mut result = HashMap::new();
|
|
||||||
result.insert(var.to_string(), (spanned_expr.span, None));
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
_ => Ok(HashMap::new()),
|
|
||||||
},
|
|
||||||
_ => Ok(HashMap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
|
|
||||||
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
|
|
||||||
found
|
|
||||||
.iter()
|
|
||||||
.map(|(v, sh)| match sh.1 {
|
|
||||||
None => (v.clone(), (sh.0, Some(sig_shape))),
|
|
||||||
Some(shape) => (v.clone(), (sh.0, Some(shape))),
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
};
|
};
|
||||||
|
let signature = DeductionToSignature::get(&name.item, &inferred_shapes);
|
||||||
|
trace!("Inferred signature: {:?}", signature);
|
||||||
|
|
||||||
let mut arg_shapes = HashMap::new();
|
Ok(OutputStream::one(ReturnSuccess::action(
|
||||||
for pipeline in &block.block {
|
CommandAction::AddAlias(Box::new(signature), block),
|
||||||
for classified in &pipeline.list {
|
)))
|
||||||
match classified {
|
|
||||||
ClassifiedCommand::Expr(spanned_expr) => {
|
|
||||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
||||||
check_merge(&mut arg_shapes, &found)?
|
|
||||||
}
|
|
||||||
ClassifiedCommand::Internal(internal) => {
|
|
||||||
if let Some(signature) = registry.get(&internal.name) {
|
|
||||||
if let Some(positional) = &internal.args.positional {
|
|
||||||
for (i, spanned_expr) in positional.iter().enumerate() {
|
|
||||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
||||||
if i >= signature.positional.len() {
|
|
||||||
if let Some((sig_shape, _)) = &signature.rest_positional {
|
|
||||||
check_merge(
|
|
||||||
&mut arg_shapes,
|
|
||||||
&apply_shape(found, *sig_shape),
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
unreachable!("should have error'd in parsing");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let (pos_type, _) = &signature.positional[i];
|
|
||||||
match pos_type {
|
|
||||||
// TODO pass on mandatory/optional?
|
|
||||||
PositionalType::Mandatory(_, sig_shape)
|
|
||||||
| PositionalType::Optional(_, sig_shape) => {
|
|
||||||
check_merge(
|
|
||||||
&mut arg_shapes,
|
|
||||||
&apply_shape(found, *sig_shape),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(named) = &internal.args.named {
|
|
||||||
for (name, val) in named.iter() {
|
|
||||||
if let NamedValue::Value(_, spanned_expr) = val {
|
|
||||||
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
||||||
match signature.named.get(name) {
|
|
||||||
None => {
|
|
||||||
unreachable!("should have error'd in parsing");
|
|
||||||
}
|
|
||||||
Some((named_type, _)) => {
|
|
||||||
if let NamedType::Mandatory(_, sig_shape)
|
|
||||||
| NamedType::Optional(_, sig_shape) = named_type
|
|
||||||
{
|
|
||||||
check_merge(
|
|
||||||
&mut arg_shapes,
|
|
||||||
&apply_shape(found, *sig_shape),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!("registry has lost name it provided");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(arg_shapes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -349,3 +180,42 @@ mod tests {
|
|||||||
Ok(test_examples(Alias {})?)
|
Ok(test_examples(Alias {})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod deduction_to_signature {
|
||||||
|
//For now this logic is relativly simple.
|
||||||
|
//For each var, one mandatory positional is added.
|
||||||
|
//As soon as more support for optional positional arguments is arrived,
|
||||||
|
//this logic might be a little bit more tricky.
|
||||||
|
use crate::types::deduction::{Deduction, VarDeclaration};
|
||||||
|
use nu_protocol::{PositionalType, Signature, SyntaxShape};
|
||||||
|
|
||||||
|
pub struct DeductionToSignature {}
|
||||||
|
impl DeductionToSignature {
|
||||||
|
pub fn get(
|
||||||
|
cmd_name: &str,
|
||||||
|
deductions: &[(VarDeclaration, Option<Deduction>)],
|
||||||
|
) -> Signature {
|
||||||
|
let mut signature = Signature::build(cmd_name);
|
||||||
|
for (decl, deduction) in deductions {
|
||||||
|
match deduction {
|
||||||
|
None => signature.positional.push((
|
||||||
|
PositionalType::optional(&decl.name, SyntaxShape::Any),
|
||||||
|
decl.name.clone(),
|
||||||
|
)),
|
||||||
|
Some(deduction) => match deduction {
|
||||||
|
Deduction::VarShapeDeduction(normal_var_deduction) => {
|
||||||
|
signature.positional.push((
|
||||||
|
PositionalType::optional(
|
||||||
|
&decl.name,
|
||||||
|
normal_var_deduction[0].deduction,
|
||||||
|
),
|
||||||
|
decl.name.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,12 +3,15 @@ use crate::prelude::*;
|
|||||||
use ansi_term::Color;
|
use ansi_term::Color;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
pub struct Ansi;
|
pub struct Ansi;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AnsiArgs {
|
struct AnsiArgs {
|
||||||
color: Value,
|
color: Value,
|
||||||
|
escape: Option<Tagged<String>>,
|
||||||
|
osc: Option<Tagged<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -18,15 +21,70 @@ impl WholeStreamCommand for Ansi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ansi").required(
|
Signature::build("ansi")
|
||||||
"color",
|
.optional(
|
||||||
SyntaxShape::Any,
|
"color",
|
||||||
"the name of the color to use or 'reset' to reset the color",
|
SyntaxShape::Any,
|
||||||
)
|
"the name of the color to use or 'reset' to reset the color",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"escape", // \x1b
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"escape sequence without the escape character(s)",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"osc",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||||
|
Some('o'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Output ANSI codes to change color"
|
r#"Output ANSI codes to change color
|
||||||
|
|
||||||
|
For escape sequences:
|
||||||
|
Escape: '\x1b[' is not required for --escape parameter
|
||||||
|
Format: #(;#)m
|
||||||
|
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
|
||||||
|
There can be multiple text formatting sequence numbers
|
||||||
|
separated by a ; and ending with an m where the # is of the
|
||||||
|
following values:
|
||||||
|
attributes
|
||||||
|
0 reset / normal display
|
||||||
|
1 bold or increased intensity
|
||||||
|
2 faint or decreased intensity
|
||||||
|
3 italic on (non-mono font)
|
||||||
|
4 underline on
|
||||||
|
5 slow blink on
|
||||||
|
6 fast blink on
|
||||||
|
7 reverse video on
|
||||||
|
8 nondisplayed (invisible) on
|
||||||
|
9 strike-through on
|
||||||
|
|
||||||
|
foreground/bright colors background/bright colors
|
||||||
|
30/90 black 40/100 black
|
||||||
|
31/91 red 41/101 red
|
||||||
|
32/92 green 42/102 green
|
||||||
|
33/93 yellow 43/103 yellow
|
||||||
|
34/94 blue 44/104 blue
|
||||||
|
35/95 magenta 45/105 magenta
|
||||||
|
36/96 cyan 46/106 cyan
|
||||||
|
37/97 white 47/107 white
|
||||||
|
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
|
OSC: '\x1b]' is not required for --osc parameter
|
||||||
|
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
|
||||||
|
Format: #
|
||||||
|
0 Set window title and icon name
|
||||||
|
1 Set icon name
|
||||||
|
2 Set window title
|
||||||
|
4 Set/read color palette
|
||||||
|
9 iTerm2 Grown notifications
|
||||||
|
10 Set foreground color (x11 color spec)
|
||||||
|
11 Set background color (x11 color spec)
|
||||||
|
... others"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -49,6 +107,14 @@ impl WholeStreamCommand for Ansi {
|
|||||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||||
)]),
|
)]),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||||
|
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||||
|
result: Some(vec![Value::from(
|
||||||
|
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||||
|
)]),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +123,41 @@ impl WholeStreamCommand for Ansi {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
let (AnsiArgs { color, escape, osc }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
if let Some(e) = escape {
|
||||||
|
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||||
|
if esc_vec[0] == '\\' {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"no need for escape characters",
|
||||||
|
"no need for escape characters",
|
||||||
|
e.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let output = format!("\x1b[{}", e.item);
|
||||||
|
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(e.tag()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(o) = osc {
|
||||||
|
let osc_vec: Vec<char> = o.item.chars().collect();
|
||||||
|
if osc_vec[0] == '\\' {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"no need for escape characters",
|
||||||
|
"no need for escape characters",
|
||||||
|
o.tag(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
|
||||||
|
// OCS's need to end with a bell '\x07' char
|
||||||
|
let output = format!("\x1b]{};", o.item);
|
||||||
|
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(output).into_value(o.tag()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let color_string = color.as_string()?;
|
let color_string = color.as_string()?;
|
||||||
|
|
||||||
let ansi_code = str_to_ansi_color(color_string);
|
let ansi_code = str_to_ansi_color(color_string);
|
||||||
|
|
||||||
if let Some(output) = ansi_code {
|
if let Some(output) = ansi_code {
|
||||||
@ -74,6 +171,7 @@ impl WholeStreamCommand for Ansi {
|
|||||||
color.tag(),
|
color.tag(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
row: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Command;
|
pub struct Command;
|
||||||
@ -26,7 +26,7 @@ impl WholeStreamCommand for Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Append the given row to the table"
|
"Append a row to the table"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -34,30 +34,57 @@ impl WholeStreamCommand for Command {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let (Arguments { mut row }, input) = args.process(registry).await?;
|
let (Arguments { mut value }, input) = args.process(registry).await?;
|
||||||
|
|
||||||
let input: Vec<Value> = input.collect().await;
|
let input: Vec<Value> = input.collect().await;
|
||||||
|
|
||||||
if let Some(first) = input.get(0) {
|
if let Some(first) = input.get(0) {
|
||||||
row.tag = first.tag();
|
value.tag = first.tag();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(
|
// Checks if we are trying to append a row literal
|
||||||
futures::stream::iter(input.into_iter().chain(vec![row]).map(ReturnSuccess::value))
|
if let Value {
|
||||||
.to_output_stream(),
|
value: UntaggedValue::Table(values),
|
||||||
|
tag,
|
||||||
|
} = &value
|
||||||
|
{
|
||||||
|
if values.len() == 1 && values[0].is_row() {
|
||||||
|
value = values[0].value.clone().into_value(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(
|
||||||
|
input
|
||||||
|
.into_iter()
|
||||||
|
.chain(vec![value])
|
||||||
|
.map(ReturnSuccess::value),
|
||||||
)
|
)
|
||||||
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
use nu_protocol::row;
|
||||||
description: "Add something to the end of a list or table",
|
|
||||||
example: "echo [1 2 3] | append 4",
|
vec![
|
||||||
result: Some(vec![
|
Example {
|
||||||
UntaggedValue::int(1).into(),
|
description: "Add values to the end of the table",
|
||||||
UntaggedValue::int(2).into(),
|
example: "echo [1 2 3] | append 4",
|
||||||
UntaggedValue::int(3).into(),
|
result: Some(vec![
|
||||||
UntaggedValue::int(4).into(),
|
UntaggedValue::int(1).into(),
|
||||||
]),
|
UntaggedValue::int(2).into(),
|
||||||
}]
|
UntaggedValue::int(3).into(),
|
||||||
|
UntaggedValue::int(4).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Add row value to the end of the table",
|
||||||
|
example: "echo [[country]; [Ecuador] ['New Zealand']] | append [[country]; [USA]]",
|
||||||
|
result: Some(vec![
|
||||||
|
row! { "country".into() => Value::from("Ecuador")},
|
||||||
|
row! { "country".into() => Value::from("New Zealand")},
|
||||||
|
row! { "country".into() => Value::from("USA")},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ pub struct Char;
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CharArgs {
|
struct CharArgs {
|
||||||
name: Tagged<String>,
|
name: Tagged<String>,
|
||||||
|
unicode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -18,11 +19,13 @@ impl WholeStreamCommand for Char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ansi").required(
|
Signature::build("ansi")
|
||||||
"character",
|
.required(
|
||||||
SyntaxShape::Any,
|
"character",
|
||||||
"the name of the character to output",
|
SyntaxShape::Any,
|
||||||
)
|
"the name of the character to output",
|
||||||
|
)
|
||||||
|
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -45,6 +48,11 @@ impl WholeStreamCommand for Char {
|
|||||||
UntaggedValue::string("\u{2261}").into(),
|
UntaggedValue::string("\u{2261}").into(),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Output unicode character",
|
||||||
|
example: r#"char -u 1f378"#,
|
||||||
|
result: Some(vec![Value::from("\u{1f378}")]),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,24 +61,44 @@ impl WholeStreamCommand for Char {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let (CharArgs { name }, _) = args.process(®istry).await?;
|
let (CharArgs { name, unicode }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
let special_character = str_to_character(&name.item);
|
if unicode {
|
||||||
|
let decoded_char = string_to_unicode_char(&name.item);
|
||||||
if let Some(output) = special_character {
|
if let Some(output) = decoded_char {
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::string(output).into_value(name.tag()),
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
)))
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"error decoding unicode character",
|
||||||
|
"error decoding unicode character",
|
||||||
|
name.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::labeled_error(
|
let special_character = str_to_character(&name.item);
|
||||||
"Unknown character",
|
if let Some(output) = special_character {
|
||||||
"unknown character",
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
name.tag(),
|
UntaggedValue::string(output).into_value(name.tag()),
|
||||||
))
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"error finding named character",
|
||||||
|
"error finding named character",
|
||||||
|
name.tag(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||||
|
u32::from_str_radix(s, 16)
|
||||||
|
.ok()
|
||||||
|
.and_then(std::char::from_u32)
|
||||||
|
}
|
||||||
|
|
||||||
fn str_to_character(s: &str) -> Option<String> {
|
fn str_to_character(s: &str) -> Option<String> {
|
||||||
match s {
|
match s {
|
||||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||||
@ -78,6 +106,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
|||||||
"sp" | "space" => Some(" ".into()),
|
"sp" | "space" => Some(" ".into()),
|
||||||
// Unicode names came from https://www.compart.com/en/unicode
|
// Unicode names came from https://www.compart.com/en/unicode
|
||||||
// Private Use Area (U+E000-U+F8FF)
|
// 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()), //
|
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||||
|
|
||||||
@ -96,15 +125,14 @@ fn str_to_character(s: &str) -> Option<String> {
|
|||||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||||
|
|
||||||
// Weather symbols
|
// Weather symbols
|
||||||
"sun" => Some("\x1b[33;1m\u{2600}\x1b[0m".to_string()), // Yellow Bold ☀
|
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||||
"moon" => Some("\x1b[36m\u{263d}\x1b[0m".to_string()), // Cyan ☽
|
"moon" => Some("🌛".to_string()),
|
||||||
"clouds" => Some("\x1b[37;1m\u{2601}\x1b[0m".to_string()), // White Bold ☁
|
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||||
"rain" => Some("\x1b[37;1m\u{2614}\x1b[0m".to_string()), // White Bold ☔
|
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||||
"fog" => Some("\x1b[37;1m\u{2592}\x1b[0m".to_string()), // White Bold ▒
|
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||||
"mist" => Some("\x1b[34m\u{2591}\x1b[0m".to_string()), // Blue ░
|
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||||
"haze" => Some("\x1b[33m\u{2591}\x1b[0m".to_string()), // Yellow ░
|
"snowy" | "snow" => Some("❄️".to_string()),
|
||||||
"snow" => Some("\x1b[37;1m\u{2744}\x1b[0m".to_string()), // White Bold ❄
|
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||||
"thunderstorm" => Some("\x1b[33;1m\u{26a1}\x1b[0m".to_string()), // Yellow Bold ⚡
|
|
||||||
|
|
||||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||||
@ -123,6 +151,7 @@ fn str_to_character(s: &str) -> Option<String> {
|
|||||||
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||||
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||||
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use nu_protocol::hir::{Block, ClassifiedCommand, Commands};
|
|||||||
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(crate) async fn run_block(
|
pub async fn run_block(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
ctx: &mut EvaluationContext,
|
ctx: &mut EvaluationContext,
|
||||||
mut input: InputStream,
|
mut input: InputStream,
|
||||||
|
@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
|||||||
use crate::futures::ThreadedReceiver;
|
use crate::futures::ThreadedReceiver;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
@ -13,6 +14,7 @@ use futures_codec::FramedRead;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::hir::Expression;
|
||||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
@ -50,6 +52,7 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
let mut command_args = vec![];
|
let mut command_args = vec![];
|
||||||
for arg in command.args.iter() {
|
for arg in command.args.iter() {
|
||||||
|
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||||
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||||
|
|
||||||
// Skip any arguments that don't really exist, treating them as optional
|
// Skip any arguments that don't really exist, treating them as optional
|
||||||
@ -65,8 +68,10 @@ async fn run_with_stdin(
|
|||||||
for t in table {
|
for t in table {
|
||||||
match &t.value {
|
match &t.value {
|
||||||
UntaggedValue::Primitive(_) => {
|
UntaggedValue::Primitive(_) => {
|
||||||
command_args
|
command_args.push((
|
||||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||||
|
is_literal,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
@ -80,14 +85,14 @@ async fn run_with_stdin(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||||
command_args.push(trimmed_value_string);
|
command_args.push((trimmed_value_string, is_literal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let process_args = command_args
|
let process_args = command_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|(arg, _is_literal)| {
|
||||||
let home_dir;
|
let home_dir;
|
||||||
|
|
||||||
#[cfg(feature = "dirs")]
|
#[cfg(feature = "dirs")]
|
||||||
@ -103,8 +108,9 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
if !_is_literal {
|
||||||
add_quotes(&arg)
|
let escaped = escape_double_quotes(&arg);
|
||||||
|
add_double_quotes(&escaped)
|
||||||
} else {
|
} else {
|
||||||
arg.as_ref().to_string()
|
arg.as_ref().to_string()
|
||||||
}
|
}
|
||||||
@ -219,36 +225,19 @@ fn spawn(
|
|||||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||||
UntaggedValue::Primitive(Primitive::String(s))
|
UntaggedValue::Primitive(Primitive::String(s))
|
||||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||||
if let Err(e) = stdin_write.write(s.as_bytes()) {
|
if stdin_write.write(s.as_bytes()).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
message,
|
|
||||||
"application may have closed before completing pipeline",
|
|
||||||
&stdin_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdin_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
UntaggedValue::Primitive(Primitive::Binary(b)) => {
|
||||||
if let Err(e) = stdin_write.write(b) {
|
if stdin_write.write(b).is_err() {
|
||||||
let message = format!("Unable to write to stdin (error = {})", e);
|
// Other side has closed, so exit
|
||||||
|
return Ok(());
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
|
||||||
message,
|
|
||||||
"application may have closed before completing pipeline",
|
|
||||||
&stdin_name_tag,
|
|
||||||
)),
|
|
||||||
tag: stdin_name_tag,
|
|
||||||
}));
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsupported => {
|
unsupported => {
|
||||||
|
println!("Unsupported: {:?}", unsupported);
|
||||||
let _ = stdin_write_tx.send(Ok(Value {
|
let _ = stdin_write_tx.send(Ok(Value {
|
||||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||||
format!(
|
format!(
|
||||||
@ -494,11 +483,6 @@ where
|
|||||||
shellexpand::tilde_with_context(input, home_dir)
|
shellexpand::tilde_with_context(input, home_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
|
||||||
argument.chars().any(|c| c.is_whitespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn argument_is_quoted(argument: &str) -> bool {
|
fn argument_is_quoted(argument: &str) -> bool {
|
||||||
if argument.len() < 2 {
|
if argument.len() < 2 {
|
||||||
return false;
|
return false;
|
||||||
@ -509,10 +493,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn add_quotes(argument: &str) -> String {
|
fn add_double_quotes(argument: &str) -> String {
|
||||||
format!("\"{}\"", argument)
|
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)]
|
#[allow(unused)]
|
||||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||||
if !argument_is_quoted(argument) {
|
if !argument_is_quoted(argument) {
|
||||||
@ -538,7 +532,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use super::{run_external_command, EvaluationContext, InputStream};
|
use super::{run_external_command, EvaluationContext, InputStream};
|
||||||
@ -618,10 +612,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
fn checks_escape_double_quotes() {
|
||||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -649,9 +643,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -185,9 +185,9 @@ pub(crate) async fn run_internal_command(
|
|||||||
));
|
));
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
CommandAction::AddAlias(name, args, block) => {
|
CommandAction::AddAlias(sig, block) => {
|
||||||
context.add_commands(vec![whole_stream_command(
|
context.add_commands(vec![whole_stream_command(
|
||||||
AliasCommand::new(name, args, block),
|
AliasCommand::new(*sig, block),
|
||||||
)]);
|
)]);
|
||||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ pub async fn clip(
|
|||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in values.iter() {
|
for i in values.iter() {
|
||||||
if !first {
|
if !first {
|
||||||
new_copy_data.push_str("\n");
|
new_copy_data.push('\n');
|
||||||
} else {
|
} else {
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
|
|
||||||
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
|
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
Dictionary, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
use std::fmt::{self, write};
|
||||||
|
|
||||||
pub struct Date;
|
pub struct Date;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct FormatArgs {
|
pub struct FormatArgs {
|
||||||
format: Tagged<String>,
|
format: Tagged<String>,
|
||||||
raw: Option<bool>,
|
table: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -24,11 +24,11 @@ impl WholeStreamCommand for Date {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("date format")
|
Signature::build("date format")
|
||||||
.required("format", SyntaxShape::String, "strftime format")
|
.required("format", SyntaxShape::String, "strftime format")
|
||||||
.switch("raw", "print date without tables", Some('r'))
|
.switch("table", "print date in a table", Some('t'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"format the current date using the given format string."
|
"Format a given date using the given format string."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -38,6 +38,21 @@ impl WholeStreamCommand for Date {
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
format(args, registry).await
|
format(args, registry).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Format the current date",
|
||||||
|
example: "date now | date format '%Y.%m.%d_%H %M %S,%z'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Format the current date and print in a table",
|
||||||
|
example: "date now | date format -t '%Y-%m-%d_%H:%M:%S %z'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn format(
|
pub async fn format(
|
||||||
@ -46,20 +61,46 @@ pub async fn format(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (FormatArgs { format, raw }, _) = args.process(®istry).await?;
|
let (FormatArgs { format, table }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
let dt_fmt = format.to_string();
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut output = String::new();
|
||||||
|
if let Err(fmt::Error) =
|
||||||
|
write(&mut output, format_args!("{}", dt.format(&format.item)))
|
||||||
|
{
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"The date format is invalid",
|
||||||
|
"invalid strftime format",
|
||||||
|
&format.tag,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let value = if table {
|
||||||
|
let mut indexmap = IndexMap::new();
|
||||||
|
indexmap.insert(
|
||||||
|
"formatted".to_string(),
|
||||||
|
UntaggedValue::string(&output).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
let value = {
|
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||||
let local: DateTime<Local> = Local::now();
|
} else {
|
||||||
if let Some(true) = raw {
|
UntaggedValue::string(&output).into_value(&tag)
|
||||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
};
|
||||||
} else {
|
|
||||||
date_to_value(local, tag, dt_fmt)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(OutputStream::one(value))
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
82
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
82
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono_tz::TZ_VARIANTS;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date list-timezone"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date list-timezone")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"List supported time zones."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
list_timezone(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "List all supported time zones",
|
||||||
|
example: "date list-timezone",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "List all supported European time zones",
|
||||||
|
example: "date list-timezone | where timezone =~ Europe",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_timezone(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once(®istry).await?;
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let list = TZ_VARIANTS.iter().map(move |tz| {
|
||||||
|
let mut entries = IndexMap::new();
|
||||||
|
|
||||||
|
entries.insert(
|
||||||
|
"timezone".to_string(),
|
||||||
|
UntaggedValue::string(tz.name()).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(ReturnSuccess::Value(
|
||||||
|
UntaggedValue::Row(Dictionary { entries }).into_value(&tag),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(futures::stream::iter(list).to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
pub mod list_timezone;
|
||||||
pub mod now;
|
pub mod now;
|
||||||
pub mod utc;
|
pub mod to_table;
|
||||||
|
pub mod to_timezone;
|
||||||
|
|
||||||
mod utils;
|
mod parser;
|
||||||
|
|
||||||
pub use command::Command as Date;
|
pub use command::Command as Date;
|
||||||
pub use format::Date as DateFormat;
|
pub use format::Date as DateFormat;
|
||||||
|
pub use list_timezone::Date as DateListTimeZone;
|
||||||
pub use now::Date as DateNow;
|
pub use now::Date as DateNow;
|
||||||
pub use utc::Date as DateUTC;
|
pub use to_table::Date as DateToTable;
|
||||||
|
pub use to_timezone::Date as DateToTimeZone;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, UntaggedValue};
|
||||||
use crate::commands::date::utils::date_to_value;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use nu_protocol::Signature;
|
|
||||||
|
|
||||||
pub struct Date;
|
pub struct Date;
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ impl WholeStreamCommand for Date {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"return the current date."
|
"Get the current date."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -35,16 +33,12 @@ pub async fn now(
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
|
||||||
let args = args.evaluate_once(®istry).await?;
|
let args = args.evaluate_once(®istry).await?;
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
let no_fmt = "".to_string();
|
let now: DateTime<Local> = Local::now();
|
||||||
|
|
||||||
let value = {
|
let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
|
||||||
let local: DateTime<Local> = Local::now();
|
|
||||||
date_to_value(local, tag, no_fmt)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(OutputStream::one(value))
|
Ok(OutputStream::one(value))
|
||||||
}
|
}
|
||||||
|
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Modified from chrono::format::scan
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, Offset, TimeZone};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
use titlecase::titlecase;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
|
pub enum ParseErrorKind {
|
||||||
|
/// Given field is out of permitted range.
|
||||||
|
OutOfRange,
|
||||||
|
|
||||||
|
/// The input string has some invalid character sequence for given formatting items.
|
||||||
|
Invalid,
|
||||||
|
|
||||||
|
/// The input string has been prematurely ended.
|
||||||
|
TooShort,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn datetime_in_timezone(
|
||||||
|
dt: &DateTime<FixedOffset>,
|
||||||
|
s: &str,
|
||||||
|
) -> Result<DateTime<FixedOffset>, ParseErrorKind> {
|
||||||
|
match timezone_offset_internal(s, true, true) {
|
||||||
|
Ok(offset) => match FixedOffset::east_opt(offset) {
|
||||||
|
Some(offset) => Ok(dt.with_timezone(&offset)),
|
||||||
|
None => Err(ParseErrorKind::OutOfRange),
|
||||||
|
},
|
||||||
|
Err(ParseErrorKind::Invalid) => {
|
||||||
|
if s.to_lowercase() == "local" {
|
||||||
|
Ok(dt.with_timezone(Local::now().offset()))
|
||||||
|
} else {
|
||||||
|
let tz: Tz = parse_timezone_internal(s)?;
|
||||||
|
let offset = tz.offset_from_utc_datetime(&dt.naive_utc()).fix();
|
||||||
|
Ok(dt.with_timezone(&offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_timezone_internal(s: &str) -> Result<Tz, ParseErrorKind> {
|
||||||
|
if let Ok(tz) = s.parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else if let Ok(tz) = titlecase(s).parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else if let Ok(tz) = s.to_uppercase().parse() {
|
||||||
|
Ok(tz)
|
||||||
|
} else {
|
||||||
|
Err(ParseErrorKind::Invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timezone_offset_internal(
|
||||||
|
mut s: &str,
|
||||||
|
consume_colon: bool,
|
||||||
|
allow_missing_minutes: bool,
|
||||||
|
) -> Result<i32, ParseErrorKind> {
|
||||||
|
fn digits(s: &str) -> Result<(u8, u8), ParseErrorKind> {
|
||||||
|
let b = s.as_bytes();
|
||||||
|
if b.len() < 2 {
|
||||||
|
Err(ParseErrorKind::TooShort)
|
||||||
|
} else {
|
||||||
|
Ok((b[0], b[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let negative = match s.as_bytes().first() {
|
||||||
|
Some(&b'+') => false,
|
||||||
|
Some(&b'-') => true,
|
||||||
|
Some(_) => return Err(ParseErrorKind::Invalid),
|
||||||
|
None => return Err(ParseErrorKind::TooShort),
|
||||||
|
};
|
||||||
|
s = &s[1..];
|
||||||
|
|
||||||
|
// hours (00--99)
|
||||||
|
let hours = match digits(s)? {
|
||||||
|
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
|
||||||
|
_ => return Err(ParseErrorKind::Invalid),
|
||||||
|
};
|
||||||
|
s = &s[2..];
|
||||||
|
|
||||||
|
// colons (and possibly other separators)
|
||||||
|
if consume_colon {
|
||||||
|
s = s.trim_start_matches(|c: char| c == ':' || c.is_whitespace());
|
||||||
|
}
|
||||||
|
|
||||||
|
// minutes (00--59)
|
||||||
|
// if the next two items are digits then we have to add minutes
|
||||||
|
let minutes = if let Ok(ds) = digits(s) {
|
||||||
|
match ds {
|
||||||
|
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
|
||||||
|
(b'6'..=b'9', b'0'..=b'9') => return Err(ParseErrorKind::OutOfRange),
|
||||||
|
_ => return Err(ParseErrorKind::Invalid),
|
||||||
|
}
|
||||||
|
} else if allow_missing_minutes {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
return Err(ParseErrorKind::TooShort);
|
||||||
|
};
|
||||||
|
match s.len() {
|
||||||
|
len if len >= 2 => &s[2..],
|
||||||
|
len if len == 0 => s,
|
||||||
|
_ => return Err(ParseErrorKind::TooShort),
|
||||||
|
};
|
||||||
|
|
||||||
|
let seconds = hours * 3600 + minutes * 60;
|
||||||
|
Ok(if negative { -seconds } else { seconds })
|
||||||
|
}
|
113
crates/nu-cli/src/commands/date/to_table.rs
Normal file
113
crates/nu-cli/src/commands/date/to_table.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date to-table"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date to-table")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Print the date in a structured table."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
to_table(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Print the current date in a table",
|
||||||
|
example: "date now | date to-table",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn to_table(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let args = args.evaluate_once(®istry).await?;
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let input = args.input;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut indexmap = IndexMap::new();
|
||||||
|
|
||||||
|
indexmap.insert(
|
||||||
|
"year".to_string(),
|
||||||
|
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||||
|
);
|
||||||
|
indexmap.insert(
|
||||||
|
"month".to_string(),
|
||||||
|
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||||
|
);
|
||||||
|
indexmap.insert(
|
||||||
|
"day".to_string(),
|
||||||
|
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||||
|
);
|
||||||
|
indexmap.insert(
|
||||||
|
"hour".to_string(),
|
||||||
|
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||||
|
);
|
||||||
|
indexmap.insert(
|
||||||
|
"minute".to_string(),
|
||||||
|
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||||
|
);
|
||||||
|
indexmap.insert(
|
||||||
|
"second".to_string(),
|
||||||
|
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tz = dt.offset();
|
||||||
|
indexmap.insert(
|
||||||
|
"timezone".to_string(),
|
||||||
|
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||||
|
|
||||||
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
118
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
118
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
use crate::commands::date::parser::{datetime_in_timezone, ParseErrorKind};
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Date;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DateToTimeZoneArgs {
|
||||||
|
timezone: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Date {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"date to-timezone"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("date to-timezone").required(
|
||||||
|
"time zone",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"time zone description",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert a date to a given time zone.
|
||||||
|
|
||||||
|
Use `date list-timezone` to list all supported time zones.
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
to_timezone(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get the current date in UTC+05:00",
|
||||||
|
example: "date now | date to-timezone +0500",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the current local date",
|
||||||
|
example: "date now | date to-timezone local",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the current date in Hawaii",
|
||||||
|
example: "date now | date to-timezone US/Hawaii",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn to_timezone(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let (DateToTimeZoneArgs { timezone }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |value| match value {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||||
|
..
|
||||||
|
} => match datetime_in_timezone(&dt, &timezone.item) {
|
||||||
|
Ok(dt) => {
|
||||||
|
let value = UntaggedValue::date(dt).into_value(&tag);
|
||||||
|
|
||||||
|
ReturnSuccess::value(value)
|
||||||
|
}
|
||||||
|
Err(e) => Err(ShellError::labeled_error(
|
||||||
|
error_message(e),
|
||||||
|
"invalid time zone",
|
||||||
|
&timezone.tag,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"Expected a date from pipeline",
|
||||||
|
"requires date input",
|
||||||
|
&tag,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_message(err: ParseErrorKind) -> &'static str {
|
||||||
|
match err {
|
||||||
|
ParseErrorKind::Invalid => "The time zone description is invalid",
|
||||||
|
ParseErrorKind::OutOfRange => "The time zone offset is out of range",
|
||||||
|
ParseErrorKind::TooShort => "The format of the time zone is invalid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Date;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Date {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
|
|
||||||
use crate::commands::date::utils::date_to_value;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use nu_protocol::Signature;
|
|
||||||
|
|
||||||
pub struct Date;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for Date {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"date utc"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("date utc")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"return the current date in utc."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
utc(args, registry).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn utc(
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let registry = registry.clone();
|
|
||||||
let args = args.evaluate_once(®istry).await?;
|
|
||||||
let tag = args.call_info.name_tag.clone();
|
|
||||||
|
|
||||||
let no_fmt = "".to_string();
|
|
||||||
|
|
||||||
let value = {
|
|
||||||
let local: DateTime<Utc> = Utc::now();
|
|
||||||
date_to_value(local, tag, no_fmt)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(OutputStream::one(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Date;
|
|
||||||
use super::ShellError;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
Ok(test_examples(Date {})?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use chrono::DateTime;
|
|
||||||
use nu_protocol::{Dictionary, Value};
|
|
||||||
|
|
||||||
use chrono::{Datelike, TimeZone, Timelike};
|
|
||||||
use core::fmt::Display;
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use nu_protocol::UntaggedValue;
|
|
||||||
|
|
||||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
|
||||||
where
|
|
||||||
T::Offset: Display,
|
|
||||||
{
|
|
||||||
let result = dt.format(&dt_format);
|
|
||||||
format!("{}", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
|
||||||
where
|
|
||||||
T::Offset: Display,
|
|
||||||
{
|
|
||||||
let mut indexmap = IndexMap::new();
|
|
||||||
|
|
||||||
if dt_format.is_empty() {
|
|
||||||
indexmap.insert(
|
|
||||||
"year".to_string(),
|
|
||||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
|
||||||
);
|
|
||||||
indexmap.insert(
|
|
||||||
"month".to_string(),
|
|
||||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
|
||||||
);
|
|
||||||
indexmap.insert(
|
|
||||||
"day".to_string(),
|
|
||||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
|
||||||
);
|
|
||||||
indexmap.insert(
|
|
||||||
"hour".to_string(),
|
|
||||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
|
||||||
);
|
|
||||||
indexmap.insert(
|
|
||||||
"minute".to_string(),
|
|
||||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
|
||||||
);
|
|
||||||
indexmap.insert(
|
|
||||||
"second".to_string(),
|
|
||||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
|
||||||
);
|
|
||||||
|
|
||||||
let tz = dt.offset();
|
|
||||||
indexmap.insert(
|
|
||||||
"timezone".to_string(),
|
|
||||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let result = dt.format(&dt_format);
|
|
||||||
indexmap.insert(
|
|
||||||
"formatted".to_string(),
|
|
||||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
|
||||||
}
|
|
@ -6,8 +6,7 @@ use crate::prelude::*;
|
|||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
hir::Block, hir::Expression, hir::SpannedExpression, hir::Synthetic, Scope, Signature,
|
hir::Block, Scope, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
@ -73,34 +72,35 @@ impl WholeStreamCommand for Each {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_expanded_it_usage(head: &SpannedExpression) -> bool {
|
|
||||||
matches!(&*head, SpannedExpression {
|
|
||||||
expr: Expression::Synthetic(Synthetic::String(s)),
|
|
||||||
..
|
|
||||||
} if s == "expanded-each")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn process_row(
|
pub async fn process_row(
|
||||||
block: Arc<Block>,
|
block: Arc<Block>,
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
head: Arc<Box<SpannedExpression>>,
|
|
||||||
mut context: Arc<EvaluationContext>,
|
mut context: Arc<EvaluationContext>,
|
||||||
input: Value,
|
input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let input_clone = input.clone();
|
let input_clone = input.clone();
|
||||||
let input_stream = if is_expanded_it_usage(&head) {
|
// When we process a row, we need to know whether the block wants to have the contents of the row as
|
||||||
|
// a parameter to the block (so it gets assigned to a variable that can be used inside the block) or
|
||||||
|
// if it wants the contents as as an input stream
|
||||||
|
|
||||||
|
let input_stream = if !block.params.is_empty() {
|
||||||
InputStream::empty()
|
InputStream::empty()
|
||||||
} else {
|
} else {
|
||||||
once(async { Ok(input_clone) }).to_input_stream()
|
once(async { Ok(input_clone) }).to_input_stream()
|
||||||
};
|
};
|
||||||
Ok(run_block(
|
|
||||||
&block,
|
let scope = if !block.params.is_empty() {
|
||||||
Arc::make_mut(&mut context),
|
// FIXME: add check for more than parameter, once that's supported
|
||||||
input_stream,
|
Scope::append_var(scope, block.params[0].clone(), input)
|
||||||
Scope::append_it(scope, input),
|
} else {
|
||||||
|
scope
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
run_block(&block, Arc::make_mut(&mut context), input_stream, scope)
|
||||||
|
.await?
|
||||||
|
.to_output_stream(),
|
||||||
)
|
)
|
||||||
.await?
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||||
@ -116,7 +116,6 @@ async fn each(
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
|
||||||
let scope = raw_args.call_info.scope.clone();
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
let (each_args, input): (EachArgs, _) = raw_args.process(®istry).await?;
|
||||||
@ -128,12 +127,11 @@ async fn each(
|
|||||||
.then(move |input| {
|
.then(move |input| {
|
||||||
let block = block.clone();
|
let block = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let row = make_indexed_item(input.0, input.1);
|
let row = make_indexed_item(input.0, input.1);
|
||||||
|
|
||||||
async {
|
async {
|
||||||
match process_row(block, scope, head, context, row).await {
|
match process_row(block, scope, context, row).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
}
|
}
|
||||||
@ -146,11 +144,10 @@ async fn each(
|
|||||||
.then(move |input| {
|
.then(move |input| {
|
||||||
let block = block.clone();
|
let block = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
async {
|
async {
|
||||||
match process_row(block, scope, head, context, input).await {
|
match process_row(block, scope, context, input).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => OutputStream::one(Err(e)),
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,7 @@ use crate::commands::each::process_row;
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
hir::Block, hir::SpannedExpression, ReturnSuccess, Scope, Signature, SyntaxShape,
|
|
||||||
UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@ -52,7 +49,6 @@ impl WholeStreamCommand for EachGroup {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
|
||||||
let scope = raw_args.call_info.scope.clone();
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process(®istry).await?;
|
let (each_args, input): (EachGroupArgs, _) = raw_args.process(®istry).await?;
|
||||||
@ -61,13 +57,7 @@ impl WholeStreamCommand for EachGroup {
|
|||||||
Ok(input
|
Ok(input
|
||||||
.chunks(each_args.group_size.item)
|
.chunks(each_args.group_size.item)
|
||||||
.then(move |input| {
|
.then(move |input| {
|
||||||
run_block_on_vec(
|
run_block_on_vec(input, block.clone(), scope.clone(), context.clone())
|
||||||
input,
|
|
||||||
block.clone(),
|
|
||||||
scope.clone(),
|
|
||||||
head.clone(),
|
|
||||||
context.clone(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.to_output_stream())
|
.to_output_stream())
|
||||||
@ -78,7 +68,6 @@ pub(crate) fn run_block_on_vec(
|
|||||||
input: Vec<Value>,
|
input: Vec<Value>,
|
||||||
block: Arc<Block>,
|
block: Arc<Block>,
|
||||||
scope: Arc<Scope>,
|
scope: Arc<Scope>,
|
||||||
head: Arc<Box<SpannedExpression>>,
|
|
||||||
context: Arc<EvaluationContext>,
|
context: Arc<EvaluationContext>,
|
||||||
) -> impl Future<Output = OutputStream> {
|
) -> impl Future<Output = OutputStream> {
|
||||||
let value = Value {
|
let value = Value {
|
||||||
@ -87,7 +76,7 @@ pub(crate) fn run_block_on_vec(
|
|||||||
};
|
};
|
||||||
|
|
||||||
async {
|
async {
|
||||||
match process_row(block, scope, head, context, value).await {
|
match process_row(block, scope, context, value).await {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
// We need to handle this differently depending on whether process_row
|
// We need to handle this differently depending on whether process_row
|
||||||
// returned just 1 value or if it returned multiple as a stream.
|
// returned just 1 value or if it returned multiple as a stream.
|
||||||
|
@ -56,7 +56,6 @@ impl WholeStreamCommand for EachWindow {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(raw_args.call_info.args.head.clone());
|
|
||||||
let scope = raw_args.call_info.scope.clone();
|
let scope = raw_args.call_info.scope.clone();
|
||||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
let context = Arc::new(EvaluationContext::from_raw(&raw_args, ®istry));
|
||||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(®istry).await?;
|
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process(®istry).await?;
|
||||||
@ -82,13 +81,12 @@ impl WholeStreamCommand for EachWindow {
|
|||||||
|
|
||||||
let block = block.clone();
|
let block = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let local_window = window.clone();
|
let local_window = window.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
if i % stride == 0 {
|
if i % stride == 0 {
|
||||||
Some(run_block_on_vec(local_window, block, scope, head, context).await)
|
Some(run_block_on_vec(local_window, block, scope, context).await)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ async fn process_row(
|
|||||||
let for_block = input.clone();
|
let for_block = input.clone();
|
||||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||||
|
|
||||||
let scope = Scope::append_it(scope, input.clone());
|
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||||
|
|
||||||
let mut stream = run_block(
|
let mut stream = run_block(
|
||||||
&default_block,
|
&default_block,
|
||||||
|
192
crates/nu-cli/src/commands/flatten.rs
Normal file
192
crates/nu-cli/src/commands/flatten.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{
|
||||||
|
Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
rest: Vec<Tagged<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"flatten"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Flatten the table."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
flatten(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "flatten a table",
|
||||||
|
example: "echo [[N, u, s, h, e, l, l]] | flatten | first",
|
||||||
|
result: Some(vec![Value::from("N")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "flatten a column having a nested table",
|
||||||
|
example: "echo [[origin, people]; [Ecuador, $(echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal",
|
||||||
|
result: Some(vec![Value::from("arepa")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "restrict the flattening by passing column names",
|
||||||
|
example: "echo [[origin, crate, versions]; [World, $(echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions",
|
||||||
|
result: Some(vec![Value::from("0.22")]),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn flatten(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let registry = registry.clone();
|
||||||
|
let (Arguments { rest: columns }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |item| {
|
||||||
|
futures::stream::iter(flat_value(&columns, &item, &tag).into_iter().flatten())
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TableInside<'a> {
|
||||||
|
Entries(&'a str, &'a Tag, Vec<&'a Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_value(
|
||||||
|
columns: &[Tagged<String>],
|
||||||
|
item: &Value,
|
||||||
|
name_tag: impl Into<Tag>,
|
||||||
|
) -> Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError> {
|
||||||
|
let tag = item.tag.clone();
|
||||||
|
let name_tag = name_tag.into();
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
if item.is_row() {
|
||||||
|
let mut out = TaggedDictBuilder::new(tag);
|
||||||
|
let mut a_table = None;
|
||||||
|
let mut tables_explicitly_flattened = 0;
|
||||||
|
|
||||||
|
for (column, value) in item.row_entries() {
|
||||||
|
let column_requested = columns.iter().find(|c| c.item == *column);
|
||||||
|
|
||||||
|
if let Value {
|
||||||
|
value: UntaggedValue::Row(Dictionary { entries: mapa }),
|
||||||
|
..
|
||||||
|
} = value
|
||||||
|
{
|
||||||
|
if column_requested.is_none() && !columns.is_empty() {
|
||||||
|
if out.contains_key(&column) {
|
||||||
|
out.insert_value(format!("{}_{}", column, column), value.clone());
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in mapa.into_iter() {
|
||||||
|
if out.contains_key(k) {
|
||||||
|
out.insert_value(format!("{}_{}", column, k), v.clone());
|
||||||
|
} else {
|
||||||
|
out.insert_value(k, v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if value.is_table() {
|
||||||
|
if tables_explicitly_flattened >= 1 && column_requested.is_some() {
|
||||||
|
let attempted = if let Some(name) = column_requested {
|
||||||
|
name.span()
|
||||||
|
} else {
|
||||||
|
name_tag.span
|
||||||
|
};
|
||||||
|
|
||||||
|
let already_flattened =
|
||||||
|
if let Some(TableInside::Entries(_, column_tag, _)) = a_table {
|
||||||
|
column_tag.span
|
||||||
|
} else {
|
||||||
|
name_tag.span
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(vec![ReturnSuccess::value(
|
||||||
|
UntaggedValue::Error(ShellError::labeled_error_with_secondary(
|
||||||
|
"can only flatten one inner table at the same time",
|
||||||
|
"tried flattening more than one column with inner tables",
|
||||||
|
attempted,
|
||||||
|
"...but is flattened already",
|
||||||
|
already_flattened,
|
||||||
|
))
|
||||||
|
.into_value(name_tag),
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !columns.is_empty() {
|
||||||
|
if let Some(requested) = column_requested {
|
||||||
|
a_table = Some(TableInside::Entries(
|
||||||
|
&requested.item,
|
||||||
|
&requested.tag,
|
||||||
|
value.table_entries().collect(),
|
||||||
|
));
|
||||||
|
|
||||||
|
tables_explicitly_flattened += 1;
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
} else if a_table.is_none() {
|
||||||
|
a_table = Some(TableInside::Entries(
|
||||||
|
&column,
|
||||||
|
&value.tag,
|
||||||
|
value.table_entries().collect(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.insert_value(column, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut expanded = vec![];
|
||||||
|
|
||||||
|
if let Some(TableInside::Entries(column, _, entries)) = a_table {
|
||||||
|
for entry in entries.into_iter() {
|
||||||
|
let mut base = out.clone();
|
||||||
|
base.insert_value(column, entry.clone());
|
||||||
|
expanded.push(base.into_value());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expanded.push(out.into_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded
|
||||||
|
} else if item.is_table() {
|
||||||
|
item.table_entries().map(Clone::clone).collect()
|
||||||
|
} else {
|
||||||
|
vec![item.clone()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res.into_iter().map(ReturnSuccess::value).collect())
|
||||||
|
}
|
@ -83,7 +83,7 @@ async fn format_command(
|
|||||||
let result = evaluate_baseline_expr(
|
let result = evaluate_baseline_expr(
|
||||||
&full_column_path.0,
|
&full_column_path.0,
|
||||||
®istry,
|
®istry,
|
||||||
Scope::append_it(scope.clone(), value.clone()),
|
Scope::append_var(scope.clone(), "$it", value.clone()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
194
crates/nu-cli/src/commands/format/format_filesize.rs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use nu_protocol::{
|
||||||
|
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
|
||||||
|
UntaggedValue::Primitive, Value,
|
||||||
|
};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use nu_value_ext::get_data_by_column_path;
|
||||||
|
|
||||||
|
use num_format::{Locale, ToFormattedString};
|
||||||
|
|
||||||
|
pub struct FileSize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
field: ColumnPath,
|
||||||
|
format: Tagged<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for FileSize {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"format filesize"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("format filesize")
|
||||||
|
.required(
|
||||||
|
"field",
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"the name of the column to update",
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"format value",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the format into which convert the filesizes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Converts a column of filesizes to some specified format"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
filesize(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Convert the size row to KB",
|
||||||
|
example: "ls | format filesize size KB",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert the apparent row to B",
|
||||||
|
example: "du | format filesize apparent B",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_row(
|
||||||
|
input: Value,
|
||||||
|
format: Tagged<String>,
|
||||||
|
field: Arc<ColumnPath>,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
Ok({
|
||||||
|
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||||
|
match replace_for {
|
||||||
|
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
|
||||||
|
Ok(b) => OutputStream::one(ReturnSuccess::value(
|
||||||
|
input.replace_data_at_column_path(&field, b).expect("Given that the existance check was already done, this souldn't trigger never"),
|
||||||
|
)),
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
},
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filesize(
|
||||||
|
raw_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let (Arguments { field, format }, input) = raw_args.process(®istry).await?;
|
||||||
|
let field = Arc::new(field);
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.then(move |input| {
|
||||||
|
let format = format.clone();
|
||||||
|
let field = field.clone();
|
||||||
|
|
||||||
|
async {
|
||||||
|
match process_row(input, format, field).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => OutputStream::one(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_bytes_to_string_using_format(
|
||||||
|
bytes: Value,
|
||||||
|
format: Tagged<String>,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
match bytes.value {
|
||||||
|
Primitive(Filesize(b)) => {
|
||||||
|
let byte = byte_unit::Byte::from_bytes(b as u128);
|
||||||
|
let value = match format.item().to_lowercase().as_str() {
|
||||||
|
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
|
||||||
|
"kb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
|
||||||
|
)),
|
||||||
|
"kib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
|
||||||
|
)),
|
||||||
|
"mb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
|
||||||
|
)),
|
||||||
|
"mib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
|
||||||
|
)),
|
||||||
|
"gb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
|
||||||
|
)),
|
||||||
|
"gib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
|
||||||
|
)),
|
||||||
|
"tb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
|
||||||
|
)),
|
||||||
|
"tib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
|
||||||
|
)),
|
||||||
|
"pb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
|
||||||
|
)),
|
||||||
|
"pib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
|
||||||
|
)),
|
||||||
|
"eb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
|
||||||
|
)),
|
||||||
|
"eib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
|
||||||
|
)),
|
||||||
|
"zb" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
|
||||||
|
)),
|
||||||
|
"zib" => Ok(UntaggedValue::string(
|
||||||
|
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
|
||||||
|
)),
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
format!("Invalid format code: {:}", format.item()),
|
||||||
|
"invalid format",
|
||||||
|
format.tag(),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
Ok(b) => Ok(Value { value: b, ..bytes }),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::labeled_error(
|
||||||
|
"the data in this row is not of the type filesize",
|
||||||
|
"invalid row type",
|
||||||
|
bytes.tag(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::FileSize;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(FileSize {})?)
|
||||||
|
}
|
||||||
|
}
|
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
5
crates/nu-cli/src/commands/format/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod command;
|
||||||
|
pub mod format_filesize;
|
||||||
|
|
||||||
|
pub use command::Format;
|
||||||
|
pub use format_filesize::FileSize;
|
@ -54,6 +54,7 @@ pub async fn from_delimited_data(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name_tag = name;
|
let name_tag = name;
|
||||||
let concat_string = input.collect_string(name_tag.clone()).await?;
|
let concat_string = input.collect_string(name_tag.clone()).await?;
|
||||||
|
let sample_lines = concat_string.item.lines().take(3).collect_vec().join("\n");
|
||||||
|
|
||||||
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
|
||||||
Ok(x) => match x {
|
Ok(x) => match x {
|
||||||
@ -65,10 +66,16 @@ pub async fn from_delimited_data(
|
|||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let line_one = match pretty_csv_error(err) {
|
let line_one = match pretty_csv_error(err) {
|
||||||
Some(pretty) => format!("Could not parse as {} ({})", format_name, pretty),
|
Some(pretty) => format!(
|
||||||
None => format!("Could not parse as {}", format_name),
|
"Could not parse as {} split by '{}' ({})",
|
||||||
|
format_name, sep, pretty
|
||||||
|
),
|
||||||
|
None => format!("Could not parse as {} split by '{}'", format_name, sep),
|
||||||
};
|
};
|
||||||
let line_two = format!("input cannot be parsed as {}", format_name);
|
let line_two = format!(
|
||||||
|
"input cannot be parsed as {} split by '{}'. Input's first lines:\n{}",
|
||||||
|
format_name, sep, sample_lines
|
||||||
|
);
|
||||||
|
|
||||||
Err(ShellError::labeled_error_with_secondary(
|
Err(ShellError::labeled_error_with_secondary(
|
||||||
line_one,
|
line_one,
|
||||||
|
@ -37,26 +37,26 @@ impl WholeStreamCommand for FromJSON {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
fn convert_json_value_to_nu_value(v: &nu_json::Value, tag: impl Into<Tag>) -> Value {
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
let span = tag.span;
|
let span = tag.span;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
nu_json::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||||
serde_hjson::Value::String(s) => {
|
nu_json::Value::String(s) => {
|
||||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
||||||
}
|
}
|
||||||
serde_hjson::Value::Array(a) => UntaggedValue::Table(
|
nu_json::Value::Array(a) => UntaggedValue::Table(
|
||||||
a.iter()
|
a.iter()
|
||||||
.map(|x| convert_json_value_to_nu_value(x, &tag))
|
.map(|x| convert_json_value_to_nu_value(x, &tag))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.into_value(tag),
|
.into_value(tag),
|
||||||
serde_hjson::Value::Object(o) => {
|
nu_json::Value::Object(o) => {
|
||||||
let mut collected = TaggedDictBuilder::new(&tag);
|
let mut collected = TaggedDictBuilder::new(&tag);
|
||||||
for (k, v) in o.iter() {
|
for (k, v) in o.iter() {
|
||||||
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
|
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
|
||||||
@ -67,8 +67,8 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson::Result<Value> {
|
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
|
||||||
let v: serde_hjson::Value = serde_hjson::from_str(&s)?;
|
let v: nu_json::Value = nu_json::from_str(&s)?;
|
||||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ async fn from_json(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut message = "Could not parse as JSON (".to_string();
|
let mut message = "Could not parse as JSON (".to_string();
|
||||||
message.push_str(&e.to_string());
|
message.push_str(&e.to_string());
|
||||||
message.push_str(")");
|
message.push(')');
|
||||||
|
|
||||||
Some(Err(ShellError::labeled_error_with_secondary(
|
Some(Err(ShellError::labeled_error_with_secondary(
|
||||||
message,
|
message,
|
||||||
@ -125,7 +125,7 @@ async fn from_json(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut message = "Could not parse as JSON (".to_string();
|
let mut message = "Could not parse as JSON (".to_string();
|
||||||
message.push_str(&e.to_string());
|
message.push_str(&e.to_string());
|
||||||
message.push_str(")");
|
message.push(')');
|
||||||
|
|
||||||
Ok(OutputStream::one(Err(
|
Ok(OutputStream::one(Err(
|
||||||
ShellError::labeled_error_with_secondary(
|
ShellError::labeled_error_with_secondary(
|
||||||
|
@ -68,13 +68,12 @@ fn convert_yaml_value_to_nu_value(
|
|||||||
Ok(match v {
|
Ok(match v {
|
||||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
UntaggedValue::int(n.as_i64().ok_or(err_not_compatible_number)?).into_value(tag)
|
||||||
|
}
|
||||||
|
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||||
|
UntaggedValue::decimal_from_float(n.as_f64().ok_or(err_not_compatible_number)?, span)
|
||||||
|
.into_value(tag)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
|
||||||
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into_value(tag),
|
|
||||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||||
serde_yaml::Value::Sequence(a) => {
|
serde_yaml::Value::Sequence(a) => {
|
||||||
let result: Result<Vec<Value>, ShellError> = a
|
let result: Result<Vec<Value>, ShellError> = a
|
||||||
@ -175,8 +174,8 @@ async fn from_yaml(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::*;
|
use super::*;
|
||||||
use nu_plugin::row;
|
use nu_protocol::row;
|
||||||
use nu_plugin::test_helpers::value::string;
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -51,30 +51,30 @@ impl WholeStreamCommand for Command {
|
|||||||
result: Some(vec![UntaggedValue::row(indexmap! {
|
result: Some(vec![UntaggedValue::row(indexmap! {
|
||||||
"File".to_string() => UntaggedValue::Table(vec![
|
"File".to_string() => UntaggedValue::Table(vec![
|
||||||
UntaggedValue::row(indexmap! {
|
UntaggedValue::row(indexmap! {
|
||||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
|
||||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||||
"type".to_string() => UntaggedValue::string("File").into(),
|
"type".to_string() => UntaggedValue::string("File").into(),
|
||||||
"chickens".to_string() => UntaggedValue::int(10).into(),
|
"chickens".to_string() => UntaggedValue::int(10).into(),
|
||||||
|
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||||
}).into(),
|
}).into(),
|
||||||
UntaggedValue::row(indexmap! {
|
UntaggedValue::row(indexmap! {
|
||||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
|
||||||
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
"name".to_string() => UntaggedValue::string("Andrés.txt").into(),
|
||||||
"type".to_string() => UntaggedValue::string("File").into(),
|
"type".to_string() => UntaggedValue::string("File").into(),
|
||||||
"chickens".to_string() => UntaggedValue::int(20).into(),
|
"chickens".to_string() => UntaggedValue::int(20).into(),
|
||||||
|
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||||
}).into(),
|
}).into(),
|
||||||
]).into(),
|
]).into(),
|
||||||
"Dir".to_string() => UntaggedValue::Table(vec![
|
"Dir".to_string() => UntaggedValue::Table(vec![
|
||||||
UntaggedValue::row(indexmap! {
|
UntaggedValue::row(indexmap! {
|
||||||
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
|
||||||
"name".to_string() => UntaggedValue::string("Jonathan").into(),
|
"name".to_string() => UntaggedValue::string("Jonathan").into(),
|
||||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||||
"chickens".to_string() => UntaggedValue::int(5).into(),
|
"chickens".to_string() => UntaggedValue::int(5).into(),
|
||||||
|
"modified".to_string() => date("2019-07-23".tagged_unknown()).unwrap().into(),
|
||||||
}).into(),
|
}).into(),
|
||||||
UntaggedValue::row(indexmap! {
|
UntaggedValue::row(indexmap! {
|
||||||
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
|
||||||
"name".to_string() => UntaggedValue::string("Yehuda").into(),
|
"name".to_string() => UntaggedValue::string("Yehuda").into(),
|
||||||
"type".to_string() => UntaggedValue::string("Dir").into(),
|
"type".to_string() => UntaggedValue::string("Dir").into(),
|
||||||
"chickens".to_string() => UntaggedValue::int(4).into(),
|
"chickens".to_string() => UntaggedValue::int(4).into(),
|
||||||
|
"modified".to_string() => date("2019-09-24".tagged_unknown()).unwrap().into(),
|
||||||
}).into(),
|
}).into(),
|
||||||
]).into(),
|
]).into(),
|
||||||
})
|
})
|
||||||
@ -139,7 +139,6 @@ pub async fn group_by(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let head = Arc::new(args.call_info.args.head.clone());
|
|
||||||
let scope = args.call_info.scope.clone();
|
let scope = args.call_info.scope.clone();
|
||||||
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
let context = Arc::new(EvaluationContext::from_raw(&args, ®istry));
|
||||||
let (Arguments { grouper }, input) = args.process(®istry).await?;
|
let (Arguments { grouper }, input) = args.process(®istry).await?;
|
||||||
@ -159,12 +158,9 @@ pub async fn group_by(
|
|||||||
for value in values.iter() {
|
for value in values.iter() {
|
||||||
let run = block.clone();
|
let run = block.clone();
|
||||||
let scope = scope.clone();
|
let scope = scope.clone();
|
||||||
let head = head.clone();
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
match crate::commands::each::process_row(run, scope, head, context, value.clone())
|
match crate::commands::each::process_row(run, scope, context, value.clone()).await {
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(mut s) => {
|
Ok(mut s) => {
|
||||||
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
let collection: Vec<Result<ReturnSuccess, ShellError>> =
|
||||||
s.drain_vec().await;
|
s.drain_vec().await;
|
||||||
@ -278,9 +274,10 @@ pub fn group(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::group;
|
use super::group;
|
||||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
use nu_data::utils::helpers::committers;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_source::*;
|
use nu_source::*;
|
||||||
|
use nu_test_support::value::{date, int, row, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
fn groups_table_by_date_column() -> Result<(), ShellError> {
|
||||||
|
314
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
314
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::ShellTypeName;
|
||||||
|
use nu_protocol::{
|
||||||
|
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::{Tag, Tagged};
|
||||||
|
|
||||||
|
use base64::{decode_config, encode_config};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
pub rest: Vec<ColumnPath>,
|
||||||
|
pub character_set: Option<Tagged<String>>,
|
||||||
|
pub encode: Tagged<bool>,
|
||||||
|
pub decode: Tagged<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Base64Config {
|
||||||
|
pub character_set: String,
|
||||||
|
pub action_type: ActionType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum ActionType {
|
||||||
|
Encode,
|
||||||
|
Decode,
|
||||||
|
}
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hash base64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("hash base64")
|
||||||
|
.named(
|
||||||
|
"character_set",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"specify the character rules for encoding the input.\n\
|
||||||
|
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
||||||
|
'binhex', 'bcrypt', 'crypt'",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"encode",
|
||||||
|
"encode the input as base64. This is the default behavior if not specified.",
|
||||||
|
Some('e')
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"decode",
|
||||||
|
"decode the input from base64",
|
||||||
|
Some('d'))
|
||||||
|
.rest(
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"optionally base64 encode / decode data by column paths",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"base64 encode or decode a value"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
operate(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Base64 encode a string with default settings",
|
||||||
|
example: "echo 'username:password' | hash base64",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value()
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Base64 encode a string with the binhex character set",
|
||||||
|
example: "echo 'username:password' | hash base64 --character_set binhex --encode",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("F@0NEPjJD97kE'&bEhFZEP3").into_untagged_value()
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Base64 decode a value",
|
||||||
|
example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("username:password").into_untagged_value()
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn operate(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
|
||||||
|
let name_tag = &args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let (
|
||||||
|
Arguments {
|
||||||
|
encode,
|
||||||
|
decode,
|
||||||
|
character_set,
|
||||||
|
rest,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
if encode.item && decode.item {
|
||||||
|
return Ok(OutputStream::one(Err(ShellError::labeled_error(
|
||||||
|
"only one of --decode and --encode flags can be used",
|
||||||
|
"conflicting flags",
|
||||||
|
name_tag,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default the action to be encoding if no flags are specified.
|
||||||
|
let action_type = if *decode.item() {
|
||||||
|
ActionType::Decode
|
||||||
|
} else {
|
||||||
|
ActionType::Encode
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default the character set to standard if the argument is not specified.
|
||||||
|
let character_set = match character_set {
|
||||||
|
Some(inner_tag) => inner_tag.item().to_string(),
|
||||||
|
None => "standard".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoding_config = Base64Config {
|
||||||
|
character_set,
|
||||||
|
action_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
let column_paths: Vec<_> = rest;
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |v| {
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
ReturnSuccess::value(action(&v, &encoding_config, v.tag())?)
|
||||||
|
} else {
|
||||||
|
let mut ret = v;
|
||||||
|
|
||||||
|
for path in &column_paths {
|
||||||
|
let config = encoding_config.clone();
|
||||||
|
ret = ret.swap_data_by_column_path(
|
||||||
|
path,
|
||||||
|
Box::new(move |old| action(old, &config, old.tag())),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnSuccess::value(ret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(
|
||||||
|
input: &Value,
|
||||||
|
base64_config: &Base64Config,
|
||||||
|
tag: impl Into<Tag>,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
match &input.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::Line(s))
|
||||||
|
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||||
|
let base64_config_enum: base64::Config = if &base64_config.character_set == "standard" {
|
||||||
|
base64::STANDARD
|
||||||
|
} else if &base64_config.character_set == "standard-no-padding" {
|
||||||
|
base64::STANDARD_NO_PAD
|
||||||
|
} else if &base64_config.character_set == "url-safe" {
|
||||||
|
base64::URL_SAFE
|
||||||
|
} else if &base64_config.character_set == "url-safe-no-padding" {
|
||||||
|
base64::URL_SAFE_NO_PAD
|
||||||
|
} else if &base64_config.character_set == "binhex" {
|
||||||
|
base64::BINHEX
|
||||||
|
} else if &base64_config.character_set == "bcrypt" {
|
||||||
|
base64::BCRYPT
|
||||||
|
} else if &base64_config.character_set == "crypt" {
|
||||||
|
base64::CRYPT
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"value is not an accepted character set",
|
||||||
|
format!(
|
||||||
|
"{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.",
|
||||||
|
&base64_config.character_set
|
||||||
|
),
|
||||||
|
tag.into().span,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
match base64_config.action_type {
|
||||||
|
ActionType::Encode => Ok(UntaggedValue::string(encode_config(
|
||||||
|
&s,
|
||||||
|
base64_config_enum,
|
||||||
|
))
|
||||||
|
.into_value(tag)),
|
||||||
|
ActionType::Decode => {
|
||||||
|
let decode_result = decode_config(&s, base64_config_enum);
|
||||||
|
|
||||||
|
match decode_result {
|
||||||
|
Ok(decoded_value) => Ok(UntaggedValue::string(
|
||||||
|
std::string::String::from_utf8_lossy(&decoded_value),
|
||||||
|
)
|
||||||
|
.into_value(tag)),
|
||||||
|
Err(_) => Err(ShellError::labeled_error(
|
||||||
|
"value could not be base64 decoded",
|
||||||
|
format!(
|
||||||
|
"invalid base64 input for character set {}",
|
||||||
|
&base64_config.character_set
|
||||||
|
),
|
||||||
|
tag.into().span,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
let got = format!("got {}", other.type_name());
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"value is not string",
|
||||||
|
got,
|
||||||
|
tag.into().span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{action, ActionType, Base64Config};
|
||||||
|
use nu_protocol::UntaggedValue;
|
||||||
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_encode_standard() {
|
||||||
|
let word = string("username:password");
|
||||||
|
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value();
|
||||||
|
|
||||||
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Base64Config {
|
||||||
|
character_set: "standard".to_string(),
|
||||||
|
action_type: ActionType::Encode,
|
||||||
|
},
|
||||||
|
Tag::unknown(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_encode_standard_no_padding() {
|
||||||
|
let word = string("username:password");
|
||||||
|
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ").into_untagged_value();
|
||||||
|
|
||||||
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Base64Config {
|
||||||
|
character_set: "standard-no-padding".to_string(),
|
||||||
|
action_type: ActionType::Encode,
|
||||||
|
},
|
||||||
|
Tag::unknown(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_encode_url_safe() {
|
||||||
|
let word = string("this is for url");
|
||||||
|
let expected = UntaggedValue::string("dGhpcyBpcyBmb3IgdXJs").into_untagged_value();
|
||||||
|
|
||||||
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Base64Config {
|
||||||
|
character_set: "url-safe".to_string(),
|
||||||
|
action_type: ActionType::Encode,
|
||||||
|
},
|
||||||
|
Tag::unknown(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base64_decode_binhex() {
|
||||||
|
let word = string("A5\"KC9jRB@IIF'8bF!");
|
||||||
|
let expected = UntaggedValue::string("a binhex test").into_untagged_value();
|
||||||
|
|
||||||
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Base64Config {
|
||||||
|
character_set: "binhex".to_string(),
|
||||||
|
action_type: ActionType::Decode,
|
||||||
|
},
|
||||||
|
Tag::unknown(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
50
crates/nu-cli/src/commands/hash_/command.rs
Normal file
50
crates/nu-cli/src/commands/hash_/command.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Command;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hash"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("hash").rest(
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"optionally convert by column paths",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Apply hash function."
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||||
|
.into_value(Tag::unknown()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Command;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Command {})?)
|
||||||
|
}
|
||||||
|
}
|
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod base64_;
|
||||||
|
mod command;
|
||||||
|
|
||||||
|
pub use base64_::SubCommand as HashBase64;
|
||||||
|
pub use command::Command as Hash;
|
@ -27,6 +27,11 @@ pub fn history_path(config: &dyn Conf) -> PathBuf {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Arguments {
|
||||||
|
clear: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct History;
|
pub struct History;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -36,7 +41,7 @@ impl WholeStreamCommand for History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("history")
|
Signature::build("history").switch("clear", "Clears out the history entries", Some('c'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -48,31 +53,45 @@ impl WholeStreamCommand for History {
|
|||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
history(args, registry)
|
history(args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn history(
|
||||||
|
args: CommandArgs,
|
||||||
|
_registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||||
let tag = args.call_info.name_tag;
|
let tag = args.call_info.name_tag.clone();
|
||||||
let path = history_path(&config);
|
let (Arguments { clear }, _) = args.process(&_registry).await?;
|
||||||
let file = File::open(path);
|
|
||||||
if let Ok(file) = file {
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
let output = reader.lines().filter_map(move |line| match line {
|
|
||||||
Ok(line) => Some(ReturnSuccess::value(
|
|
||||||
UntaggedValue::string(line).into_value(tag.clone()),
|
|
||||||
)),
|
|
||||||
Err(_) => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(futures::stream::iter(output).to_output_stream())
|
let path = history_path(&config);
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
match clear {
|
||||||
"Could not open history",
|
Some(_) => {
|
||||||
"history file could not be opened",
|
// This is a NOOP, the logic to clear is handled in cli.rs
|
||||||
tag,
|
Ok(OutputStream::empty())
|
||||||
))
|
}
|
||||||
|
None => {
|
||||||
|
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(futures::stream::iter(output).to_output_stream())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"Could not open history",
|
||||||
|
"history file could not be opened",
|
||||||
|
tag,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ async fn if_command(
|
|||||||
let then_case = then_case.clone();
|
let then_case = then_case.clone();
|
||||||
let else_case = else_case.clone();
|
let else_case = else_case.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Scope::append_it(scope.clone(), input);
|
let scope = Scope::append_var(scope.clone(), "$it", input);
|
||||||
let mut context = context.clone();
|
let mut context = context.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@ -87,7 +87,7 @@ async fn process_row(
|
|||||||
let for_block = input.clone();
|
let for_block = input.clone();
|
||||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||||
|
|
||||||
let scope = Scope::append_it(scope, input.clone());
|
let scope = Scope::append_var(scope, "$it", input.clone());
|
||||||
|
|
||||||
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
let result = run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await;
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ async fn process_row(
|
|||||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||||
..
|
..
|
||||||
} => match scope
|
} => match scope
|
||||||
.it()
|
.var("$it")
|
||||||
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
|
.unwrap_or_else(|| UntaggedValue::nothing().into_untagged_value())
|
||||||
.insert_data_at_column_path(&field, value.clone())
|
.insert_data_at_column_path(&field, value.clone())
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ impl WholeStreamCommand for IntoInt {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Convert filesize to integer",
|
description: "Convert filesize to integer",
|
||||||
example: "echo 1kb | into-int $it | = $it / 1024",
|
example: "into-int 1kb | each { = $it / 1024 }",
|
||||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
.take_while(move |item| {
|
.take_while(move |item| {
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
.take_while(move |item| {
|
.take_while(move |item| {
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
73
crates/nu-cli/src/commands/math/abs.rs
Normal file
73
crates/nu-cli/src/commands/math/abs.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"math abs"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math abs")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Returns absolute values of a list of numbers"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
_: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let mapped = args.input.map(move |val| match val.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::Int(val)) => {
|
||||||
|
UntaggedValue::int(val.magnitude().clone()).into()
|
||||||
|
}
|
||||||
|
UntaggedValue::Primitive(Primitive::Decimal(val)) => {
|
||||||
|
UntaggedValue::decimal(val.abs()).into()
|
||||||
|
}
|
||||||
|
UntaggedValue::Primitive(Primitive::Duration(val)) => {
|
||||||
|
UntaggedValue::duration(val.magnitude().clone()).into()
|
||||||
|
}
|
||||||
|
other => abs_default(other),
|
||||||
|
});
|
||||||
|
Ok(OutputStream::from_input(mapped))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Get absolute of each value in a list of numbers",
|
||||||
|
example: "echo [-50 -100.0 25] | math abs",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(50).into(),
|
||||||
|
UntaggedValue::decimal_from_float(100.0, Span::default()).into(),
|
||||||
|
UntaggedValue::int(25).into(),
|
||||||
|
]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abs_default(_: UntaggedValue) -> Value {
|
||||||
|
UntaggedValue::Error(ShellError::unexpected(
|
||||||
|
"Only numerical values are supported",
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
91
crates/nu-cli/src/commands/math/ceil.rs
Normal file
91
crates/nu-cli/src/commands/math/ceil.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use bigdecimal::One;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"math ceil"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math celi")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Applies the ceil function to a list of numbers"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
run_with_numerical_functions_on_stream(
|
||||||
|
RunnableContext {
|
||||||
|
input: args.input,
|
||||||
|
registry: registry.clone(),
|
||||||
|
shell_manager: args.shell_manager,
|
||||||
|
host: args.host,
|
||||||
|
ctrl_c: args.ctrl_c,
|
||||||
|
current_errors: args.current_errors,
|
||||||
|
name: args.call_info.name_tag,
|
||||||
|
raw_input: args.raw_input,
|
||||||
|
},
|
||||||
|
ceil_big_int,
|
||||||
|
ceil_big_decimal,
|
||||||
|
ceil_default,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Apply the ceil function to a list of numbers",
|
||||||
|
example: "echo [1.5 2.3 -3.1] | math ceil",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(2).into(),
|
||||||
|
UntaggedValue::int(3).into(),
|
||||||
|
UntaggedValue::int(-3).into(),
|
||||||
|
]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ceil_big_int(val: BigInt) -> Value {
|
||||||
|
UntaggedValue::int(val).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ceil_big_decimal(val: BigDecimal) -> Value {
|
||||||
|
let mut maybe_ceiled = val.round(0);
|
||||||
|
if maybe_ceiled < val {
|
||||||
|
maybe_ceiled += BigDecimal::one();
|
||||||
|
}
|
||||||
|
let (ceiled, _) = maybe_ceiled.into_bigint_and_exponent();
|
||||||
|
UntaggedValue::int(ceiled).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ceil_default(_: UntaggedValue) -> Value {
|
||||||
|
UntaggedValue::Error(ShellError::unexpected(
|
||||||
|
"Only numerical values are supported",
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -38,9 +38,8 @@ mod tests {
|
|||||||
avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
|
avg::average, max::maximum, median::median, min::minimum, mode::mode, stddev::stddev,
|
||||||
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
sum::summation, utils::calculate, utils::MathFunction, variance::variance,
|
||||||
};
|
};
|
||||||
use nu_plugin::row;
|
use nu_protocol::{row, Value};
|
||||||
use nu_plugin::test_helpers::value::{decimal, decimal_from_float, int, table};
|
use nu_test_support::value::{decimal, decimal_from_float, int, table};
|
||||||
use nu_protocol::Value;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -71,14 +70,14 @@ mod tests {
|
|||||||
values: vec![int(10)],
|
values: vec![int(10)],
|
||||||
expected_err: None,
|
expected_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(10)),
|
Ok(decimal_from_float(10.0)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(table(&[int(10)])),
|
Ok(table(&[int(10)])),
|
||||||
Ok(decimal(0)),
|
Ok(decimal_from_float(0.0)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(decimal(0)),
|
Ok(decimal_from_float(0.0)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
@ -86,7 +85,7 @@ mod tests {
|
|||||||
values: vec![int(10), int(20), int(30)],
|
values: vec![int(10), int(20), int(30)],
|
||||||
expected_err: None,
|
expected_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(20)),
|
Ok(decimal_from_float(20.0)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(int(30)),
|
Ok(int(30)),
|
||||||
Ok(int(20)),
|
Ok(int(20)),
|
||||||
@ -101,13 +100,13 @@ mod tests {
|
|||||||
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
|
values: vec![int(10), decimal_from_float(26.5), decimal_from_float(26.5)],
|
||||||
expected_err: None,
|
expected_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(21)),
|
Ok(decimal_from_float(21.0)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(decimal_from_float(26.5)),
|
Ok(decimal_from_float(26.5)),
|
||||||
Ok(decimal_from_float(26.5)),
|
Ok(decimal_from_float(26.5)),
|
||||||
Ok(table(&[decimal_from_float(26.5)])),
|
Ok(table(&[decimal_from_float(26.5)])),
|
||||||
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
Ok(decimal(BigDecimal::from_str("7.77817459305202276840928798315333943213319531457321440247173855894902863154158871367713143880202865").expect("Could not convert to decimal from string"))),
|
||||||
Ok(decimal(63)),
|
Ok(decimal_from_float(63.0)),
|
||||||
Ok(decimal_from_float(60.5)),
|
Ok(decimal_from_float(60.5)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -116,14 +115,14 @@ mod tests {
|
|||||||
values: vec![int(-14), int(-11), int(10)],
|
values: vec![int(-14), int(-11), int(10)],
|
||||||
expected_err: None,
|
expected_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(-5)),
|
Ok(decimal_from_float(-5.0)),
|
||||||
Ok(int(-14)),
|
Ok(int(-14)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(int(-11)),
|
Ok(int(-11)),
|
||||||
Ok(table(&[int(-14), int(-11), int(10)])),
|
Ok(table(&[int(-14), int(-11), int(10)])),
|
||||||
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
|
Ok(decimal(BigDecimal::from_str("10.67707825203131121081152396559571062628228776946058011397810604284900898365140801704064843595778374").expect("Could not convert to decimal from string"))),
|
||||||
Ok(int(-15)),
|
Ok(int(-15)),
|
||||||
Ok(decimal(114)),
|
Ok(decimal_from_float(114.0)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
TestCase {
|
TestCase {
|
||||||
@ -131,13 +130,13 @@ mod tests {
|
|||||||
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
|
values: vec![decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)],
|
||||||
expected_err: None,
|
expected_err: None,
|
||||||
expected_res: vec![
|
expected_res: vec![
|
||||||
Ok(decimal(-5)),
|
Ok(decimal_from_float(-5.0)),
|
||||||
Ok(decimal_from_float(-13.5)),
|
Ok(decimal_from_float(-13.5)),
|
||||||
Ok(int(10)),
|
Ok(int(10)),
|
||||||
Ok(decimal_from_float(-11.5)),
|
Ok(decimal_from_float(-11.5)),
|
||||||
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
|
Ok(table(&[decimal_from_float(-13.5), decimal_from_float(-11.5), int(10)])),
|
||||||
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
Ok(decimal(BigDecimal::from_str("10.63798226482196513098036125801342585449179971588207816421068645273754903468375890632981926875247027").expect("Could not convert to decimal from string"))),
|
||||||
Ok(decimal(-15)),
|
Ok(decimal_from_float(-15.0)),
|
||||||
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
Ok(decimal(BigDecimal::from_str("113.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667").expect("Could not convert to decimal from string"))),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -91,7 +91,9 @@ pub async fn eval(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
|
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
|
||||||
match meval::eval_str(math_expression) {
|
let mut ctx = meval::Context::new();
|
||||||
|
ctx.var("tau", std::f64::consts::TAU);
|
||||||
|
match meval::eval_str_with_context(math_expression, &ctx) {
|
||||||
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
||||||
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
|
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
|
||||||
Err(error) => Err(error.to_string().to_lowercase()),
|
Err(error) => Err(error.to_string().to_lowercase()),
|
||||||
|
91
crates/nu-cli/src/commands/math/floor.rs
Normal file
91
crates/nu-cli/src/commands/math/floor.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use crate::commands::math::utils::run_with_numerical_functions_on_stream;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use bigdecimal::One;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"math floor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math floor")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Applies the floor function to a list of numbers"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
run_with_numerical_functions_on_stream(
|
||||||
|
RunnableContext {
|
||||||
|
input: args.input,
|
||||||
|
registry: registry.clone(),
|
||||||
|
shell_manager: args.shell_manager,
|
||||||
|
host: args.host,
|
||||||
|
ctrl_c: args.ctrl_c,
|
||||||
|
current_errors: args.current_errors,
|
||||||
|
name: args.call_info.name_tag,
|
||||||
|
raw_input: args.raw_input,
|
||||||
|
},
|
||||||
|
floor_big_int,
|
||||||
|
floor_big_decimal,
|
||||||
|
floor_default,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Apply the floor function to a list of numbers",
|
||||||
|
example: "echo [1.5 2.3 -3.1] | math floor",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(1).into(),
|
||||||
|
UntaggedValue::int(2).into(),
|
||||||
|
UntaggedValue::int(-4).into(),
|
||||||
|
]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floor_big_int(val: BigInt) -> Value {
|
||||||
|
UntaggedValue::int(val).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floor_big_decimal(val: BigDecimal) -> Value {
|
||||||
|
let mut maybe_floored = val.round(0);
|
||||||
|
if maybe_floored > val {
|
||||||
|
maybe_floored -= BigDecimal::one();
|
||||||
|
}
|
||||||
|
let (floored, _) = maybe_floored.into_bigint_and_exponent();
|
||||||
|
UntaggedValue::int(floored).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floor_default(_: UntaggedValue) -> Value {
|
||||||
|
UntaggedValue::Error(ShellError::unexpected(
|
||||||
|
"Only numerical values are supported",
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
|
pub mod abs;
|
||||||
pub mod avg;
|
pub mod avg;
|
||||||
|
pub mod ceil;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
pub mod floor;
|
||||||
pub mod max;
|
pub mod max;
|
||||||
pub mod median;
|
pub mod median;
|
||||||
pub mod min;
|
pub mod min;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod product;
|
pub mod product;
|
||||||
|
pub mod round;
|
||||||
pub mod stddev;
|
pub mod stddev;
|
||||||
pub mod sum;
|
pub mod sum;
|
||||||
pub mod variance;
|
pub mod variance;
|
||||||
@ -13,14 +17,18 @@ pub mod variance;
|
|||||||
mod reducers;
|
mod reducers;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
pub use abs::SubCommand as MathAbs;
|
||||||
pub use avg::SubCommand as MathAverage;
|
pub use avg::SubCommand as MathAverage;
|
||||||
|
pub use ceil::SubCommand as MathCeil;
|
||||||
pub use command::Command as Math;
|
pub use command::Command as Math;
|
||||||
pub use eval::SubCommand as MathEval;
|
pub use eval::SubCommand as MathEval;
|
||||||
|
pub use floor::SubCommand as MathFloor;
|
||||||
pub use max::SubCommand as MathMaximum;
|
pub use max::SubCommand as MathMaximum;
|
||||||
pub use median::SubCommand as MathMedian;
|
pub use median::SubCommand as MathMedian;
|
||||||
pub use min::SubCommand as MathMinimum;
|
pub use min::SubCommand as MathMinimum;
|
||||||
pub use mode::SubCommand as MathMode;
|
pub use mode::SubCommand as MathMode;
|
||||||
pub use product::SubCommand as MathProduct;
|
pub use product::SubCommand as MathProduct;
|
||||||
|
pub use round::SubCommand as MathRound;
|
||||||
pub use stddev::SubCommand as MathStddev;
|
pub use stddev::SubCommand as MathStddev;
|
||||||
pub use sum::SubCommand as MathSummation;
|
pub use sum::SubCommand as MathSummation;
|
||||||
pub use variance::SubCommand as MathVariance;
|
pub use variance::SubCommand as MathVariance;
|
||||||
|
111
crates/nu-cli/src/commands/math/round.rs
Normal file
111
crates/nu-cli/src/commands/math/round.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Arguments {
|
||||||
|
precision: Option<Tagged<i64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"math round"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math round").named(
|
||||||
|
"precision",
|
||||||
|
SyntaxShape::Number,
|
||||||
|
"digits of precision",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Applies the round function to a list of numbers"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
operate(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Apply the round function to a list of numbers",
|
||||||
|
example: "echo [1.5 2.3 -3.1] | math round",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::int(2).into(),
|
||||||
|
UntaggedValue::int(2).into(),
|
||||||
|
UntaggedValue::int(-3).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Apply the round function with precision specified",
|
||||||
|
example: "echo [1.555 2.333 -3.111] | math round -p 2",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::decimal_from_float(1.56, Span::default()).into(),
|
||||||
|
UntaggedValue::decimal_from_float(2.33, Span::default()).into(),
|
||||||
|
UntaggedValue::decimal_from_float(-3.11, Span::default()).into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn operate(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (Arguments { precision }, input) = args.process(®istry).await?;
|
||||||
|
let precision = precision.map(|p| p.item).unwrap_or(0);
|
||||||
|
|
||||||
|
let mapped = input.map(move |val| match val.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::Int(val)) => round_big_int(val),
|
||||||
|
UntaggedValue::Primitive(Primitive::Decimal(val)) => round_big_decimal(val, precision),
|
||||||
|
other => round_default(other),
|
||||||
|
});
|
||||||
|
Ok(OutputStream::from_input(mapped))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_big_int(val: BigInt) -> Value {
|
||||||
|
UntaggedValue::int(val).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_big_decimal(val: BigDecimal, precision: i64) -> Value {
|
||||||
|
if precision > 0 {
|
||||||
|
UntaggedValue::decimal(val.round(precision)).into()
|
||||||
|
} else {
|
||||||
|
let (rounded, _) = val.round(precision).as_bigint_and_exponent();
|
||||||
|
UntaggedValue::int(rounded).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_default(_: UntaggedValue) -> Value {
|
||||||
|
UntaggedValue::Error(ShellError::unexpected(
|
||||||
|
"Only numerical values are supported",
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||||
|
|
||||||
use indexmap::map::IndexMap;
|
use indexmap::map::IndexMap;
|
||||||
|
|
||||||
@ -31,6 +31,26 @@ pub async fn run_with_function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type IntFunction = fn(val: BigInt) -> Value;
|
||||||
|
|
||||||
|
pub type DecimalFunction = fn(val: BigDecimal) -> Value;
|
||||||
|
|
||||||
|
pub type DefaultFunction = fn(val: UntaggedValue) -> Value;
|
||||||
|
|
||||||
|
pub async fn run_with_numerical_functions_on_stream(
|
||||||
|
RunnableContext { input, .. }: RunnableContext,
|
||||||
|
int_function: IntFunction,
|
||||||
|
decimal_function: DecimalFunction,
|
||||||
|
default_function: DefaultFunction,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let mapped = input.map(move |val| match val.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::Int(val)) => int_function(val),
|
||||||
|
UntaggedValue::Primitive(Primitive::Decimal(val)) => decimal_function(val),
|
||||||
|
other => default_function(other),
|
||||||
|
});
|
||||||
|
Ok(OutputStream::from_input(mapped))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
|
pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result<Value, ShellError> {
|
||||||
if values.iter().all(|v| v.is_primitive()) {
|
if values.iter().all(|v| v.is_primitive()) {
|
||||||
mf(&values, &name)
|
mf(&values, &name)
|
||||||
|
@ -1,328 +0,0 @@
|
|||||||
use crate::command_registry::CommandRegistry;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use nu_data::base::select_fields;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
|
||||||
use nu_source::HasFallibleSpan;
|
|
||||||
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct Arguments {
|
|
||||||
rest: Vec<ColumnPath>,
|
|
||||||
after: Option<ColumnPath>,
|
|
||||||
before: Option<ColumnPath>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"move column"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("move column")
|
|
||||||
.rest(SyntaxShape::ColumnPath, "the columns to move")
|
|
||||||
.named(
|
|
||||||
"after",
|
|
||||||
SyntaxShape::ColumnPath,
|
|
||||||
"the column that will precede the columns moved",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"before",
|
|
||||||
SyntaxShape::ColumnPath,
|
|
||||||
"the column that will be next the columns moved",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Move columns."
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
operate(args, registry).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn operate(
|
|
||||||
raw_args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let name = raw_args.call_info.name_tag.clone();
|
|
||||||
let registry = registry.clone();
|
|
||||||
let (
|
|
||||||
Arguments {
|
|
||||||
rest: mut columns,
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
) = raw_args.process(®istry).await?;
|
|
||||||
|
|
||||||
if columns.is_empty() {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"expected columns",
|
|
||||||
"expected columns",
|
|
||||||
name,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if columns.iter().any(|c| c.members().len() > 1) {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"expected columns",
|
|
||||||
"expected columns",
|
|
||||||
name,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if vec![&after, &before]
|
|
||||||
.iter()
|
|
||||||
.map(|o| if o.is_some() { 1 } else { 0 })
|
|
||||||
.sum::<usize>()
|
|
||||||
> 1
|
|
||||||
{
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"can't move column(s)",
|
|
||||||
"pick exactly one (before, after)",
|
|
||||||
name,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(after) = after {
|
|
||||||
let member = columns.remove(0);
|
|
||||||
|
|
||||||
Ok(input
|
|
||||||
.map(move |item| {
|
|
||||||
let member = vec![member.clone()];
|
|
||||||
let column_paths = vec![&member, &columns]
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<&ColumnPath>>();
|
|
||||||
|
|
||||||
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
|
|
||||||
|
|
||||||
if after.members().len() == 1 {
|
|
||||||
let keys = column_paths
|
|
||||||
.iter()
|
|
||||||
.filter_map(|c| c.last())
|
|
||||||
.map(|c| c.as_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if let Some(column) = after.last() {
|
|
||||||
if !keys.contains(&column.as_string()) {
|
|
||||||
ReturnSuccess::value(move_after(&item, &keys, &after, &name)?)
|
|
||||||
} else {
|
|
||||||
let msg =
|
|
||||||
format!("can't move column {} after itself", column.as_string());
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"can't move column",
|
|
||||||
msg,
|
|
||||||
after_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"expected column",
|
|
||||||
"expected column",
|
|
||||||
after_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"expected column",
|
|
||||||
"expected column",
|
|
||||||
after_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_output_stream())
|
|
||||||
} else if let Some(before) = before {
|
|
||||||
let member = columns.remove(0);
|
|
||||||
|
|
||||||
Ok(input
|
|
||||||
.map(move |item| {
|
|
||||||
let member = vec![member.clone()];
|
|
||||||
let column_paths = vec![&member, &columns]
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<&ColumnPath>>();
|
|
||||||
|
|
||||||
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
|
|
||||||
|
|
||||||
if before.members().len() == 1 {
|
|
||||||
let keys = column_paths
|
|
||||||
.iter()
|
|
||||||
.filter_map(|c| c.last())
|
|
||||||
.map(|c| c.as_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if let Some(column) = before.last() {
|
|
||||||
if !keys.contains(&column.as_string()) {
|
|
||||||
ReturnSuccess::value(move_before(&item, &keys, &before, &name)?)
|
|
||||||
} else {
|
|
||||||
let msg =
|
|
||||||
format!("can't move column {} before itself", column.as_string());
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"can't move column",
|
|
||||||
msg,
|
|
||||||
before_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"expected column",
|
|
||||||
"expected column",
|
|
||||||
before_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"expected column",
|
|
||||||
"expected column",
|
|
||||||
before_span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_output_stream())
|
|
||||||
} else {
|
|
||||||
Err(ShellError::labeled_error(
|
|
||||||
"no columns given",
|
|
||||||
"no columns given",
|
|
||||||
name,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_after(
|
|
||||||
table: &Value,
|
|
||||||
columns: &[String],
|
|
||||||
from: &ColumnPath,
|
|
||||||
tag: impl Into<Tag>,
|
|
||||||
) -> Result<Value, ShellError> {
|
|
||||||
let tag = tag.into();
|
|
||||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
|
||||||
let from = if let Some((last, _)) = from.split_last() {
|
|
||||||
last.as_string()
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"unknown column",
|
|
||||||
"unknown column",
|
|
||||||
from_fields,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
|
||||||
if columns.contains(&name) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(name)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut reordered_columns = vec![];
|
|
||||||
let mut insert = false;
|
|
||||||
let mut inserted = false;
|
|
||||||
|
|
||||||
for name in columns_moved.into_iter() {
|
|
||||||
if let Some(name) = name {
|
|
||||||
reordered_columns.push(Some(name.clone()));
|
|
||||||
|
|
||||||
if !inserted && name == from {
|
|
||||||
insert = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reordered_columns.push(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if insert {
|
|
||||||
for column in columns {
|
|
||||||
reordered_columns.push(Some(column.clone()));
|
|
||||||
}
|
|
||||||
inserted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(select_fields(
|
|
||||||
table,
|
|
||||||
&reordered_columns
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| v)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&tag,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_before(
|
|
||||||
table: &Value,
|
|
||||||
columns: &[String],
|
|
||||||
from: &ColumnPath,
|
|
||||||
tag: impl Into<Tag>,
|
|
||||||
) -> Result<Value, ShellError> {
|
|
||||||
let tag = tag.into();
|
|
||||||
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
|
||||||
let from = if let Some((last, _)) = from.split_last() {
|
|
||||||
last.as_string()
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::labeled_error(
|
|
||||||
"unknown column",
|
|
||||||
"unknown column",
|
|
||||||
from_fields,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
|
||||||
if columns.contains(&name) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(name)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut reordered_columns = vec![];
|
|
||||||
let mut inserted = false;
|
|
||||||
|
|
||||||
for name in columns_moved.into_iter() {
|
|
||||||
if let Some(name) = name {
|
|
||||||
if !inserted && name == from {
|
|
||||||
for column in columns {
|
|
||||||
reordered_columns.push(Some(column.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
inserted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reordered_columns.push(Some(name.clone()));
|
|
||||||
} else {
|
|
||||||
reordered_columns.push(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(select_fields(
|
|
||||||
table,
|
|
||||||
&reordered_columns
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| v)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&tag,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::ShellError;
|
|
||||||
use super::SubCommand;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
Ok(test_examples(SubCommand {})?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,20 @@
|
|||||||
|
use crate::command_registry::CommandRegistry;
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use nu_data::base::select_fields;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||||
|
use nu_source::HasFallibleSpan;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Command;
|
pub struct Command;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Arguments {
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
after: Option<ColumnPath>,
|
||||||
|
before: Option<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for Command {
|
impl WholeStreamCommand for Command {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -14,34 +23,318 @@ impl WholeStreamCommand for Command {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("move")
|
Signature::build("move")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "the columns to move")
|
||||||
|
.named(
|
||||||
|
"after",
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"the column that will precede the columns moved",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"before",
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"the column that will be next the columns moved",
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Moves across desired subcommand."
|
"Move columns."
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
&self,
|
&self,
|
||||||
_args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
operate(args, registry).await
|
||||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
}
|
||||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
|
||||||
.into_value(Tag::unknown()),
|
fn examples(&self) -> Vec<Example> {
|
||||||
))))
|
use nu_test_support::value::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Move the column \"type\" before the column \"name\"",
|
||||||
|
example: r#"ls | move type --before name | first"#,
|
||||||
|
result: Some(vec![row! {
|
||||||
|
"type".into() => string("File"),
|
||||||
|
"name".into() => string("Andrés.txt"),
|
||||||
|
"chickens".into() => int(10),
|
||||||
|
"modified".into() => date("2019-07-23")
|
||||||
|
}]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "or move the column \"chickens\" after \"name\"",
|
||||||
|
example: r#"ls | move chickens --after name | first"#,
|
||||||
|
result: Some(vec![row! {
|
||||||
|
"name".into() => string("Andrés.txt"),
|
||||||
|
"chickens".into() => int(10),
|
||||||
|
"type".into() => string("File"),
|
||||||
|
"modified".into() => date("2019-07-23")
|
||||||
|
}]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "you can selectively move many columns in either direction",
|
||||||
|
example: r#"ls | move name chickens --after type | first"#,
|
||||||
|
result: Some(vec![row! {
|
||||||
|
"type".into() => string("File"),
|
||||||
|
"name".into() => string("Andrés.txt"),
|
||||||
|
"chickens".into() => int(10),
|
||||||
|
"modified".into() => date("2019-07-23")
|
||||||
|
}]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
async fn operate(
|
||||||
mod tests {
|
raw_args: CommandArgs,
|
||||||
use super::Command;
|
registry: &CommandRegistry,
|
||||||
use super::ShellError;
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let name = raw_args.call_info.name_tag.clone();
|
||||||
|
let registry = registry.clone();
|
||||||
|
let (
|
||||||
|
Arguments {
|
||||||
|
rest: mut columns,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = raw_args.process(®istry).await?;
|
||||||
|
|
||||||
#[test]
|
if columns.is_empty() {
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
return Err(ShellError::labeled_error(
|
||||||
use crate::examples::test as test_examples;
|
"expected columns",
|
||||||
|
"expected columns",
|
||||||
|
name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(test_examples(Command {})?)
|
if columns.iter().any(|c| c.members().len() > 1) {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"expected columns",
|
||||||
|
"expected columns",
|
||||||
|
name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if vec![&after, &before]
|
||||||
|
.iter()
|
||||||
|
.map(|o| if o.is_some() { 1 } else { 0 })
|
||||||
|
.sum::<usize>()
|
||||||
|
> 1
|
||||||
|
{
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"can't move column(s)",
|
||||||
|
"pick exactly one (before, after)",
|
||||||
|
name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(after) = after {
|
||||||
|
let member = columns.remove(0);
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |item| {
|
||||||
|
let member = vec![member.clone()];
|
||||||
|
let column_paths = vec![&member, &columns]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<&ColumnPath>>();
|
||||||
|
|
||||||
|
let after_span = after.maybe_span().unwrap_or_else(Span::unknown);
|
||||||
|
|
||||||
|
if after.members().len() == 1 {
|
||||||
|
let keys = column_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| c.last())
|
||||||
|
.map(|c| c.as_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(column) = after.last() {
|
||||||
|
if !keys.contains(&column.as_string()) {
|
||||||
|
ReturnSuccess::value(move_after(&item, &keys, &after)?)
|
||||||
|
} else {
|
||||||
|
let msg =
|
||||||
|
format!("can't move column {} after itself", column.as_string());
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"can't move column",
|
||||||
|
msg,
|
||||||
|
after_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"expected column",
|
||||||
|
"expected column",
|
||||||
|
after_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"expected column",
|
||||||
|
"expected column",
|
||||||
|
after_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
} else if let Some(before) = before {
|
||||||
|
let member = columns.remove(0);
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.map(move |item| {
|
||||||
|
let member = vec![member.clone()];
|
||||||
|
let column_paths = vec![&member, &columns]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<&ColumnPath>>();
|
||||||
|
|
||||||
|
let before_span = before.maybe_span().unwrap_or_else(Span::unknown);
|
||||||
|
|
||||||
|
if before.members().len() == 1 {
|
||||||
|
let keys = column_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| c.last())
|
||||||
|
.map(|c| c.as_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(column) = before.last() {
|
||||||
|
if !keys.contains(&column.as_string()) {
|
||||||
|
ReturnSuccess::value(move_before(&item, &keys, &before)?)
|
||||||
|
} else {
|
||||||
|
let msg =
|
||||||
|
format!("can't move column {} before itself", column.as_string());
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"can't move column",
|
||||||
|
msg,
|
||||||
|
before_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"expected column",
|
||||||
|
"expected column",
|
||||||
|
before_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"expected column",
|
||||||
|
"expected column",
|
||||||
|
before_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"no columns given",
|
||||||
|
"no columns given",
|
||||||
|
name,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_after(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
|
||||||
|
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||||
|
let from = if let Some((last, _)) = from.split_last() {
|
||||||
|
last.as_string()
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"unknown column",
|
||||||
|
"unknown column",
|
||||||
|
from_fields,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||||
|
if columns.contains(&name) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut reordered_columns = vec![];
|
||||||
|
let mut insert = false;
|
||||||
|
let mut inserted = false;
|
||||||
|
|
||||||
|
for name in columns_moved.into_iter() {
|
||||||
|
if let Some(name) = name {
|
||||||
|
reordered_columns.push(Some(name.clone()));
|
||||||
|
|
||||||
|
if !inserted && name == from {
|
||||||
|
insert = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reordered_columns.push(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if insert {
|
||||||
|
for column in columns {
|
||||||
|
reordered_columns.push(Some(column.clone()));
|
||||||
|
}
|
||||||
|
inserted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(select_fields(
|
||||||
|
table,
|
||||||
|
&reordered_columns
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|v| v)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&table.tag,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_before(table: &Value, columns: &[String], from: &ColumnPath) -> Result<Value, ShellError> {
|
||||||
|
let from_fields = from.maybe_span().unwrap_or_else(Span::unknown);
|
||||||
|
let from = if let Some((last, _)) = from.split_last() {
|
||||||
|
last.as_string()
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"unknown column",
|
||||||
|
"unknown column",
|
||||||
|
from_fields,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let columns_moved = table.data_descriptors().into_iter().map(|name| {
|
||||||
|
if columns.contains(&name) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut reordered_columns = vec![];
|
||||||
|
let mut inserted = false;
|
||||||
|
|
||||||
|
for name in columns_moved.into_iter() {
|
||||||
|
if let Some(name) = name {
|
||||||
|
if !inserted && name == from {
|
||||||
|
for column in columns {
|
||||||
|
reordered_columns.push(Some(column.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
inserted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
reordered_columns.push(Some(name.clone()));
|
||||||
|
} else {
|
||||||
|
reordered_columns.push(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(select_fields(
|
||||||
|
table,
|
||||||
|
&reordered_columns
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|v| v)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&table.tag,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
mod column;
|
|
||||||
mod command;
|
mod command;
|
||||||
pub mod mv;
|
pub mod mv;
|
||||||
|
|
||||||
pub use column::SubCommand as MoveColumn;
|
|
||||||
pub use command::Command as Move;
|
pub use command::Command as Move;
|
||||||
pub use mv::Mv;
|
pub use mv::Mv;
|
||||||
|
@ -213,7 +213,8 @@ pub async fn fetch(
|
|||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = std::fs::read(location)?;
|
let res = std::fs::read(location)
|
||||||
|
.map_err(|_| ShellError::labeled_error("Can't open filename given", "can't open", span))?;
|
||||||
|
|
||||||
// If no encoding is provided we try to guess the encoding to read the file with
|
// If no encoding is provided we try to guess the encoding to read the file with
|
||||||
let encoding = if encoding_choice.is_none() {
|
let encoding = if encoding_choice.is_none() {
|
||||||
|
@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathBasename;
|
pub struct PathBasename;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathBasenameArguments {
|
||||||
|
replace: Option<Tagged<String>>,
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathBasename {
|
impl WholeStreamCommand for PathBasename {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathBasename {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path basename")
|
Signature::build("path basename")
|
||||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
.named(
|
||||||
|
"replace",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Return original path with basename replaced by this string",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"gets the filename of a path"
|
"Gets the final component of a path"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -28,24 +41,60 @@ impl WholeStreamCommand for PathBasename {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (PathBasenameArguments { replace, rest }, input) = args.process(®istry).await?;
|
||||||
operate(input, rest, &action, tag.span).await
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: replace.map(|v| v.item),
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Get basename of a path",
|
Example {
|
||||||
example: "echo '/home/joe/test.txt' | path basename",
|
description: "Get basename of a path",
|
||||||
result: Some(vec![Value::from("test.txt")]),
|
example: "echo 'C:\\Users\\joe\\test.txt' | path basename",
|
||||||
}]
|
result: Some(vec![Value::from("test.txt")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace basename of a path",
|
||||||
|
example: "echo 'C:\\Users\\joe\\test.txt' | path basename -r 'spam.png'",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path(
|
||||||
|
"C:\\Users\\joe\\spam.png",
|
||||||
|
))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get basename of a path",
|
||||||
|
example: "echo '/home/joe/test.txt' | path basename",
|
||||||
|
result: Some(vec![Value::from("test.txt")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace basename of a path",
|
||||||
|
example: "echo '/home/joe/test.txt' | path basename -r 'spam.png'",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/spam.png"))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
UntaggedValue::string(match path.file_name() {
|
match args.replace {
|
||||||
Some(filename) => filename.to_string_lossy().to_string(),
|
Some(ref basename) => UntaggedValue::path(path.with_file_name(basename)),
|
||||||
_ => "".to_string(),
|
None => UntaggedValue::string(match path.file_name() {
|
||||||
})
|
Some(filename) => filename.to_string_lossy(),
|
||||||
|
None => "".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for Path {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Apply path function"
|
"Explore and manipulate paths"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
|
@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathDirname;
|
pub struct PathDirname;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathDirnameArguments {
|
||||||
|
replace: Option<Tagged<String>>,
|
||||||
|
#[serde(rename = "num-levels")]
|
||||||
|
num_levels: Option<Tagged<u32>>,
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathDirname {
|
impl WholeStreamCommand for PathDirname {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -14,11 +23,24 @@ impl WholeStreamCommand for PathDirname {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path dirname").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
Signature::build("path dirname")
|
||||||
|
.named(
|
||||||
|
"replace",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Return original path with dirname replaced by this string",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"num-levels",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"Number of directories to walk up",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"gets the dirname of a path"
|
"Gets the parent directory of a path"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -27,24 +49,100 @@ impl WholeStreamCommand for PathDirname {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (
|
||||||
operate(input, rest, &action, tag.span).await
|
PathDirnameArguments {
|
||||||
|
replace,
|
||||||
|
num_levels,
|
||||||
|
rest,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: replace.map(|v| v.item),
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: num_levels.map(|v| v.item),
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Get dirname of a path",
|
Example {
|
||||||
example: "echo '/home/joe/test.txt' | path dirname",
|
description: "Get dirname of a path",
|
||||||
result: Some(vec![Value::from("/home/joe")]),
|
example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname",
|
||||||
}]
|
result: Some(vec![Value::from(UntaggedValue::path(
|
||||||
|
"C:\\Users\\joe\\code",
|
||||||
|
))]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set how many levels up to skip",
|
||||||
|
example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe"))]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace the part that would be returned with custom string",
|
||||||
|
example:
|
||||||
|
"echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2 -r C:\\Users\\viking",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path(
|
||||||
|
"C:\\Users\\viking\\code\\test.txt",
|
||||||
|
))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get dirname of a path",
|
||||||
|
example: "echo '/home/joe/code/test.txt' | path dirname",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/code"))]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Set how many levels up to skip",
|
||||||
|
example: "echo '/home/joe/code/test.txt' | path dirname -n 2",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("/home/joe"))]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace the part that would be returned with custom string",
|
||||||
|
example: "echo '/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path(
|
||||||
|
"/home/viking/code/test.txt",
|
||||||
|
))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
UntaggedValue::string(match path.parent() {
|
let num_levels = args.num_levels.unwrap_or(1);
|
||||||
Some(dirname) => dirname.to_string_lossy().to_string(),
|
|
||||||
_ => "".to_string(),
|
let mut dirname = path;
|
||||||
})
|
let mut reached_top = false; // end early if somebody passes -n 99999999
|
||||||
|
for _ in 0..num_levels {
|
||||||
|
dirname = dirname.parent().unwrap_or_else(|| {
|
||||||
|
reached_top = true;
|
||||||
|
dirname
|
||||||
|
});
|
||||||
|
if reached_top {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match args.replace {
|
||||||
|
Some(ref newdir) => {
|
||||||
|
let remainder = path.strip_prefix(dirname).unwrap_or(dirname);
|
||||||
|
if !remainder.as_os_str().is_empty() {
|
||||||
|
UntaggedValue::path(Path::new(newdir).join(remainder))
|
||||||
|
} else {
|
||||||
|
UntaggedValue::path(Path::new(newdir))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => UntaggedValue::path(dirname),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathExists;
|
pub struct PathExists;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathExistsArguments {
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathExists {
|
impl WholeStreamCommand for PathExists {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExists {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
Signature::build("path exists")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"checks whether the path exists"
|
"Checks whether a path exists"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -27,10 +33,27 @@ impl WholeStreamCommand for PathExists {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (PathExistsArguments { rest }, input) = args.process(®istry).await?;
|
||||||
operate(input, rest, &action, tag.span).await
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: None,
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Check if file exists",
|
||||||
|
example: "echo 'C:\\Users\\joe\\todo.txt' | path exists",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Check if file exists",
|
description: "Check if file exists",
|
||||||
@ -40,7 +63,7 @@ impl WholeStreamCommand for PathExists {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
UntaggedValue::boolean(path.exists())
|
UntaggedValue::boolean(path.exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub struct PathExpand;
|
pub struct PathExpand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathExpandArguments {
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathExpand {
|
impl WholeStreamCommand for PathExpand {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExpand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
Signature::build("path expand")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"expands the path to its absolute form"
|
"Expands a path to its absolute form"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -27,28 +33,43 @@ impl WholeStreamCommand for PathExpand {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (PathExpandArguments { rest }, input) = args.process(®istry).await?;
|
||||||
operate(input, rest, &action, tag.span).await
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: None,
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Expand relative directories",
|
||||||
|
example: "echo 'C:\\Users\\joe\\foo\\..\\bar' | path expand",
|
||||||
|
result: None,
|
||||||
|
// fails to canonicalize into Some(vec![Value::from("C:\\Users\\joe\\bar")]) due to non-existing path
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Expand relative directories",
|
description: "Expand relative directories",
|
||||||
example: "echo '/home/joe/foo/../bar' | path expand",
|
example: "echo '/home/joe/foo/../bar' | path expand",
|
||||||
result: None,
|
result: None,
|
||||||
//Some(vec![Value::from("/home/joe/bar")]),
|
// fails to canonicalize into Some(vec![Value::from("/home/joe/bar")]) due to non-existing path
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
let ps = path.to_string_lossy();
|
let ps = path.to_string_lossy();
|
||||||
let expanded = shellexpand::tilde(&ps);
|
let expanded = shellexpand::tilde(&ps);
|
||||||
let path: &Path = expanded.as_ref().as_ref();
|
let path: &Path = expanded.as_ref().as_ref();
|
||||||
UntaggedValue::string(match path.canonicalize() {
|
UntaggedValue::path(dunce::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)))
|
||||||
Ok(p) => p.to_string_lossy().to_string(),
|
|
||||||
Err(_) => ps.to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathExtension;
|
pub struct PathExtension;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathExtensionArguments {
|
||||||
|
replace: Option<Tagged<String>>,
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathExtension {
|
impl WholeStreamCommand for PathExtension {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathExtension {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path extension")
|
Signature::build("path extension")
|
||||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
.named(
|
||||||
|
"replace",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Return original path with extension replaced by this string",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"gets the extension of a path"
|
"Gets the extension of a path"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -28,8 +41,15 @@ impl WholeStreamCommand for PathExtension {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (PathExtensionArguments { replace, rest }, input) = args.process(®istry).await?;
|
||||||
operate(input, rest, &action, tag.span).await
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: replace.map(|v| v.item),
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -44,15 +64,28 @@ impl WholeStreamCommand for PathExtension {
|
|||||||
example: "echo 'test' | path extension",
|
example: "echo 'test' | path extension",
|
||||||
result: Some(vec![Value::from("")]),
|
result: Some(vec![Value::from("")]),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace an extension with a custom string",
|
||||||
|
example: "echo 'test.txt' | path extension -r md",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("test.md"))]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "To replace more complex extensions:",
|
||||||
|
example: "echo 'test.tar.gz' | path extension -r '' | path extension -r txt",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("test.txt"))]),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
UntaggedValue::string(match path.extension() {
|
match args.replace {
|
||||||
Some(ext) => ext.to_string_lossy().to_string(),
|
Some(ref extension) => UntaggedValue::path(path.with_extension(extension)),
|
||||||
_ => "".to_string(),
|
None => UntaggedValue::string(match path.extension() {
|
||||||
})
|
Some(extension) => extension.to_string_lossy(),
|
||||||
|
None => "".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathFilestem;
|
pub struct PathFilestem;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathFilestemArguments {
|
||||||
|
prefix: Option<Tagged<String>>,
|
||||||
|
suffix: Option<Tagged<String>>,
|
||||||
|
replace: Option<Tagged<String>>,
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathFilestem {
|
impl WholeStreamCommand for PathFilestem {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -15,11 +24,29 @@ impl WholeStreamCommand for PathFilestem {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path filestem")
|
Signature::build("path filestem")
|
||||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
.named(
|
||||||
|
"replace",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Return original path with filestem replaced by this string",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"prefix",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Strip this string from from the beginning of a file name",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"suffix",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Strip this string from from the end of a file name",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"gets the filestem of a path"
|
"Gets the file stem of a path"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -28,24 +55,111 @@ impl WholeStreamCommand for PathFilestem {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (
|
||||||
operate(input, rest, &action, tag.span).await
|
PathFilestemArguments {
|
||||||
|
replace,
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
|
rest,
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: replace.map(|v| v.item),
|
||||||
|
prefix: prefix.map(|v| v.item),
|
||||||
|
suffix: suffix.map(|v| v.item),
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Get filestem of a path",
|
Example {
|
||||||
example: "echo '/home/joe/test.txt' | path filestem",
|
description: "Get filestem of a path",
|
||||||
result: Some(vec![Value::from("test")]),
|
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg' | path filestem",
|
||||||
}]
|
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||||
|
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||||
|
result: Some(vec![Value::from("lettuce")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace the filestem that would be returned",
|
||||||
|
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe\\bacon_spam.egg.gz"))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get filestem of a path",
|
||||||
|
example: "echo '/home/joe/bacon_lettuce.egg' | path filestem",
|
||||||
|
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||||
|
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||||
|
result: Some(vec![Value::from("lettuce")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Replace the filestem that would be returned",
|
||||||
|
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||||
|
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/bacon_spam.egg.gz"))]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
UntaggedValue::string(match path.file_stem() {
|
let basename = match path.file_name() {
|
||||||
Some(stem) => stem.to_string_lossy().to_string(),
|
Some(name) => name.to_string_lossy().to_string(),
|
||||||
_ => "".to_string(),
|
None => "".to_string(),
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let suffix = match args.suffix {
|
||||||
|
Some(ref suf) => match basename.rmatch_indices(suf).next() {
|
||||||
|
Some((i, _)) => basename.split_at(i).1.to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
},
|
||||||
|
None => match path.extension() {
|
||||||
|
// Prepend '.' since the extension returned comes without it
|
||||||
|
Some(ext) => ".".to_string() + &ext.to_string_lossy().to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let prefix = match args.prefix {
|
||||||
|
Some(ref pre) => match basename.matches(pre).next() {
|
||||||
|
Some(m) => basename.split_at(m.len()).0.to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
},
|
||||||
|
None => "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let basename_without_prefix = match basename.matches(&prefix).next() {
|
||||||
|
Some(m) => basename.split_at(m.len()).1.to_string(),
|
||||||
|
None => basename,
|
||||||
|
};
|
||||||
|
|
||||||
|
let stem = match basename_without_prefix.rmatch_indices(&suffix).next() {
|
||||||
|
Some((i, _)) => basename_without_prefix.split_at(i).0.to_string(),
|
||||||
|
None => basename_without_prefix,
|
||||||
|
};
|
||||||
|
|
||||||
|
match args.replace {
|
||||||
|
Some(ref replace) => {
|
||||||
|
let new_name = prefix + replace + &suffix;
|
||||||
|
UntaggedValue::path(path.with_file_name(&new_name))
|
||||||
|
}
|
||||||
|
None => UntaggedValue::string(stem),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -12,6 +12,7 @@ use nu_errors::ShellError;
|
|||||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Span;
|
use nu_source::Span;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use basename::PathBasename;
|
pub use basename::PathBasename;
|
||||||
pub use command::Path as PathCommand;
|
pub use command::Path as PathCommand;
|
||||||
@ -24,17 +25,32 @@ pub use r#type::PathType;
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct DefaultArguments {
|
struct DefaultArguments {
|
||||||
rest: Vec<ColumnPath>,
|
// used by basename, dirname, extension and filestem
|
||||||
|
replace: Option<String>,
|
||||||
|
// used by filestem
|
||||||
|
prefix: Option<String>,
|
||||||
|
suffix: Option<String>,
|
||||||
|
// used by dirname
|
||||||
|
num_levels: Option<u32>,
|
||||||
|
// used by all
|
||||||
|
paths: Vec<ColumnPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_value<F>(action: &F, v: &Value, span: Span) -> Result<Value, ShellError>
|
fn handle_value<F>(
|
||||||
|
action: &F,
|
||||||
|
v: &Value,
|
||||||
|
span: Span,
|
||||||
|
args: Arc<DefaultArguments>,
|
||||||
|
) -> Result<Value, ShellError>
|
||||||
where
|
where
|
||||||
F: Fn(&Path) -> UntaggedValue + Send + 'static,
|
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
|
||||||
{
|
{
|
||||||
let v = match &v.value {
|
let v = match &v.value {
|
||||||
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf).into_value(v.tag()),
|
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf, args).into_value(v.tag()),
|
||||||
UntaggedValue::Primitive(Primitive::String(s))
|
UntaggedValue::Primitive(Primitive::String(s))
|
||||||
| UntaggedValue::Primitive(Primitive::Line(s)) => action(s.as_ref()).into_value(v.tag()),
|
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||||
|
action(s.as_ref(), args).into_value(v.tag())
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
let got = format!("got {}", other.type_name());
|
let got = format!("got {}", other.type_name());
|
||||||
return Err(ShellError::labeled_error_with_secondary(
|
return Err(ShellError::labeled_error_with_secondary(
|
||||||
@ -51,24 +67,25 @@ where
|
|||||||
|
|
||||||
async fn operate<F>(
|
async fn operate<F>(
|
||||||
input: crate::InputStream,
|
input: crate::InputStream,
|
||||||
paths: Vec<ColumnPath>,
|
|
||||||
action: &'static F,
|
action: &'static F,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
args: Arc<DefaultArguments>,
|
||||||
) -> Result<OutputStream, ShellError>
|
) -> Result<OutputStream, ShellError>
|
||||||
where
|
where
|
||||||
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static,
|
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Ok(input
|
Ok(input
|
||||||
.map(move |v| {
|
.map(move |v| {
|
||||||
if paths.is_empty() {
|
if args.paths.is_empty() {
|
||||||
ReturnSuccess::value(handle_value(&action, &v, span)?)
|
ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
|
|
||||||
for path in &paths {
|
for path in &args.paths {
|
||||||
|
let cloned_args = Arc::clone(&args);
|
||||||
ret = ret.swap_data_by_column_path(
|
ret = ret.swap_data_by_column_path(
|
||||||
path,
|
path,
|
||||||
Box::new(move |old| handle_value(&action, &old, span)),
|
Box::new(move |old| handle_value(&action, &old, span, cloned_args)),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,16 @@ use crate::commands::WholeStreamCommand;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::shell::filesystem_shell::get_file_type;
|
use crate::shell::filesystem_shell::get_file_type;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathType;
|
pub struct PathType;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PathTypeArguments {
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PathType {
|
impl WholeStreamCommand for PathType {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -15,11 +20,12 @@ impl WholeStreamCommand for PathType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
Signature::build("path type")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"gives the type of the object the path refers to (eg file, dir, symlink)"
|
"Gives the type of the object a path refers to (e.g., file, dir, symlink)"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
@ -28,8 +34,15 @@ impl WholeStreamCommand for PathType {
|
|||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
let (PathTypeArguments { rest }, input) = args.process(®istry).await?;
|
||||||
operate(input, rest, &action, tag.span).await
|
let args = Arc::new(DefaultArguments {
|
||||||
|
replace: None,
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
|
num_levels: None,
|
||||||
|
paths: rest,
|
||||||
|
});
|
||||||
|
operate(input, &action, tag.span, args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -41,7 +54,7 @@ impl WholeStreamCommand for PathType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(path: &Path) -> UntaggedValue {
|
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||||
let meta = std::fs::symlink_metadata(path);
|
let meta = std::fs::symlink_metadata(path);
|
||||||
UntaggedValue::string(match &meta {
|
UntaggedValue::string(match &meta {
|
||||||
Ok(md) => get_file_type(md),
|
Ok(md) => get_file_type(md),
|
||||||
|
89
crates/nu-cli/src/commands/random/chars.rs
Normal file
89
crates/nu-cli/src/commands/random/chars.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rand::prelude::{thread_rng, Rng};
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CharsArgs {
|
||||||
|
length: Option<Tagged<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CHARS_LENGTH: u32 = 25;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"random chars"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("random chars").named(
|
||||||
|
"length",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"Number of chars",
|
||||||
|
Some('l'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Generate random chars"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
chars(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Generate random chars",
|
||||||
|
example: "random chars",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Generate random chars with specified length",
|
||||||
|
example: "random chars -l 20",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn chars(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (CharsArgs { length }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let chars_length = length.map_or(DEFAULT_CHARS_LENGTH, |l| l.item);
|
||||||
|
|
||||||
|
let random_string: String = thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(chars_length as usize)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let result = UntaggedValue::string(random_string);
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
111
crates/nu-cli/src/commands/random/decimal.rs
Normal file
111
crates/nu-cli/src/commands/random/decimal.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::deserializer::NumericRange;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use rand::prelude::{thread_rng, Rng};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DecimalArgs {
|
||||||
|
range: Option<Tagged<NumericRange>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"random decimal"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("random decimal").optional("range", SyntaxShape::Range, "Range of values")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Generate a random decimal within a range [min..max]"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
decimal(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Generate a default decimal value between 0 and 1",
|
||||||
|
example: "random decimal",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Generate a random decimal less than or equal to 500",
|
||||||
|
example: "random decimal ..500",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Generate a random decimal greater than or equal to 100000",
|
||||||
|
example: "random decimal 100000..",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Generate a random decimal between 1 and 10",
|
||||||
|
example: "random decimal 1..10",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decimal(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DecimalArgs { range }, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let (min, max) = if let Some(range) = &range {
|
||||||
|
(range.item.min() as f64, range.item.max() as f64)
|
||||||
|
} else {
|
||||||
|
(0.0, 1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
match min.partial_cmp(&max) {
|
||||||
|
Some(Ordering::Greater) => Err(ShellError::labeled_error(
|
||||||
|
format!("Invalid range {}..{}", min, max),
|
||||||
|
"expected a valid range",
|
||||||
|
range
|
||||||
|
.expect("Unexpected ordering error in random decimal")
|
||||||
|
.span(),
|
||||||
|
)),
|
||||||
|
Some(Ordering::Equal) => {
|
||||||
|
let untagged_result = UntaggedValue::decimal_from_float(min, Span::new(64, 64));
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut thread_rng = thread_rng();
|
||||||
|
let result: f64 = thread_rng.gen_range(min, max);
|
||||||
|
|
||||||
|
let untagged_result = UntaggedValue::decimal_from_float(result, Span::new(64, 64));
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(untagged_result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::SubCommand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SubCommand {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
pub mod command;
|
pub mod command;
|
||||||
|
|
||||||
pub mod bool;
|
pub mod bool;
|
||||||
|
pub mod chars;
|
||||||
|
pub mod decimal;
|
||||||
pub mod dice;
|
pub mod dice;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
@ -9,6 +11,8 @@ pub mod uuid;
|
|||||||
pub use command::Command as Random;
|
pub use command::Command as Random;
|
||||||
|
|
||||||
pub use self::bool::SubCommand as RandomBool;
|
pub use self::bool::SubCommand as RandomBool;
|
||||||
|
pub use chars::SubCommand as RandomChars;
|
||||||
|
pub use decimal::SubCommand as RandomDecimal;
|
||||||
pub use dice::SubCommand as RandomDice;
|
pub use dice::SubCommand as RandomDice;
|
||||||
pub use integer::SubCommand as RandomInteger;
|
pub use integer::SubCommand as RandomInteger;
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
|
@ -87,7 +87,7 @@ async fn process_row(
|
|||||||
let row_clone = row.clone();
|
let row_clone = row.clone();
|
||||||
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
|
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
|
||||||
|
|
||||||
let scope = Scope::append_it(scope, row);
|
let scope = Scope::append_var(scope, "$it", row);
|
||||||
|
|
||||||
Ok(run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await?)
|
Ok(run_block(&block, Arc::make_mut(&mut context), input_stream, scope).await?)
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ async fn reduce(
|
|||||||
UntaggedValue::table(&values).into_untagged_value()
|
UntaggedValue::table(&values).into_untagged_value()
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope = Scope::append_var(scope, "$acc".into(), f);
|
let scope = Scope::append_var(scope, "$acc", f);
|
||||||
process_row(block, scope, context, row).await
|
process_row(block, scope, context, row).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -173,7 +173,7 @@ async fn reduce(
|
|||||||
UntaggedValue::table(&values).into_untagged_value()
|
UntaggedValue::table(&values).into_untagged_value()
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope = Scope::append_var(scope, "$acc".into(), f);
|
let scope = Scope::append_var(scope, "$acc", f);
|
||||||
process_row(block, scope, context, row).await
|
process_row(block, scope, context, row).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,29 +4,22 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{hir::Block, Scope, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{hir::Block, PositionalType, Scope, Signature, UntaggedValue};
|
||||||
|
|
||||||
#[derive(new, Clone)]
|
#[derive(new, Clone)]
|
||||||
pub struct AliasCommand {
|
pub struct AliasCommand {
|
||||||
name: String,
|
sig: Signature,
|
||||||
args: Vec<(String, SyntaxShape)>,
|
|
||||||
block: Block,
|
block: Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for AliasCommand {
|
impl WholeStreamCommand for AliasCommand {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.sig.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
let mut alias = Signature::build(&self.name);
|
self.sig.clone()
|
||||||
|
|
||||||
for (arg, shape) in &self.args {
|
|
||||||
alias = alias.optional(arg, *shape, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
alias
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -43,7 +36,7 @@ impl WholeStreamCommand for AliasCommand {
|
|||||||
let mut block = self.block.clone();
|
let mut block = self.block.clone();
|
||||||
block.set_redirect(call_info.args.external_redirection);
|
block.set_redirect(call_info.args.external_redirection);
|
||||||
|
|
||||||
let alias_command = self.clone();
|
// let alias_command = self.clone();
|
||||||
let mut context = EvaluationContext::from_args(&args, ®istry);
|
let mut context = EvaluationContext::from_args(&args, ®istry);
|
||||||
let input = args.input;
|
let input = args.input;
|
||||||
|
|
||||||
@ -51,21 +44,27 @@ impl WholeStreamCommand for AliasCommand {
|
|||||||
let evaluated = call_info.evaluate(®istry).await?;
|
let evaluated = call_info.evaluate(®istry).await?;
|
||||||
|
|
||||||
let mut vars = IndexMap::new();
|
let mut vars = IndexMap::new();
|
||||||
|
|
||||||
let mut num_positionals = 0;
|
let mut num_positionals = 0;
|
||||||
if let Some(positional) = &evaluated.args.positional {
|
if let Some(positional) = &evaluated.args.positional {
|
||||||
num_positionals = positional.len();
|
num_positionals = positional.len();
|
||||||
for (pos, arg) in positional.iter().enumerate() {
|
for (idx, arg) in positional.iter().enumerate() {
|
||||||
vars.insert(alias_command.args[pos].0.to_string(), arg.clone());
|
let pos_type = &self.sig.positional[idx].0;
|
||||||
|
match pos_type {
|
||||||
|
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
|
||||||
|
vars.insert(name.clone(), arg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Fill out every missing argument with empty value
|
||||||
if alias_command.args.len() > num_positionals {
|
if self.sig.positional.len() > num_positionals {
|
||||||
for idx in 0..(alias_command.args.len() - num_positionals) {
|
for idx in num_positionals..self.sig.positional.len() {
|
||||||
vars.insert(
|
let pos_type = &self.sig.positional[idx].0;
|
||||||
alias_command.args[idx + num_positionals].0.to_string(),
|
match pos_type {
|
||||||
UntaggedValue::nothing().into_untagged_value(),
|
PositionalType::Mandatory(name, _) | PositionalType::Optional(name, _) => {
|
||||||
);
|
vars.insert(name.clone(), UntaggedValue::nothing().into_untagged_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ fn string_from(input: &[Value]) -> String {
|
|||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in input.iter() {
|
for i in input.iter() {
|
||||||
if !first {
|
if !first {
|
||||||
save_data.push_str("\n");
|
save_data.push('\n');
|
||||||
} else {
|
} else {
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
327
crates/nu-cli/src/commands/seq.rs
Normal file
327
crates/nu-cli/src/commands/seq.rs
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::value::StrExt;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
pub struct Seq;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SeqArgs {
|
||||||
|
rest: Vec<Tagged<f64>>,
|
||||||
|
separator: Option<Tagged<String>>,
|
||||||
|
terminator: Option<Tagged<String>>,
|
||||||
|
widths: Tagged<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Seq {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"seq"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("seq")
|
||||||
|
.rest(SyntaxShape::Number, "sequence values")
|
||||||
|
.named(
|
||||||
|
"separator",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"separator character (defaults to \\n)",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"terminator",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"terminator character (defaults to \\n)",
|
||||||
|
Some('t'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"widths",
|
||||||
|
"equalize widths of all numbers by padding with zeros",
|
||||||
|
Some('w'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"print sequences of numbers"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
seq(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "sequence 1 to 10 with newline separator",
|
||||||
|
example: "seq 1 10",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("1").into(),
|
||||||
|
UntaggedValue::string("2").into(),
|
||||||
|
UntaggedValue::string("3").into(),
|
||||||
|
UntaggedValue::string("4").into(),
|
||||||
|
UntaggedValue::string("5").into(),
|
||||||
|
UntaggedValue::string("6").into(),
|
||||||
|
UntaggedValue::string("7").into(),
|
||||||
|
UntaggedValue::string("8").into(),
|
||||||
|
UntaggedValue::string("9").into(),
|
||||||
|
UntaggedValue::string("10").into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "sequence 1 to 10 with pipe separator",
|
||||||
|
example: "seq -s '|' 1 10",
|
||||||
|
result: Some(vec![Value::from("1|2|3|4|5|6|7|8|9|10")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "sequence 1 to 10 with pipe separator padded with 0",
|
||||||
|
example: "seq -s '|' -w 1 10",
|
||||||
|
result: Some(vec![Value::from("01|02|03|04|05|06|07|08|09|10")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "sequence 1 to 10 with pipe separator padded by 2s",
|
||||||
|
example: "seq -s ' | ' -w 1 2 10",
|
||||||
|
result: Some(vec![Value::from("01 | 03 | 05 | 07 | 09")]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn seq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let name = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let (
|
||||||
|
SeqArgs {
|
||||||
|
rest: rest_nums,
|
||||||
|
separator,
|
||||||
|
terminator,
|
||||||
|
widths,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
if rest_nums.is_empty() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"seq requires some parameters",
|
||||||
|
"needs parameter",
|
||||||
|
name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sep: String = match separator {
|
||||||
|
Some(s) => {
|
||||||
|
if s.item == r"\t" {
|
||||||
|
'\t'.to_string()
|
||||||
|
} else if s.item == r"\n" {
|
||||||
|
'\n'.to_string()
|
||||||
|
} else if s.item == r"\r" {
|
||||||
|
'\r'.to_string()
|
||||||
|
} else {
|
||||||
|
let vec_s: Vec<char> = s.chars().collect();
|
||||||
|
if vec_s.is_empty() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Expected a single separator char from --separator",
|
||||||
|
"requires a single character string input",
|
||||||
|
&s.tag,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
vec_s.iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => '\n'.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let term: String = match terminator {
|
||||||
|
Some(t) => {
|
||||||
|
if t.item == r"\t" {
|
||||||
|
'\t'.to_string()
|
||||||
|
} else if t.item == r"\n" {
|
||||||
|
'\n'.to_string()
|
||||||
|
} else if t.item == r"\r" {
|
||||||
|
'\r'.to_string()
|
||||||
|
} else {
|
||||||
|
let vec_t: Vec<char> = t.chars().collect();
|
||||||
|
if vec_t.is_empty() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Expected a single terminator char from --terminator",
|
||||||
|
"requires a single character string input",
|
||||||
|
&t.tag,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
vec_t.iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => '\n'.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rest_nums: Vec<String> = rest_nums.iter().map(|n| n.item.to_string()).collect();
|
||||||
|
|
||||||
|
run_seq(sep, Some(term), widths.item, rest_nums)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Seq;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(Seq {})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_float(mut s: &str) -> Result<f64, String> {
|
||||||
|
if s.starts_with('+') {
|
||||||
|
s = &s[1..];
|
||||||
|
}
|
||||||
|
match s.parse() {
|
||||||
|
Ok(n) => Ok(n),
|
||||||
|
Err(e) => Err(format!(
|
||||||
|
"seq: invalid floating point argument `{}`: {}",
|
||||||
|
s, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape_sequences(s: &str) -> String {
|
||||||
|
s.replace("\\n", "\n").replace("\\t", "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_seq(
|
||||||
|
sep: String,
|
||||||
|
termy: Option<String>,
|
||||||
|
widths: bool,
|
||||||
|
free: Vec<String>,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let mut largest_dec = 0;
|
||||||
|
let mut padding = 0;
|
||||||
|
let first = if free.len() > 1 {
|
||||||
|
let slice = &free[0][..];
|
||||||
|
let len = slice.len();
|
||||||
|
let dec = slice.find('.').unwrap_or(len);
|
||||||
|
largest_dec = len - dec;
|
||||||
|
padding = dec;
|
||||||
|
match parse_float(slice) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(s) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
s,
|
||||||
|
"error parsing float",
|
||||||
|
Tag::unknown(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let step = if free.len() > 2 {
|
||||||
|
let slice = &free[1][..];
|
||||||
|
let len = slice.len();
|
||||||
|
let dec = slice.find('.').unwrap_or(len);
|
||||||
|
largest_dec = cmp::max(largest_dec, len - dec);
|
||||||
|
padding = cmp::max(padding, dec);
|
||||||
|
match parse_float(slice) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(s) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
s,
|
||||||
|
"error parsing float",
|
||||||
|
Tag::unknown(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let last = {
|
||||||
|
let slice = &free[free.len() - 1][..];
|
||||||
|
padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len()));
|
||||||
|
match parse_float(slice) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(s) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
s,
|
||||||
|
"error parsing float",
|
||||||
|
Tag::unknown(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if largest_dec > 0 {
|
||||||
|
largest_dec -= 1;
|
||||||
|
}
|
||||||
|
let separator = escape_sequences(&sep[..]);
|
||||||
|
let terminator = match termy {
|
||||||
|
Some(term) => escape_sequences(&term[..]),
|
||||||
|
None => separator.clone(),
|
||||||
|
};
|
||||||
|
print_seq(
|
||||||
|
first,
|
||||||
|
step,
|
||||||
|
last,
|
||||||
|
largest_dec,
|
||||||
|
separator,
|
||||||
|
terminator,
|
||||||
|
widths,
|
||||||
|
padding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn done_printing(next: f64, step: f64, last: f64) -> bool {
|
||||||
|
if step >= 0f64 {
|
||||||
|
next > last
|
||||||
|
} else {
|
||||||
|
next < last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn print_seq(
|
||||||
|
first: f64,
|
||||||
|
step: f64,
|
||||||
|
last: f64,
|
||||||
|
largest_dec: usize,
|
||||||
|
separator: String,
|
||||||
|
terminator: String,
|
||||||
|
pad: bool,
|
||||||
|
padding: usize,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let mut i = 0isize;
|
||||||
|
let mut value = first + i as f64 * step;
|
||||||
|
let mut ret_str = "".to_owned();
|
||||||
|
while !done_printing(value, step, last) {
|
||||||
|
let istr = format!("{:.*}", largest_dec, value);
|
||||||
|
let ilen = istr.len();
|
||||||
|
let before_dec = istr.find('.').unwrap_or(ilen);
|
||||||
|
if pad && before_dec < padding {
|
||||||
|
for _ in 0..(padding - before_dec) {
|
||||||
|
ret_str.push('0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret_str.push_str(&istr);
|
||||||
|
i += 1;
|
||||||
|
value = first + i as f64 * step;
|
||||||
|
if !done_printing(value, step, last) {
|
||||||
|
ret_str.push_str(&separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first >= last && step < 0f64) || (first <= last && step > 0f64) {
|
||||||
|
ret_str.push_str(&terminator);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows: Vec<Value> = ret_str
|
||||||
|
.lines()
|
||||||
|
.map(|v| v.to_str_value_create_tag())
|
||||||
|
.collect();
|
||||||
|
Ok(futures::stream::iter(rows.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||||
|
}
|
379
crates/nu-cli/src/commands/seq_dates.rs
Normal file
379
crates/nu-cli/src/commands/seq_dates.rs
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use chrono::naive::NaiveDate;
|
||||||
|
use chrono::{Duration, Local};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{value::I64Ext, value::StrExt, value::StringExt, value::U64Ext};
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct SeqDates;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SeqDatesArgs {
|
||||||
|
separator: Option<Tagged<String>>,
|
||||||
|
output_format: Option<Tagged<String>>,
|
||||||
|
input_format: Option<Tagged<String>>,
|
||||||
|
begin_date: Option<Tagged<String>>,
|
||||||
|
end_date: Option<Tagged<String>>,
|
||||||
|
increment: Option<Tagged<i64>>,
|
||||||
|
days: Option<Tagged<u64>>,
|
||||||
|
reverse: Tagged<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for SeqDates {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"seq date"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("seq date")
|
||||||
|
.named(
|
||||||
|
"separator",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"separator character (defaults to \\n)",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"output_format",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"prints dates in this format (defaults to %Y-%m-%d)",
|
||||||
|
Some('o'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"input_format",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"give argument dates in this format (defaults to %Y-%m-%d)",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"begin_date",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"beginning date range",
|
||||||
|
Some('b'),
|
||||||
|
)
|
||||||
|
.named("end_date", SyntaxShape::String, "ending date", Some('e'))
|
||||||
|
.named(
|
||||||
|
"increment",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"increment dates by this number",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"days",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"number of days to print",
|
||||||
|
Some('d'),
|
||||||
|
)
|
||||||
|
.switch("reverse", "print dates in reverse", Some('r'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"print sequences of dates"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
seq_dates(args, registry).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "print the next 10 days in YYYY-MM-DD format with newline separator",
|
||||||
|
example: "seq date --days 10",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "print the previous 10 days in YYYY-MM-DD format with newline separator",
|
||||||
|
example: "seq date --days 10 -r",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "print the previous 10 days starting today in MM/DD/YYYY format with newline separator",
|
||||||
|
example: "seq date --days 10 -o '%m/%d/%Y' -r",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "print the first 10 days in January, 2020",
|
||||||
|
example: "seq date -b '2020-01-01' -e '2020-01-10'",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("2020-01-01").into(),
|
||||||
|
UntaggedValue::string("2020-01-02").into(),
|
||||||
|
UntaggedValue::string("2020-01-03").into(),
|
||||||
|
UntaggedValue::string("2020-01-04").into(),
|
||||||
|
UntaggedValue::string("2020-01-05").into(),
|
||||||
|
UntaggedValue::string("2020-01-06").into(),
|
||||||
|
UntaggedValue::string("2020-01-07").into(),
|
||||||
|
UntaggedValue::string("2020-01-08").into(),
|
||||||
|
UntaggedValue::string("2020-01-09").into(),
|
||||||
|
UntaggedValue::string("2020-01-10").into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "print every fifth day between January 1st 2020 and January 31st 2020",
|
||||||
|
example: "seq date -b '2020-01-01' -e '2020-01-31' -n 5",
|
||||||
|
result: Some(vec![
|
||||||
|
UntaggedValue::string("2020-01-01").into(),
|
||||||
|
UntaggedValue::string("2020-01-06").into(),
|
||||||
|
UntaggedValue::string("2020-01-11").into(),
|
||||||
|
UntaggedValue::string("2020-01-16").into(),
|
||||||
|
UntaggedValue::string("2020-01-21").into(),
|
||||||
|
UntaggedValue::string("2020-01-26").into(),
|
||||||
|
UntaggedValue::string("2020-01-31").into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "starting on May 5th, 2020, print the next 10 days in your locale's date format, colon separated",
|
||||||
|
example: "seq date -o %x -s ':' -d 10 -b '2020-05-01'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn seq_dates(
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
let _name = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let (
|
||||||
|
SeqDatesArgs {
|
||||||
|
separator,
|
||||||
|
output_format,
|
||||||
|
input_format,
|
||||||
|
begin_date,
|
||||||
|
end_date,
|
||||||
|
increment,
|
||||||
|
days,
|
||||||
|
reverse,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) = args.process(®istry).await?;
|
||||||
|
|
||||||
|
let sep: String = match separator {
|
||||||
|
Some(s) => {
|
||||||
|
if s.item == r"\t" {
|
||||||
|
'\t'.to_string()
|
||||||
|
} else if s.item == r"\n" {
|
||||||
|
'\n'.to_string()
|
||||||
|
} else if s.item == r"\r" {
|
||||||
|
'\r'.to_string()
|
||||||
|
} else {
|
||||||
|
let vec_s: Vec<char> = s.chars().collect();
|
||||||
|
if vec_s.is_empty() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Expected a single separator char from --separator",
|
||||||
|
"requires a single character string input",
|
||||||
|
&s.tag,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
vec_s.iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => '\n'.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let outformat = match output_format {
|
||||||
|
Some(s) => Some(s.item.to_string_value(s.tag)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let informat = match input_format {
|
||||||
|
Some(s) => Some(s.item.to_string_value(s.tag)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let begin = match begin_date {
|
||||||
|
Some(s) => Some(s.item),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match end_date {
|
||||||
|
Some(s) => Some(s.item),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inc = match increment {
|
||||||
|
Some(i) => {
|
||||||
|
let clone = i.clone();
|
||||||
|
i.to_value(clone.tag)
|
||||||
|
}
|
||||||
|
_ => (1 as i64).to_value_create_tag(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let day_count: Option<Value> = match days {
|
||||||
|
Some(i) => Some(i.item.to_value(i.tag)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rev = false;
|
||||||
|
if *reverse {
|
||||||
|
rev = *reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
run_seq_dates(sep, outformat, informat, begin, end, inc, day_count, rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_date_string(s: &str, format: &str) -> Result<NaiveDate, &'static str> {
|
||||||
|
let d = match NaiveDate::parse_from_str(s, format) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => return Err("Failed to parse date."),
|
||||||
|
};
|
||||||
|
Ok(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn run_seq_dates(
|
||||||
|
separator: String,
|
||||||
|
output_format: Option<Value>,
|
||||||
|
input_format: Option<Value>,
|
||||||
|
beginning_date: Option<String>,
|
||||||
|
ending_date: Option<String>,
|
||||||
|
increment: Value,
|
||||||
|
day_count: Option<Value>,
|
||||||
|
reverse: bool,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let today = Local::today().naive_local();
|
||||||
|
let mut step_size: i64 = increment
|
||||||
|
.as_i64()
|
||||||
|
.expect("unable to change increment to i64");
|
||||||
|
|
||||||
|
if step_size == 0 {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"increment cannot be 0",
|
||||||
|
"increment cannot be 0",
|
||||||
|
increment.tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let in_format = match input_format {
|
||||||
|
Some(i) => i.as_string().map_err(|e| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
e.to_string(),
|
||||||
|
"error with input_format as_string",
|
||||||
|
i.tag.span,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
None => "%Y-%m-%d".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let out_format = match output_format {
|
||||||
|
Some(o) => o.as_string().map_err(|e| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
e.to_string(),
|
||||||
|
"error with output_format as_string",
|
||||||
|
o.tag.span,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
None => "%Y-%m-%d".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_date = match beginning_date {
|
||||||
|
Some(d) => match parse_date_string(&d, &in_format) {
|
||||||
|
Ok(nd) => nd,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
e,
|
||||||
|
"Failed to parse date",
|
||||||
|
Tag::unknown(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => today,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut end_date = match ending_date {
|
||||||
|
Some(d) => match parse_date_string(&d, &in_format) {
|
||||||
|
Ok(nd) => nd,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
e,
|
||||||
|
"Failed to parse date",
|
||||||
|
Tag::unknown(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => today,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut days_to_output = match day_count {
|
||||||
|
Some(d) => d.as_i64()?,
|
||||||
|
None => 0i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the signs opposite if we're created dates in reverse direction
|
||||||
|
if reverse {
|
||||||
|
step_size *= -1;
|
||||||
|
days_to_output *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if days_to_output != 0 {
|
||||||
|
end_date = match start_date.checked_add_signed(Duration::days(days_to_output)) {
|
||||||
|
Some(date) => date,
|
||||||
|
None => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"integer value too large",
|
||||||
|
"integer value too large",
|
||||||
|
Tag::unknown(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conceptually counting down with a positive step or counting up with a negative step
|
||||||
|
// makes no sense, attempt to do what one means by inverting the signs in those cases.
|
||||||
|
if (start_date > end_date) && (step_size > 0) || (start_date < end_date) && step_size < 0 {
|
||||||
|
step_size = -step_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_out_of_range =
|
||||||
|
|next| (step_size > 0 && next > end_date) || (step_size < 0 && next < end_date);
|
||||||
|
|
||||||
|
let mut next = start_date;
|
||||||
|
if is_out_of_range(next) {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"date is out of range",
|
||||||
|
"date is out of range",
|
||||||
|
Tag::unknown(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ret_str = String::from("");
|
||||||
|
loop {
|
||||||
|
ret_str.push_str(&format!("{}", next.format(&out_format)));
|
||||||
|
// TODO: check this value is good
|
||||||
|
next += Duration::days(step_size);
|
||||||
|
|
||||||
|
if is_out_of_range(next) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret_str.push_str(&separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows: Vec<Value> = ret_str
|
||||||
|
.lines()
|
||||||
|
.map(|v| v.to_str_value_create_tag())
|
||||||
|
.collect();
|
||||||
|
Ok(futures::stream::iter(rows.into_iter().map(ReturnSuccess::value)).to_output_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SeqDates;
|
||||||
|
use super::ShellError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
Ok(test_examples(SeqDates {})?)
|
||||||
|
}
|
||||||
|
}
|
@ -84,7 +84,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
.skip_while(move |item| {
|
.skip_while(move |item| {
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@ -85,7 +85,7 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
let item = item.clone();
|
let item = item.clone();
|
||||||
let condition = condition.clone();
|
let condition = condition.clone();
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let scope = Scope::append_it(scope.clone(), item.clone());
|
let scope = Scope::append_var(scope.clone(), "$it", item.clone());
|
||||||
trace!("ITEM = {:?}", item);
|
trace!("ITEM = {:?}", item);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@ -101,9 +101,10 @@ pub fn split(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::split;
|
use super::split;
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use nu_data::utils::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
use nu_data::utils::helpers::committers_grouped_by_date;
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::*;
|
use nu_source::*;
|
||||||
|
use nu_test_support::value::{date, int, row, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn splits_inner_tables_by_key() {
|
fn splits_inner_tables_by_key() {
|
||||||
|
@ -112,8 +112,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -46,8 +46,8 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{to_camel_case, SubCommand};
|
use super::{to_camel_case, SubCommand};
|
||||||
use crate::commands::str_::case::action;
|
use crate::commands::str_::case::action;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -46,8 +46,8 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{to_kebab_case, SubCommand};
|
use super::{to_kebab_case, SubCommand};
|
||||||
use crate::commands::str_::case::action;
|
use crate::commands::str_::case::action;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -46,8 +46,8 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{to_pascal_case, SubCommand};
|
use super::{to_pascal_case, SubCommand};
|
||||||
use crate::commands::str_::case::action;
|
use crate::commands::str_::case::action;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -46,8 +46,8 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{to_screaming_snake_case, SubCommand};
|
use super::{to_screaming_snake_case, SubCommand};
|
||||||
use crate::commands::str_::case::action;
|
use crate::commands::str_::case::action;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -46,8 +46,8 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{to_snake_case, SubCommand};
|
use super::{to_snake_case, SubCommand};
|
||||||
use crate::commands::str_::case::action;
|
use crate::commands::str_::case::action;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -130,9 +130,9 @@ fn action(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -100,8 +100,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -149,8 +149,8 @@ fn action(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, FindReplace, SubCommand};
|
use super::{action, FindReplace, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -248,9 +248,9 @@ fn process_range(input: &Value, range: &Value) -> Result<IndexOfOptionalBounds,
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::{Primitive, UntaggedValue};
|
use nu_protocol::{Primitive, UntaggedValue};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -145,9 +145,9 @@ fn action(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -145,9 +145,9 @@ fn action(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -101,8 +101,8 @@ fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Valu
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, Replace, SubCommand};
|
use super::{action, Replace, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -104,9 +104,9 @@ fn action(input: &Value, pattern: &str, tag: impl Into<Tag>) -> Result<Value, Sh
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::UntaggedValue;
|
use nu_protocol::UntaggedValue;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -159,12 +159,14 @@ fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Val
|
|||||||
"End must be greater than or equal to Start",
|
"End must be greater than or equal to Start",
|
||||||
tag.span,
|
tag.span,
|
||||||
)),
|
)),
|
||||||
Ordering::Less => Ok(UntaggedValue::string(
|
Ordering::Less => Ok(UntaggedValue::string(if end == isize::max_value() {
|
||||||
|
s.chars().skip(start as usize).collect::<String>()
|
||||||
|
} else {
|
||||||
s.chars()
|
s.chars()
|
||||||
.skip(start as usize)
|
.skip(start as usize)
|
||||||
.take((end - start) as usize)
|
.take((end - start) as usize)
|
||||||
.collect::<String>(),
|
.collect::<String>()
|
||||||
)
|
})
|
||||||
.into_value(tag)),
|
.into_value(tag)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -285,8 +287,8 @@ fn process_arguments(range: Value, name: impl Into<Tag>) -> Result<(isize, isize
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand, Substring};
|
use super::{action, SubCommand, Substring};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
@ -330,6 +332,9 @@ mod tests {
|
|||||||
expectation("and", (0, -3)),
|
expectation("and", (0, -3)),
|
||||||
expectation("andr", (0, -2)),
|
expectation("andr", (0, -2)),
|
||||||
expectation("andre", (0, -1)),
|
expectation("andre", (0, -1)),
|
||||||
|
// str substring [ -4 , _ ]
|
||||||
|
// str substring -4 ,
|
||||||
|
expectation("dres", (-4, isize::max_value())),
|
||||||
expectation("", (0, -110)),
|
expectation("", (0, -110)),
|
||||||
expectation("", (6, 0)),
|
expectation("", (6, 0)),
|
||||||
expectation("", (6, -1)),
|
expectation("", (6, -1)),
|
||||||
|
@ -176,9 +176,9 @@ fn action(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, DatetimeFormat, SubCommand};
|
use super::{action, DatetimeFormat, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::string;
|
|
||||||
use nu_protocol::{Primitive, UntaggedValue};
|
use nu_protocol::{Primitive, UntaggedValue};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::string;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -114,8 +114,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::{decimal_from_float, string};
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::{decimal_from_float, string};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -5,15 +5,16 @@ use nu_protocol::ShellTypeName;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::Tag;
|
use nu_source::{Tag, Tagged};
|
||||||
use nu_value_ext::ValueExt;
|
use nu_value_ext::ValueExt;
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use std::str::FromStr;
|
use num_traits::Num;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
rest: Vec<ColumnPath>,
|
rest: Vec<ColumnPath>,
|
||||||
|
radix: Option<Tagged<u32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
@ -25,10 +26,12 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("str to-int").rest(
|
Signature::build("str to-int")
|
||||||
SyntaxShape::ColumnPath,
|
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||||
"optionally convert text into integer by column paths",
|
.rest(
|
||||||
)
|
SyntaxShape::ColumnPath,
|
||||||
|
"optionally convert text into integer by column paths",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -44,11 +47,28 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Convert to an integer",
|
Example {
|
||||||
example: "echo '255' | str to-int",
|
description: "Convert to an integer",
|
||||||
result: None,
|
example: "echo '255' | str to-int",
|
||||||
}]
|
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert str column to an integer",
|
||||||
|
example: "echo [['count']; ['255']] | str to-int count | get count",
|
||||||
|
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert to integer from binary",
|
||||||
|
example: "echo '1101' | str to-int -r 2",
|
||||||
|
result: Some(vec![UntaggedValue::int(13).into()]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert to integer from hex",
|
||||||
|
example: "echo 'FF' | str to-int -r 16",
|
||||||
|
result: Some(vec![UntaggedValue::int(255).into()]),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,21 +78,23 @@ async fn operate(
|
|||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
|
|
||||||
let (Arguments { rest }, input) = args.process(®istry).await?;
|
let (Arguments { rest, radix }, input) = args.process(®istry).await?;
|
||||||
|
|
||||||
let column_paths: Vec<_> = rest;
|
let radix = radix.map(|r| r.item).unwrap_or(10);
|
||||||
|
|
||||||
|
let column_paths: Vec<ColumnPath> = rest;
|
||||||
|
|
||||||
Ok(input
|
Ok(input
|
||||||
.map(move |v| {
|
.map(move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
ReturnSuccess::value(action(&v, v.tag())?)
|
ReturnSuccess::value(action(&v, v.tag(), radix)?)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
|
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
ret = ret.swap_data_by_column_path(
|
ret = ret.swap_data_by_column_path(
|
||||||
path,
|
path,
|
||||||
Box::new(move |old| action(old, old.tag())),
|
Box::new(move |old| action(old, old.tag(), radix)),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,21 +104,54 @@ async fn operate(
|
|||||||
.to_output_stream())
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
fn action(input: &Value, tag: impl Into<Tag>, radix: u32) -> Result<Value, ShellError> {
|
||||||
match &input.value {
|
match &input.value {
|
||||||
UntaggedValue::Primitive(Primitive::Line(s))
|
UntaggedValue::Primitive(Primitive::Line(s))
|
||||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||||
let other = s.trim();
|
let trimmed = s.trim();
|
||||||
let out = match BigInt::from_str(other) {
|
|
||||||
Ok(v) => UntaggedValue::int(v),
|
let out = match trimmed {
|
||||||
Err(reason) => {
|
b if b.starts_with("0b") => {
|
||||||
return Err(ShellError::labeled_error(
|
let num = match BigInt::from_str_radix(b.trim_start_matches("0b"), 2) {
|
||||||
"could not parse as an integer",
|
Ok(n) => n,
|
||||||
reason.to_string(),
|
Err(reason) => {
|
||||||
tag.into().span,
|
return Err(ShellError::labeled_error(
|
||||||
))
|
"could not parse as integer",
|
||||||
|
reason.to_string(),
|
||||||
|
tag.into().span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
UntaggedValue::int(num)
|
||||||
|
}
|
||||||
|
h if h.starts_with("0x") => {
|
||||||
|
let num = match BigInt::from_str_radix(h.trim_start_matches("0x"), 16) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(reason) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"could not parse as int",
|
||||||
|
reason.to_string(),
|
||||||
|
tag.into().span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
UntaggedValue::int(num)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let num = match BigInt::from_str_radix(trimmed, radix) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(reason) => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"could not parse as int",
|
||||||
|
reason.to_string(),
|
||||||
|
tag.into().span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
UntaggedValue::int(num)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(out.into_value(tag))
|
Ok(out.into_value(tag))
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
@ -114,8 +169,8 @@ fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{action, SubCommand};
|
use super::{action, SubCommand};
|
||||||
use nu_plugin::test_helpers::value::{int, string};
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::{int, string};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
@ -129,15 +184,29 @@ mod tests {
|
|||||||
let word = string("10");
|
let word = string("10");
|
||||||
let expected = int(10);
|
let expected = int(10);
|
||||||
|
|
||||||
let actual = action(&word, Tag::unknown()).unwrap();
|
let actual = action(&word, Tag::unknown(), 10).unwrap();
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn turns_binary_to_integer() {
|
||||||
|
let s = string("0b101");
|
||||||
|
let actual = action(&s, Tag::unknown(), 10).unwrap();
|
||||||
|
assert_eq!(actual, int(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn turns_hex_to_integer() {
|
||||||
|
let s = string("0xFF");
|
||||||
|
let actual = action(&s, Tag::unknown(), 16).unwrap();
|
||||||
|
assert_eq!(actual, int(255));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||||
let integer_str = string("36anra");
|
let integer_str = string("36anra");
|
||||||
|
|
||||||
let actual = action(&integer_str, Tag::unknown());
|
let actual = action(&integer_str, Tag::unknown(), 10);
|
||||||
|
|
||||||
assert!(actual.is_err());
|
assert!(actual.is_err());
|
||||||
}
|
}
|
||||||
|
@ -65,11 +65,9 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{trim, SubCommand};
|
use super::{trim, SubCommand};
|
||||||
use crate::commands::str_::trim::{action, ActionMode};
|
use crate::commands::str_::trim::{action, ActionMode};
|
||||||
use nu_plugin::{
|
use nu_protocol::row;
|
||||||
row,
|
|
||||||
test_helpers::value::{int, string, table},
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::{int, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -66,11 +66,9 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{trim_left, SubCommand};
|
use super::{trim_left, SubCommand};
|
||||||
use crate::commands::str_::trim::{action, ActionMode};
|
use crate::commands::str_::trim::{action, ActionMode};
|
||||||
use nu_plugin::{
|
use nu_protocol::row;
|
||||||
row,
|
|
||||||
test_helpers::value::{int, string, table},
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::{int, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
@ -66,11 +66,9 @@ mod tests {
|
|||||||
use super::ShellError;
|
use super::ShellError;
|
||||||
use super::{trim_right, SubCommand};
|
use super::{trim_right, SubCommand};
|
||||||
use crate::commands::str_::trim::{action, ActionMode};
|
use crate::commands::str_::trim::{action, ActionMode};
|
||||||
use nu_plugin::{
|
use nu_protocol::row;
|
||||||
row,
|
|
||||||
test_helpers::value::{int, string, table},
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
use nu_test_support::value::{int, string, table};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user