mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
134 Commits
Author | SHA1 | Date | |
---|---|---|---|
02d5729941 | |||
ce35689d2e | |||
da81e21bf2 | |||
3b2ed7631f | |||
1a46e70dfb | |||
0fc9b6cfa2 | |||
61768aa2fd | |||
ea5bf9db36 | |||
9d24afcfe3 | |||
033df9457b | |||
d8e105fe34 | |||
d34068da18 | |||
611103d211 | |||
528c1c5fd8 | |||
f73732bf1e | |||
fd7875e572 | |||
e8bc319f08 | |||
a92ff57270 | |||
004230d02d | |||
a148c640b2 | |||
ea0205f2ff | |||
005649b6fc | |||
e09e3b01d6 | |||
fc15e0e27d | |||
b2fe5fabb1 | |||
52d69bb021 | |||
5f550a355b | |||
dbecbdccd4 | |||
734877338d | |||
2e439ca77f | |||
a853880e07 | |||
93f3ed98e1 | |||
a131eddf54 | |||
b19a7aa8a6 | |||
f5aa53c530 | |||
80f5e14512 | |||
41390cd963 | |||
0b5e131410 | |||
556596bce8 | |||
b791d1ab0d | |||
ac070ae942 | |||
111ad868a7 | |||
a7274115d0 | |||
81160bcefb | |||
2880109f31 | |||
09a1f5acb9 | |||
5fcf11fcb0 | |||
42fac722bb | |||
073e5727c6 | |||
ad1c4f5e39 | |||
dc8a68c98f | |||
e5621dea58 | |||
00acf22f5f | |||
4c09716ad8 | |||
1c941557c3 | |||
28e1a7915d | |||
4bc9d9fd3b | |||
e278ca61d1 | |||
2146ede15d | |||
e737222a5d | |||
f03f1949bf | |||
0fe6c7c558 | |||
b13202bbfc | |||
90fae903ce | |||
06b154f4b2 | |||
419a0665c8 | |||
387098fc87 | |||
c42b588782 | |||
4faaa5310e | |||
c448abd44e | |||
2517588d7d | |||
8fc8fc89aa | |||
b243b3ee1d | |||
28e08afada | |||
7e184b58b2 | |||
589fc0b8ad | |||
f0c7c1b500 | |||
840bd98e01 | |||
a5cdd22bfe | |||
0c7bcae9b1 | |||
ab666c170c | |||
d2213d18fa | |||
82b6300dcb | |||
b69cda9e07 | |||
56adc7c3c6 | |||
2ace20fade | |||
c13fe83784 | |||
6cf8df8685 | |||
86a89404be | |||
0d305d7c3e | |||
ee5bd2b4b3 | |||
22ae962b57 | |||
864139d67f | |||
1dc7e00d20 | |||
49a9107e0f | |||
7b8c2c232f | |||
15e1e6376b | |||
06d9d9ed08 | |||
74e10d6f72 | |||
d43489a6a0 | |||
983de8974b | |||
c91a1ec08d | |||
507de45d40 | |||
fe0fc8d5e1 | |||
e4a8db56f9 | |||
1d1ec4727a | |||
0b71e45072 | |||
9c375b33a6 | |||
28a6a5ea57 | |||
f83ff0e47d | |||
079e575cac | |||
6b2327f231 | |||
596608aa0c | |||
120e80d1b6 | |||
aa6c6120f6 | |||
19d5f782cc | |||
84169a91ff | |||
d1c48cdcf9 | |||
dfe95d3ae6 | |||
57ebec385f | |||
7a77910720 | |||
23d8dc959c | |||
7f303a856e | |||
e834e617f3 | |||
2c89a228d5 | |||
803826cdcd | |||
42d18d2294 | |||
b5ae024cc8 | |||
5968811441 | |||
fc59c87606 | |||
7dc1d6a350 | |||
deff1aa63b | |||
08e7d0dfb6 | |||
892aae267d |
@ -2,7 +2,7 @@
|
||||
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)*
|
||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
||||
|
||||
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
|
||||
|
||||
|
1586
Cargo.lock
generated
1586
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
94
Cargo.toml
94
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.27.0"
|
||||
version = "0.30.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,35 +18,36 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.27.0", path = "./crates/nu-cli", default-features = false }
|
||||
nu-command = { version = "0.27.0", path = "./crates/nu-command" }
|
||||
nu-data = { version = "0.27.0", path = "./crates/nu-data" }
|
||||
nu-engine = { version = "0.27.0", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.27.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.27.0", path = "./crates/nu-parser" }
|
||||
nu-plugin = { version = "0.27.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.27.0", path = "./crates/nu-protocol" }
|
||||
nu-source = { version = "0.27.0", path = "./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.27.0", path = "./crates/nu-value-ext" }
|
||||
nu-cli = { version = "0.30.0", path = "./crates/nu-cli", default-features = false }
|
||||
nu-command = { version = "0.30.0", path = "./crates/nu-command" }
|
||||
nu-data = { version = "0.30.0", path = "./crates/nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.30.0", path = "./crates/nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "./crates/nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.30.0", path = "./crates/nu-value-ext" }
|
||||
|
||||
nu_plugin_binaryview = { version = "0.27.0", path = "./crates/nu_plugin_binaryview", optional = true }
|
||||
nu_plugin_chart = { version = "0.27.0", path = "./crates/nu_plugin_chart", optional = true }
|
||||
nu_plugin_fetch = { version = "0.27.0", path = "./crates/nu_plugin_fetch", optional = true }
|
||||
nu_plugin_from_bson = { version = "0.27.0", path = "./crates/nu_plugin_from_bson", optional = true }
|
||||
nu_plugin_from_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
|
||||
nu_plugin_inc = { version = "0.27.0", path = "./crates/nu_plugin_inc", optional = true }
|
||||
nu_plugin_match = { version = "0.27.0", path = "./crates/nu_plugin_match", optional = true }
|
||||
nu_plugin_post = { version = "0.27.0", path = "./crates/nu_plugin_post", optional = true }
|
||||
nu_plugin_ps = { version = "0.27.0", path = "./crates/nu_plugin_ps", optional = true }
|
||||
nu_plugin_s3 = { version = "0.27.0", path = "./crates/nu_plugin_s3", optional = true }
|
||||
nu_plugin_selector = { version = "0.27.0", path = "./crates/nu_plugin_selector", optional = true }
|
||||
nu_plugin_start = { version = "0.27.0", path = "./crates/nu_plugin_start", optional = true }
|
||||
nu_plugin_sys = { version = "0.27.0", path = "./crates/nu_plugin_sys", optional = true }
|
||||
nu_plugin_textview = { version = "0.27.0", path = "./crates/nu_plugin_textview", optional = true }
|
||||
nu_plugin_to_bson = { version = "0.27.0", path = "./crates/nu_plugin_to_bson", optional = true }
|
||||
nu_plugin_to_sqlite = { version = "0.27.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
|
||||
nu_plugin_tree = { version = "0.27.0", path = "./crates/nu_plugin_tree", optional = true }
|
||||
nu_plugin_xpath = { version = "0.27.0", path = "./crates/nu_plugin_xpath", optional = true }
|
||||
nu_plugin_binaryview = { version = "0.30.0", path = "./crates/nu_plugin_binaryview", optional = true }
|
||||
nu_plugin_chart = { version = "0.30.0", path = "./crates/nu_plugin_chart", optional = true }
|
||||
nu_plugin_fetch = { version = "0.30.0", path = "./crates/nu_plugin_fetch", optional = true }
|
||||
nu_plugin_from_bson = { version = "0.30.0", path = "./crates/nu_plugin_from_bson", optional = true }
|
||||
nu_plugin_from_sqlite = { version = "0.30.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
|
||||
nu_plugin_inc = { version = "0.30.0", path = "./crates/nu_plugin_inc", optional = true }
|
||||
nu_plugin_match = { version = "0.30.0", path = "./crates/nu_plugin_match", optional = true }
|
||||
nu_plugin_post = { version = "0.30.0", path = "./crates/nu_plugin_post", optional = true }
|
||||
nu_plugin_ps = { version = "0.30.0", path = "./crates/nu_plugin_ps", optional = true }
|
||||
nu_plugin_query_json = { version = "0.30.0", path = "./crates/nu_plugin_query_json", optional = true }
|
||||
nu_plugin_s3 = { version = "0.30.0", path = "./crates/nu_plugin_s3", optional = true }
|
||||
nu_plugin_selector = { version = "0.30.0", path = "./crates/nu_plugin_selector", optional = true }
|
||||
nu_plugin_start = { version = "0.30.0", path = "./crates/nu_plugin_start", optional = true }
|
||||
nu_plugin_sys = { version = "0.30.0", path = "./crates/nu_plugin_sys", optional = true }
|
||||
nu_plugin_textview = { version = "0.30.0", path = "./crates/nu_plugin_textview", optional = true }
|
||||
nu_plugin_to_bson = { version = "0.30.0", path = "./crates/nu_plugin_to_bson", optional = true }
|
||||
nu_plugin_to_sqlite = { version = "0.30.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
|
||||
nu_plugin_tree = { version = "0.30.0", path = "./crates/nu_plugin_tree", optional = true }
|
||||
nu_plugin_xpath = { version = "0.30.0", path = "./crates/nu_plugin_xpath", optional = true }
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
@ -57,8 +58,11 @@ log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.30.0", path = "./crates/nu-test-support" }
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = { version = "0.27.0", path = "./crates/nu-test-support" }
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@ -82,20 +86,17 @@ which-support = [
|
||||
"nu-cli/which",
|
||||
"nu-command/ichwh",
|
||||
"nu-command/which",
|
||||
"nu-engine/which",
|
||||
]
|
||||
|
||||
default = [
|
||||
"nu-cli/shadow-rs",
|
||||
"sys",
|
||||
"ps",
|
||||
"textview",
|
||||
"inc",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
@ -107,9 +108,13 @@ stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"inc",
|
||||
"tree",
|
||||
"ptree-support",
|
||||
"textview",
|
||||
"clipboard-cli",
|
||||
"trash-support",
|
||||
"uuid-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
@ -117,6 +122,7 @@ extra = [
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
"query-json",
|
||||
]
|
||||
|
||||
wasi = ["inc", "match", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
@ -131,26 +137,35 @@ post = ["nu_plugin_post"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli", "nu-command/clipboard-cli"]
|
||||
query-json = ["nu_plugin_query_json"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
|
||||
trash-support = [
|
||||
"nu-cli/trash-support",
|
||||
"nu-command/trash-support",
|
||||
"nu-engine/trash-support",
|
||||
]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
|
||||
#This is disabled in extra for now
|
||||
table-pager = ["nu-command/table-pager"]
|
||||
|
||||
[profile.release]
|
||||
#strip = "symbols" #Couldn't get working +nightly
|
||||
codegen-units = 1 #Reduce parallel codegen units
|
||||
lto = true #Link Time Optimization
|
||||
opt-level = 'z' #Optimize for size
|
||||
# opt-level = 'z' #Optimize for size
|
||||
# debug = true
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -202,6 +217,11 @@ name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_query_json"
|
||||
path = "src/plugins/nu_plugin_extra_query_json.rs"
|
||||
required-features = ["query-json"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
|
@ -1,11 +1,12 @@
|
||||
# README
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||

|
||||

|
||||
|
||||
## Nushell
|
||||
|
||||
@ -239,7 +240,7 @@ Nu has early support for configuring the shell. You can refer to the book for a
|
||||
To set one of these variables, you can use `config set`. For example:
|
||||
|
||||
```shell
|
||||
> config set edit_mode "vi"
|
||||
> config set line_editor.edit_mode "vi"
|
||||
> config set path $nu.path
|
||||
```
|
||||
|
||||
|
2
crates/nu-ansi-term/.gitignore
vendored
Normal file
2
crates/nu-ansi-term/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
35
crates/nu-ansi-term/Cargo.toml
Normal file
35
crates/nu-ansi-term/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
authors = [
|
||||
"ogham@bsago.me",
|
||||
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
|
||||
"Josh Triplett <josh@joshtriplett.org>",
|
||||
"The Nu Project Contributors",
|
||||
]
|
||||
description = "Library for ANSI terminal colors and styles (bold, underline)"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-ansi-term"
|
||||
version = "0.30.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
# name = "nu-ansi-term"
|
||||
|
||||
[features]
|
||||
derive_serde_style = ["serde"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.90"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
regex = "1.1.9"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.39"
|
21
crates/nu-ansi-term/LICENCE
Normal file
21
crates/nu-ansi-term/LICENCE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Benjamin Sago
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
182
crates/nu-ansi-term/README.md
Normal file
182
crates/nu-ansi-term/README.md
Normal file
@ -0,0 +1,182 @@
|
||||
# nu-ansi-term
|
||||
|
||||
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
|
||||
|
||||
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
nu_ansi_term = "0.13"
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
|
||||
|
||||
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
|
||||
The `Color` enum represents the available colors.
|
||||
And an `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
`Color` is also available as an alias to `Color`.
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
|
||||
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
|
||||
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
|
||||
|
||||
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
let red_string = Red.paint("a red string").to_string();
|
||||
```
|
||||
|
||||
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
|
||||
|
||||
```rust,ignore
|
||||
let enabled = nu_ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
|
||||
Each method creates a new style that has that specific property set.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
|
||||
println!("How about some {} and {}?",
|
||||
Style::new().bold().paint("bold"),
|
||||
Style::new().underline().paint("underline"));
|
||||
```
|
||||
|
||||
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::{Blue, Yellow};
|
||||
|
||||
println!("Demonstrating {} and {}!",
|
||||
Blue.bold().paint("blue bold"),
|
||||
Yellow.underline().paint("yellow underline"));
|
||||
|
||||
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
```
|
||||
|
||||
The complete list of styles you can use are:
|
||||
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
|
||||
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
```
|
||||
|
||||
You can turn a `Color` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
## Extended colors
|
||||
|
||||
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
|
||||
This can be included wherever you would use a `Color`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Fixed;
|
||||
|
||||
Fixed(134).paint("A sort of light purple");
|
||||
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
```
|
||||
|
||||
The first sixteen of these values are the same as the normal and bold standard color variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colors instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::RGB;
|
||||
|
||||
RGB(70, 130, 180).paint("Steel blue");
|
||||
```
|
||||
|
||||
## Combining successive coloured strings
|
||||
|
||||
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
|
||||
For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
|
||||
|
||||
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
|
||||
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
|
||||
|
||||
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
|
||||
let some_value = format!("{:b}", 42);
|
||||
let strings: &[ANSIString<'static>] = &[
|
||||
Red.paint("["),
|
||||
Red.bold().paint(some_value),
|
||||
Red.paint("]"),
|
||||
];
|
||||
|
||||
println!("Value: {}", ANSIStrings(strings));
|
||||
```
|
||||
|
||||
There are several things to note here.
|
||||
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
|
||||
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
|
||||
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
|
||||
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
|
||||
|
||||
## Byte strings
|
||||
|
||||
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
|
||||
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
|
||||
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
|
||||
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
||||
|
||||
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
use nu_ansi_term::ANSIByteStrings;
|
||||
|
||||
ANSIByteStrings(&[
|
||||
Green.paint("user data 1\n".as_bytes()),
|
||||
Green.bold().paint("user data 2\n".as_bytes()),
|
||||
]).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
72
crates/nu-ansi-term/examples/256_colors.rs
Normal file
72
crates/nu-ansi-term/examples/256_colors.rs
Normal file
@ -0,0 +1,72 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
// This example prints out the 256 colors.
|
||||
// They're arranged like this:
|
||||
//
|
||||
// - 0 to 8 are the eight standard colors.
|
||||
// - 9 to 15 are the eight bold colors.
|
||||
// - 16 to 231 are six blocks of six-by-six color squares.
|
||||
// - 232 to 255 are shades of grey.
|
||||
|
||||
fn main() {
|
||||
// First two lines
|
||||
for c in 0..8 {
|
||||
glow(c, c != 0);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 8..16 {
|
||||
glow(c, c != 8);
|
||||
print!(" ");
|
||||
}
|
||||
println!("\n");
|
||||
|
||||
// Six lines of the first three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(16 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// Six more lines of the other three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(124 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// The last greyscale lines
|
||||
for c in 232..=243 {
|
||||
glow(c, false);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 244..=255 {
|
||||
glow(c, true);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn glow(c: u8, light_bg: bool) {
|
||||
let base = if light_bg { Color::Black } else { Color::White };
|
||||
let style = base.on(Color::Fixed(c));
|
||||
print!("{}", style.paint(&format!(" {:3} ", c)));
|
||||
}
|
18
crates/nu-ansi-term/examples/basic_colors.rs
Normal file
18
crates/nu-ansi-term/examples/basic_colors.rs
Normal file
@ -0,0 +1,18 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color::*, Style};
|
||||
|
||||
// This example prints out the 16 basic colors.
|
||||
|
||||
fn main() {
|
||||
let normal = Style::default();
|
||||
|
||||
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
|
||||
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
|
||||
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
|
||||
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
|
||||
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
|
||||
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
|
||||
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
|
||||
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
|
||||
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
|
||||
}
|
23
crates/nu-ansi-term/examples/rgb_colors.rs
Normal file
23
crates/nu-ansi-term/examples/rgb_colors.rs
Normal file
@ -0,0 +1,23 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
// This example prints out a color gradient in a grid by calculating each
|
||||
// character’s red, green, and blue components, and using 24-bit color codes
|
||||
// to display them.
|
||||
|
||||
const WIDTH: i32 = 80;
|
||||
const HEIGHT: i32 = 24;
|
||||
|
||||
fn main() {
|
||||
for row in 0..HEIGHT {
|
||||
for col in 0..WIDTH {
|
||||
let r = (row * 255 / HEIGHT) as u8;
|
||||
let g = (col * 255 / WIDTH) as u8;
|
||||
let b = 128;
|
||||
|
||||
print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" "));
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
405
crates/nu-ansi-term/src/ansi.rs
Normal file
405
crates/nu-ansi-term/src/ansi.rs
Normal file
@ -0,0 +1,405 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::fmt;
|
||||
|
||||
impl Style {
|
||||
/// Write any bytes that go *before* a piece of text to the given writer.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
// If there are actually no styles here, then don’t write *any* codes
|
||||
// as the prefix. An empty ANSI code may not affect the terminal
|
||||
// output at all, but a user may just want a code-free string.
|
||||
if self.is_plain() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Write the codes’ prefix, then write numbers, separated by
|
||||
// semicolons, for each text style we want to apply.
|
||||
write!(f, "\x1B[")?;
|
||||
let mut written_anything = false;
|
||||
|
||||
{
|
||||
let mut write_char = |c| {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
write!(f, "{}", c)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if self.is_bold {
|
||||
write_char('1')?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_char('2')?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_char('3')?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_char('4')?
|
||||
}
|
||||
if self.is_blink {
|
||||
write_char('5')?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_char('7')?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_char('8')?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_char('9')?
|
||||
}
|
||||
}
|
||||
|
||||
// The foreground and background colors, if specified, need to be
|
||||
// handled specially because the number codes are more complicated.
|
||||
// (see `write_background_code` and `write_foreground_code`)
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
bg.write_background_code(f)?;
|
||||
}
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
fg.write_foreground_code(f)?;
|
||||
}
|
||||
|
||||
// All the codes end with an `m`, because reasons.
|
||||
write!(f, "m")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write any bytes that go *after* a piece of text to the given writer.
|
||||
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
if self.is_plain() {
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "{}", RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The code to send to reset all styles and return to `Style::default()`.
|
||||
pub static RESET: &str = "\x1B[0m";
|
||||
|
||||
impl Color {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Color::Black => write!(f, "30"),
|
||||
Color::Red => write!(f, "31"),
|
||||
Color::Green => write!(f, "32"),
|
||||
Color::Yellow => write!(f, "33"),
|
||||
Color::Blue => write!(f, "34"),
|
||||
Color::Purple => write!(f, "35"),
|
||||
Color::Magenta => write!(f, "35"),
|
||||
Color::Cyan => write!(f, "36"),
|
||||
Color::White => write!(f, "37"),
|
||||
Color::Fixed(num) => write!(f, "38;5;{}", &num),
|
||||
Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
|
||||
Color::DarkGray => write!(f, "90"),
|
||||
Color::LightRed => write!(f, "91"),
|
||||
Color::LightGreen => write!(f, "92"),
|
||||
Color::LightYellow => write!(f, "93"),
|
||||
Color::LightBlue => write!(f, "94"),
|
||||
Color::LightPurple => write!(f, "95"),
|
||||
Color::LightMagenta => write!(f, "95"),
|
||||
Color::LightCyan => write!(f, "96"),
|
||||
Color::LightGray => write!(f, "97"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Color::Black => write!(f, "40"),
|
||||
Color::Red => write!(f, "41"),
|
||||
Color::Green => write!(f, "42"),
|
||||
Color::Yellow => write!(f, "43"),
|
||||
Color::Blue => write!(f, "44"),
|
||||
Color::Purple => write!(f, "45"),
|
||||
Color::Magenta => write!(f, "45"),
|
||||
Color::Cyan => write!(f, "46"),
|
||||
Color::White => write!(f, "47"),
|
||||
Color::Fixed(num) => write!(f, "48;5;{}", &num),
|
||||
Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
|
||||
Color::DarkGray => write!(f, "100"),
|
||||
Color::LightRed => write!(f, "101"),
|
||||
Color::LightGreen => write!(f, "102"),
|
||||
Color::LightYellow => write!(f, "103"),
|
||||
Color::LightBlue => write!(f, "104"),
|
||||
Color::LightPurple => write!(f, "105"),
|
||||
Color::LightMagenta => write!(f, "105"),
|
||||
Color::LightCyan => write!(f, "106"),
|
||||
Color::LightGray => write!(f, "107"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::infix`](struct.Style.html#method.infix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
impl Style {
|
||||
/// The prefix bytes for this style. These are the bytes that tell the
|
||||
/// terminal to use a different color or font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Blue};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Blue.bold();
|
||||
/// assert_eq!("\x1b[1;34m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.prefix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix bytes between this style and `next` style. These are the bytes
|
||||
/// that tell the terminal to change the style to `next`. These may include
|
||||
/// a reset followed by the next color and style, depending on the two styles.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[32m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Green.normal();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.infix(style).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Style) -> Infix {
|
||||
Infix(self, next)
|
||||
}
|
||||
|
||||
/// The suffix for this style. These are the bytes that tell the terminal
|
||||
/// to reset back to its normal color and font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Green.normal().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// The prefix bytes for this color as a `Style`. These are the bytes
|
||||
/// that tell the terminal to use a different color or font style.
|
||||
///
|
||||
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Green;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Green.suffix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix bytes between this color and `next` color. These are the bytes
|
||||
/// that tell the terminal to use the `next` color, or to do nothing if
|
||||
/// the two colors are equal.
|
||||
///
|
||||
/// See also [`Style::infix`](struct.Style.html#method.infix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::{Red, Yellow};
|
||||
///
|
||||
/// assert_eq!("\x1b[33m",
|
||||
/// Red.infix(Yellow).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Color) -> Infix {
|
||||
Infix(self.normal(), next.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this color as a `Style`. These are the bytes that
|
||||
/// tell the terminal to reset back to its normal color and font style.
|
||||
///
|
||||
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Purple;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Purple.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self.normal())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use crate::difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
}
|
||||
Difference::Reset => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.1.prefix())
|
||||
}
|
||||
Difference::NoDifference => {
|
||||
Ok(()) // nothing to write
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $style: expr; $input: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($style.paint($input).to_string(), $result.to_string());
|
||||
|
||||
let mut v = Vec::new();
|
||||
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
|
||||
assert_eq!(v.as_slice(), $result.as_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(plain: Style::default(); "text/plain" => "text/plain");
|
||||
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
|
||||
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
|
||||
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
|
||||
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
|
||||
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
|
||||
test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
|
||||
test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
|
||||
test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
|
||||
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
|
||||
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
|
||||
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
|
||||
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
|
||||
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
|
||||
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
|
||||
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
|
||||
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
|
||||
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
|
||||
|
||||
#[test]
|
||||
fn test_infix() {
|
||||
assert_eq!(
|
||||
Style::new().dimmed().infix(Style::new()).to_string(),
|
||||
"\x1B[0m"
|
||||
);
|
||||
assert_eq!(
|
||||
White.dimmed().infix(White.normal()).to_string(),
|
||||
"\x1B[0m\x1B[37m"
|
||||
);
|
||||
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
|
||||
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
|
||||
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
|
||||
}
|
||||
}
|
152
crates/nu-ansi-term/src/debug.rs
Normal file
152
crates/nu-ansi-term/src/debug.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::style::Style;
|
||||
use std::fmt;
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use nu_ansi_term::Color::{Red, Blue};
|
||||
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
|
||||
/// format!("{:?}", Red.on(Blue).bold().italic()));
|
||||
impl fmt::Debug for Style {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
fmt.debug_struct("Style")
|
||||
.field("foreground", &self.foreground)
|
||||
.field("background", &self.background)
|
||||
.field("blink", &self.is_blink)
|
||||
.field("bold", &self.is_bold)
|
||||
.field("dimmed", &self.is_dimmed)
|
||||
.field("hidden", &self.is_hidden)
|
||||
.field("italic", &self.is_italic)
|
||||
.field("reverse", &self.is_reverse)
|
||||
.field("strikethrough", &self.is_strikethrough)
|
||||
.field("underline", &self.is_underline)
|
||||
.finish()
|
||||
} else if self.is_plain() {
|
||||
fmt.write_str("Style {}")
|
||||
} else {
|
||||
fmt.write_str("Style { ")?;
|
||||
|
||||
let mut written_anything = false;
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "fg({:?})", fg)?
|
||||
}
|
||||
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "on({:?})", bg)?
|
||||
}
|
||||
|
||||
{
|
||||
let mut write_flag = |name| {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
fmt.write_str(name)
|
||||
};
|
||||
|
||||
if self.is_blink {
|
||||
write_flag("blink")?
|
||||
}
|
||||
if self.is_bold {
|
||||
write_flag("bold")?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_flag("dimmed")?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_flag("hidden")?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_flag("italic")?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_flag("reverse")?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_flag("strikethrough")?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_flag("underline")?
|
||||
}
|
||||
}
|
||||
|
||||
write!(fmt, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $obj: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, format!("{:?}", $obj));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(empty: style() => "Style {}");
|
||||
test!(bold: style().bold() => "Style { bold }");
|
||||
test!(italic: style().italic() => "Style { italic }");
|
||||
test!(both: style().bold().italic() => "Style { bold, italic }");
|
||||
|
||||
test!(red: Red.normal() => "Style { fg(Red) }");
|
||||
test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }");
|
||||
|
||||
test!(everything:
|
||||
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
|
||||
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
|
||||
|
||||
#[test]
|
||||
fn long_and_detailed() {
|
||||
extern crate regex;
|
||||
let expected_debug = "Style { fg(Blue), bold }";
|
||||
let expected_pretty_repat = r##"(?x)
|
||||
Style\s+\{\s+
|
||||
foreground:\s+Some\(\s+
|
||||
Blue,?\s+
|
||||
\),\s+
|
||||
background:\s+None,\s+
|
||||
blink:\s+false,\s+
|
||||
bold:\s+true,\s+
|
||||
dimmed:\s+false,\s+
|
||||
hidden:\s+false,\s+
|
||||
italic:\s+false,\s+
|
||||
reverse:\s+false,\s+
|
||||
strikethrough:\s+
|
||||
false,\s+
|
||||
underline:\s+false,?\s+
|
||||
\}"##;
|
||||
let re = regex::Regex::new(expected_pretty_repat).unwrap();
|
||||
|
||||
let style = Blue.bold();
|
||||
let style_fmt_debug = format!("{:?}", style);
|
||||
let style_fmt_pretty = format!("{:#?}", style);
|
||||
println!("style_fmt_debug:\n{}", style_fmt_debug);
|
||||
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
|
||||
|
||||
assert_eq!(expected_debug, style_fmt_debug);
|
||||
assert!(re.is_match(&style_fmt_pretty));
|
||||
}
|
||||
}
|
174
crates/nu-ansi-term/src/difference.rs
Normal file
174
crates/nu-ansi-term/src/difference.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use super::Style;
|
||||
|
||||
/// When printing out one colored string followed by another, use one of
|
||||
/// these rules to figure out which *extra* control codes need to be sent.
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Difference {
|
||||
/// Print out the control codes specified by this style to end up looking
|
||||
/// like the second string's styles.
|
||||
ExtraStyles(Style),
|
||||
|
||||
/// Converting between these two is impossible, so just send a reset
|
||||
/// command and then the second string's styles.
|
||||
Reset,
|
||||
|
||||
/// The before style is exactly the same as the after style, so no further
|
||||
/// control codes need to be printed.
|
||||
NoDifference,
|
||||
}
|
||||
|
||||
impl Difference {
|
||||
/// Compute the 'style difference' required to turn an existing style into
|
||||
/// the given, second style.
|
||||
///
|
||||
/// For example, to turn green text into green bold text, it's redundant
|
||||
/// to write a reset command then a second green+bold command, instead of
|
||||
/// just writing one bold command. This method should see that both styles
|
||||
/// use the foreground color green, and reduce it to a single command.
|
||||
///
|
||||
/// This method returns an enum value because it's not actually always
|
||||
/// possible to turn one style into another: for example, text could be
|
||||
/// made bold and underlined, but you can't remove the bold property
|
||||
/// without also removing the underline property. So when this has to
|
||||
/// happen, this function returns None, meaning that the entire set of
|
||||
/// styles should be reset and begun again.
|
||||
pub fn between(first: &Style, next: &Style) -> Difference {
|
||||
use self::Difference::*;
|
||||
|
||||
// XXX(Havvy): This algorithm is kind of hard to replicate without
|
||||
// having the Plain/Foreground enum variants, so I'm just leaving
|
||||
// it commented out for now, and defaulting to Reset.
|
||||
|
||||
if first == next {
|
||||
return NoDifference;
|
||||
}
|
||||
|
||||
// Cannot un-bold, so must Reset.
|
||||
if first.is_bold && !next.is_bold {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_dimmed && !next.is_dimmed {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_italic && !next.is_italic {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot un-underline, so must Reset.
|
||||
if first.is_underline && !next.is_underline {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_blink && !next.is_blink {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_reverse && !next.is_reverse {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_hidden && !next.is_hidden {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_strikethrough && !next.is_strikethrough {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from foreground to no foreground, so must Reset.
|
||||
if first.foreground.is_some() && next.foreground.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from background to no background, so must Reset.
|
||||
if first.background.is_some() && next.background.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
let mut extra_styles = Style::default();
|
||||
|
||||
if first.is_bold != next.is_bold {
|
||||
extra_styles.is_bold = true;
|
||||
}
|
||||
|
||||
if first.is_dimmed != next.is_dimmed {
|
||||
extra_styles.is_dimmed = true;
|
||||
}
|
||||
|
||||
if first.is_italic != next.is_italic {
|
||||
extra_styles.is_italic = true;
|
||||
}
|
||||
|
||||
if first.is_underline != next.is_underline {
|
||||
extra_styles.is_underline = true;
|
||||
}
|
||||
|
||||
if first.is_blink != next.is_blink {
|
||||
extra_styles.is_blink = true;
|
||||
}
|
||||
|
||||
if first.is_reverse != next.is_reverse {
|
||||
extra_styles.is_reverse = true;
|
||||
}
|
||||
|
||||
if first.is_hidden != next.is_hidden {
|
||||
extra_styles.is_hidden = true;
|
||||
}
|
||||
|
||||
if first.is_strikethrough != next.is_strikethrough {
|
||||
extra_styles.is_strikethrough = true;
|
||||
}
|
||||
|
||||
if first.foreground != next.foreground {
|
||||
extra_styles.foreground = next.foreground;
|
||||
}
|
||||
|
||||
if first.background != next.background {
|
||||
extra_styles.background = next.background;
|
||||
}
|
||||
|
||||
ExtraStyles(extra_styles)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Difference::*;
|
||||
use super::*;
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $first: expr; $next: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, Difference::between(&$first, &$next));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(nothing: Green.normal(); Green.normal() => NoDifference);
|
||||
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
|
||||
test!(lowercase: Green.bold(); Green.normal() => Reset);
|
||||
test!(nothing2: Green.bold(); Green.bold() => NoDifference);
|
||||
|
||||
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
|
||||
|
||||
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
|
||||
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
|
||||
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
|
||||
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
|
||||
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
|
||||
|
||||
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
|
||||
test!(removal_of_reverse: style().reverse(); style() => Reset);
|
||||
test!(removal_of_hidden: style().hidden(); style() => Reset);
|
||||
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
|
||||
test!(removal_of_blink: style().blink(); style() => Reset);
|
||||
}
|
303
crates/nu-ansi-term/src/display.rs
Normal file
303
crates/nu-ansi-term/src/display.rs
Normal file
@ -0,0 +1,303 @@
|
||||
use crate::ansi::RESET;
|
||||
use crate::difference::Difference;
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// An `ANSIGenericString` includes a generic string type and a `Style` to
|
||||
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
|
||||
/// this type on `str` and `\[u8]`, respectively.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
style: Style,
|
||||
string: Cow<'a, S>,
|
||||
}
|
||||
|
||||
/// Cloning an `ANSIGenericString` will clone its underlying string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// let clone_string = plain_string.clone();
|
||||
/// assert_eq!(clone_string, plain_string);
|
||||
/// ```
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn clone(&self) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
style: self.style,
|
||||
string: self.string.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You might think that the hand-written Clone impl above is the same as the
|
||||
// one that gets generated with #[derive]. But it’s not *quite* the same!
|
||||
//
|
||||
// `str` is not Clone, and the derived Clone implementation puts a Clone
|
||||
// constraint on the S type parameter (generated using --pretty=expanded):
|
||||
//
|
||||
// ↓_________________↓
|
||||
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
|
||||
// for ANSIGenericString<'a, S> where
|
||||
// <S as ToOwned>::Owned: fmt::Debug { ... }
|
||||
//
|
||||
// This resulted in compile errors when you tried to derive Clone on a type
|
||||
// that used it:
|
||||
//
|
||||
// #[derive(PartialEq, Debug, Clone, Default)]
|
||||
// pub struct TextCellContents(Vec<ANSIString<'static>>);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
|
||||
//
|
||||
// The hand-written impl above can ignore that constraint and still compile.
|
||||
|
||||
/// An ANSI String is a string coupled with the `Style` to display it
|
||||
/// in a terminal.
|
||||
///
|
||||
/// Although not technically a string itself, it can be turned into
|
||||
/// one with the `to_string` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
/// use nu_ansi_term::Color::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// assert_eq!(&*plain_string, "a plain string");
|
||||
/// ```
|
||||
pub type AnsiString<'a> = AnsiGenericString<'a, str>;
|
||||
|
||||
/// An `AnsiByteString` represents a formatted series of bytes. Use
|
||||
/// `AnsiByteString` when styling text with an unknown encoding.
|
||||
pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>;
|
||||
|
||||
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn from(input: I) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
/// Directly access the style
|
||||
pub fn style_ref(&self) -> &Style {
|
||||
&self.style
|
||||
}
|
||||
|
||||
/// Directly access the style mutably
|
||||
pub fn style_ref_mut(&mut self) -> &mut Style {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.string.deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of `AnsiGenericStrings`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>])
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
S: PartialEq;
|
||||
|
||||
/// A set of `AnsiString`s collected together, in order to be written with a
|
||||
/// minimum of control characters.
|
||||
pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>;
|
||||
|
||||
/// A function to construct an `AnsiStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
/// A set of `AnsiByteString`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>;
|
||||
|
||||
/// A function to construct an `ANSIByteStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
// ---- paint functions ----
|
||||
|
||||
impl Style {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for individual ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let w: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteString<'a> {
|
||||
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
|
||||
/// sequences for the associated `Style` around the bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
write!(w, "{}", self.style.prefix())?;
|
||||
w.write_str(self.string.as_ref())?;
|
||||
write!(w, "{}", self.style.suffix())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for combined ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiStrings<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteStrings<'a> {
|
||||
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
|
||||
/// escape sequences for the associated `Style`s around each set of
|
||||
/// bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
use self::Difference::*;
|
||||
|
||||
let first = match self.0.first() {
|
||||
None => return Ok(()),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
write!(w, "{}", first.style.prefix())?;
|
||||
w.write_str(first.string.as_ref())?;
|
||||
|
||||
for window in self.0.windows(2) {
|
||||
match Difference::between(&window[0].style, &window[1].style) {
|
||||
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
|
||||
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
|
||||
NoDifference => { /* Do nothing! */ }
|
||||
}
|
||||
|
||||
w.write_str(&window[1].string)?;
|
||||
}
|
||||
|
||||
// Write the final reset string after all of the ANSIStrings have been
|
||||
// written, *except* if the last one has no styles, because it would
|
||||
// have already been written by this point.
|
||||
if let Some(last) = self.0.last() {
|
||||
if !last.style.is_plain() {
|
||||
write!(w, "{}", RESET)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tests ----
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub use super::super::AnsiStrings;
|
||||
pub use crate::style::Color::*;
|
||||
pub use crate::style::Style;
|
||||
|
||||
#[test]
|
||||
fn no_control_codes_for_plain() {
|
||||
let one = Style::default().paint("one");
|
||||
let two = Style::default().paint("two");
|
||||
let output = format!("{}", AnsiStrings(&[one, two]));
|
||||
assert_eq!(&*output, "onetwo");
|
||||
}
|
||||
}
|
267
crates/nu-ansi-term/src/lib.rs
Normal file
267
crates/nu-ansi-term/src/lib.rs
Normal file
@ -0,0 +1,267 @@
|
||||
//! This is a library for controlling colors and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are three main types in this crate that you need to be
|
||||
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
|
||||
//!
|
||||
//! A `Style` holds stylistic information: foreground and background colors,
|
||||
//! whether the text should be bold, or blinking, or other properties. The
|
||||
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
|
||||
//! string paired with a [`Style`].
|
||||
//!
|
||||
//! [`Color`] is also available as an alias to `Color`.
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Color`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//! ```
|
||||
//!
|
||||
//! It’s important to note that the `paint` method does *not* actually return a
|
||||
//! string with the ANSI control characters surrounding it. Instead, it returns
|
||||
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
|
||||
//! formatted, returns the characters. This allows strings to be printed with a
|
||||
//! minimum of [`String`] allocations being performed behind the scenes.
|
||||
//!
|
||||
//! If you *do* want to get at the escape codes, then you can convert the
|
||||
//! [`ANSIString`] to a string as you would any other `Display` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground color changes, you need to
|
||||
//! construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
//! You can do this by chaining methods based on a new `Style`, created with
|
||||
//! [`Style::new()`]. Each method creates a new style that has that specific
|
||||
//! property set. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//!
|
||||
//! println!("How about some {} and {}?",
|
||||
//! Style::new().bold().paint("bold"),
|
||||
//! Style::new().underline().paint("underline"));
|
||||
//! ```
|
||||
//!
|
||||
//! For brevity, these methods have also been implemented for `Color` values,
|
||||
//! so you can give your styles a foreground color without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::{Blue, Yellow};
|
||||
//!
|
||||
//! println!("Demonstrating {} and {}!",
|
||||
//! Blue.bold().paint("blue bold"),
|
||||
//! Yellow.underline().paint("yellow underline"));
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
|
||||
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
|
||||
//! background colors.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Color`.
|
||||
//! You can do this using the [`fg`] method:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! You can turn a `Color` into a `Style` with the [`normal`] method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Color` directly, but it’s useful in certain cases:
|
||||
//! for example, you may have a method that returns `Styles`, and need to
|
||||
//! represent both the “red bold” and “red, but not bold” styles with values of
|
||||
//! the same type. The `Style` struct also has a [`Default`] implementation if you
|
||||
//! want to have a style with *nothing* set.
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colors
|
||||
//!
|
||||
//! You can access the extended range of 256 colors by using the `Color::Fixed`
|
||||
//! variant, which takes an argument of the color number to use. This can be
|
||||
//! included wherever you would use a `Color`:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Fixed;
|
||||
//!
|
||||
//! Fixed(134).paint("A sort of light purple");
|
||||
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
//! ```
|
||||
//!
|
||||
//! The first sixteen of these values are the same as the normal and bold
|
||||
//! standard color variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `Color::RGB` variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::RGB;
|
||||
//!
|
||||
//! RGB(70, 130, 180).paint("Steel blue");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Combining successive colored strings
|
||||
//!
|
||||
//! The benefit of writing ANSI escape codes to the terminal is that they
|
||||
//! *stack*: you do not need to end every colored string with a reset code if
|
||||
//! the text that follows it is of a similar style. For example, if you want to
|
||||
//! have some blue text followed by some blue bold text, it’s possible to send
|
||||
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
|
||||
//! with a reset code without having to have an extra one between the two
|
||||
//! strings.
|
||||
//!
|
||||
//! This crate can optimise the ANSI codes that get printed in situations like
|
||||
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
|
||||
//! type takes a slice of several [`ANSIString`] values, and will iterate over
|
||||
//! each of them, printing only the codes for the styles that need to be updated
|
||||
//! as part of its formatting routine.
|
||||
//!
|
||||
//! The following code snippet uses this to enclose a binary number displayed in
|
||||
//! red bold text inside some red, but not bold, brackets:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//! use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
//!
|
||||
//! let some_value = format!("{:b}", 42);
|
||||
//! let strings: &[ANSIString<'static>] = &[
|
||||
//! Red.paint("["),
|
||||
//! Red.bold().paint(some_value),
|
||||
//! Red.paint("]"),
|
||||
//! ];
|
||||
//!
|
||||
//! println!("Value: {}", ANSIStrings(strings));
|
||||
//! ```
|
||||
//!
|
||||
//! There are several things to note here. Firstly, the [`paint`] method can take
|
||||
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
|
||||
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
|
||||
//! borrowed strings at the same time. This is used here to display a `String`,
|
||||
//! the result of the `format!` call, using the same mechanism as some
|
||||
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
|
||||
//! works in the same way as its singular counterpart, with a [`Display`]
|
||||
//! implementation that only performs the formatting when required.
|
||||
//!
|
||||
//! ## Byte strings
|
||||
//!
|
||||
//! This library also supports formatting `\[u8]` byte strings; this supports
|
||||
//! applications working with text in an unknown encoding. [`Style`] and
|
||||
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
|
||||
//! This type does not implement [`Display`], as it may not contain UTF-8, but
|
||||
//! it does provide a method [`write_to`] to write the result to any value that
|
||||
//! implements [`Write`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//!
|
||||
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
|
||||
//! [`ANSIByteString`] values with minimal escape sequences:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//! use nu_ansi_term::ANSIByteStrings;
|
||||
//!
|
||||
//! ANSIByteStrings(&[
|
||||
//! Green.paint("user data 1\n".as_bytes()),
|
||||
//! Green.bold().paint("user data 2\n".as_bytes()),
|
||||
//! ]).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
|
||||
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
|
||||
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
|
||||
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
//! [`Style`]: struct.Style.html
|
||||
//! [`Style::new()`]: struct.Style.html#method.new
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`ANSIString`]: type.ANSIString.html
|
||||
//! [`ANSIStrings`]: type.ANSIStrings.html
|
||||
//! [`ANSIByteString`]: type.ANSIByteString.html
|
||||
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
|
||||
//! [`write_to`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`paint`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`normal`]: enum.Color.html#method.normal
|
||||
//!
|
||||
//! [`bold`]: struct.Style.html#method.bold
|
||||
//! [`dimmed`]: struct.Style.html#method.dimmed
|
||||
//! [`italic`]: struct.Style.html#method.italic
|
||||
//! [`underline`]: struct.Style.html#method.underline
|
||||
//! [`blink`]: struct.Style.html#method.blink
|
||||
//! [`reverse`]: struct.Style.html#method.reverse
|
||||
//! [`hidden`]: struct.Style.html#method.hidden
|
||||
//! [`strikethrough`]: struct.Style.html#method.strikethrough
|
||||
//! [`fg`]: struct.Style.html#method.fg
|
||||
//! [`on`]: struct.Style.html#method.on
|
||||
|
||||
#![crate_name = "nu_ansi_term"]
|
||||
#![crate_type = "rlib"]
|
||||
#![crate_type = "dylib"]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate doc_comment;
|
||||
|
||||
#[cfg(test)]
|
||||
doctest!("../README.md");
|
||||
|
||||
pub mod ansi;
|
||||
pub use ansi::{Infix, Prefix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Color, Style};
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod util;
|
||||
pub use util::*;
|
||||
|
||||
mod debug;
|
620
crates/nu-ansi-term/src/style.rs
Normal file
620
crates/nu-ansi-term/src/style.rs
Normal file
@ -0,0 +1,620 @@
|
||||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().bold().on(Color::Black);
|
||||
/// println!("{}", style.paint("Bold on black"));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub struct Style {
|
||||
/// The style's foreground color, if it has one.
|
||||
pub foreground: Option<Color>,
|
||||
|
||||
/// The style's background color, if it has one.
|
||||
pub background: Option<Color>,
|
||||
|
||||
/// Whether this style is bold.
|
||||
pub is_bold: bool,
|
||||
|
||||
/// Whether this style is dimmed.
|
||||
pub is_dimmed: bool,
|
||||
|
||||
/// Whether this style is italic.
|
||||
pub is_italic: bool,
|
||||
|
||||
/// Whether this style is underlined.
|
||||
pub is_underline: bool,
|
||||
|
||||
/// Whether this style is blinking.
|
||||
pub is_blink: bool,
|
||||
|
||||
/// Whether this style has reverse colors.
|
||||
pub is_reverse: bool,
|
||||
|
||||
/// Whether this style is hidden.
|
||||
pub is_hidden: bool,
|
||||
|
||||
/// Whether this style is struckthrough.
|
||||
pub is_strikethrough: bool,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Creates a new Style with no properties set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(&self) -> Style {
|
||||
Style {
|
||||
is_bold: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style {
|
||||
is_dimmed: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(&self) -> Style {
|
||||
Style {
|
||||
is_italic: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(&self) -> Style {
|
||||
Style {
|
||||
is_underline: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(&self) -> Style {
|
||||
Style {
|
||||
is_blink: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style {
|
||||
is_reverse: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style {
|
||||
is_hidden: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style {
|
||||
is_strikethrough: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().fg(Color::Yellow);
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn fg(&self, foreground: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(foreground),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().on(Color::Blue);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(&self, background: Color) -> Style {
|
||||
Style {
|
||||
background: Some(background),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// assert_eq!(true, Style::default().is_plain());
|
||||
/// assert_eq!(false, Style::default().bold().is_plain());
|
||||
/// ```
|
||||
pub fn is_plain(self) -> bool {
|
||||
self == Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
/// Returns a style with *no* properties set. Formatting text using this
|
||||
/// style returns the exact same text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
/// assert_eq!(None, Style::default().foreground);
|
||||
/// assert_eq!(None, Style::default().background);
|
||||
/// assert_eq!(false, Style::default().is_bold);
|
||||
/// assert_eq!("txt", Style::default().paint("txt").to_string());
|
||||
/// ```
|
||||
fn default() -> Style {
|
||||
Style {
|
||||
foreground: None,
|
||||
background: None,
|
||||
is_bold: false,
|
||||
is_dimmed: false,
|
||||
is_italic: false,
|
||||
is_underline: false,
|
||||
is_blink: false,
|
||||
is_reverse: false,
|
||||
is_hidden: false,
|
||||
is_strikethrough: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- colors ----
|
||||
|
||||
/// A color is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background color.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub enum Color {
|
||||
/// Color #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background color, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Color #0 (foreground code `90`, background code `100`).
|
||||
DarkGray,
|
||||
|
||||
/// Color #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Color #1 (foreground code `91`, background code `101`).
|
||||
LightRed,
|
||||
|
||||
/// Color #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Color #2 (foreground code `92`, background code `102`).
|
||||
LightGreen,
|
||||
|
||||
/// Color #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Color #3 (foreground code `93`, background code `103`).
|
||||
LightYellow,
|
||||
|
||||
/// Color #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Color #4 (foreground code `94`, background code `104`).
|
||||
LightBlue,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightPurple,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Magenta,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightMagenta,
|
||||
|
||||
/// Color #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Color #6 (foreground code `96`, background code `106`).
|
||||
LightCyan,
|
||||
|
||||
/// Color #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground color, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// Color #7 (foreground code `97`, background code `107`).
|
||||
LightGray,
|
||||
|
||||
/// A color number from 0 to 255, for use in 256-color terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colors can usually be changed in the terminal emulator.
|
||||
/// - colors 8 to 15 are brighter versions of the eight colors above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colors and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - colors 16 to 231 contain several palettes of bright colors,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - colors 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [color chart][cc].
|
||||
///
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit RGB color, as specified by ISO-8613-3.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Returns a `Style` with the foreground color set to this color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Red.normal();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn normal(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Green.bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_bold: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Yellow.dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_dimmed: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Blue.italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_italic: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Purple.underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_underline: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// blink property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Cyan.blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_blink: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Black.reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_reverse: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::White.hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_hidden: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Fixed(244).strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_strikethrough: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// background color property set to the given color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::RGB(31, 31, 31).on(Color::White);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(self, background: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
background: Some(background),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
/// You can turn a `Color` into a `Style` with the foreground color set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
/// let green_foreground = Style::default().fg(Color::Green);
|
||||
/// assert_eq!(green_foreground, Color::Green.normal());
|
||||
/// assert_eq!(green_foreground, Color::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Color::Green));
|
||||
/// ```
|
||||
fn from(color: Color) -> Style {
|
||||
color.normal()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "derive_serde_style")]
|
||||
mod serde_json_tests {
|
||||
use super::{Color, Style};
|
||||
|
||||
#[test]
|
||||
fn color_serialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::RGB(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&colors).unwrap(),
|
||||
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_deserialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::RGB(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
for color in colors.into_iter() {
|
||||
let serialized = serde_json::to_string(&color).unwrap();
|
||||
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!(color, &deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_serialization() {
|
||||
let style = Style::default();
|
||||
|
||||
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
|
||||
}
|
||||
}
|
80
crates/nu-ansi-term/src/util.rs
Normal file
80
crates/nu-ansi-term/src/util.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::display::{AnsiString, AnsiStrings};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
|
||||
pub fn sub_string<'a>(
|
||||
start: usize,
|
||||
len: usize,
|
||||
strs: &AnsiStrings<'a>,
|
||||
) -> Vec<AnsiString<'static>> {
|
||||
let mut vec = Vec::new();
|
||||
let mut pos = start;
|
||||
let mut len_rem = len;
|
||||
|
||||
for i in strs.0.iter() {
|
||||
let fragment = i.deref();
|
||||
let frag_len = fragment.len();
|
||||
if pos >= frag_len {
|
||||
pos -= frag_len;
|
||||
continue;
|
||||
}
|
||||
if len_rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let end = pos + len_rem;
|
||||
let pos_end = if end >= frag_len { frag_len } else { end };
|
||||
|
||||
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
|
||||
|
||||
if end <= frag_len {
|
||||
break;
|
||||
}
|
||||
|
||||
len_rem -= pos_end - pos;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
|
||||
pub fn unstyle(strs: &AnsiStrings) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
for i in strs.0.iter() {
|
||||
s += &i.deref();
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
|
||||
pub fn unstyled_len(strs: &AnsiStrings) -> usize {
|
||||
let mut l = 0;
|
||||
for i in strs.0.iter() {
|
||||
l += i.deref().len();
|
||||
}
|
||||
l
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Color::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let l = [
|
||||
Black.paint("first"),
|
||||
Red.paint("-second"),
|
||||
White.paint("-third"),
|
||||
];
|
||||
let a = AnsiStrings(&l);
|
||||
assert_eq!(unstyle(&a), "first-second-third");
|
||||
assert_eq!(unstyled_len(&a), 18);
|
||||
|
||||
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
|
||||
assert_eq!(sub_string(3, 11, &a).as_slice(), &l2);
|
||||
}
|
||||
}
|
62
crates/nu-ansi-term/src/windows.rs
Normal file
62
crates/nu-ansi-term/src/windows.rs
Normal file
@ -0,0 +1,62 @@
|
||||
/// Enables ANSI code support on Windows 10.
|
||||
///
|
||||
/// This uses Windows API calls to alter the properties of the console that
|
||||
/// the program is running in.
|
||||
///
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
|
||||
///
|
||||
/// Returns a `Result` with the Windows error code if unsuccessful.
|
||||
#[cfg(windows)]
|
||||
pub fn enable_ansi_support() -> Result<(), u32> {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::null_mut;
|
||||
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
|
||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
|
||||
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
unsafe {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
|
||||
let console_out_name: Vec<u16> =
|
||||
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
|
||||
let console_handle = CreateFileW(
|
||||
console_out_name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_WRITE,
|
||||
null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
if console_handle == INVALID_HANDLE_VALUE {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut console_mode: u32 = 0;
|
||||
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// VT processing not already enabled?
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
if 0 == SetConsoleMode(
|
||||
console_handle,
|
||||
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
37
crates/nu-ansi-term/src/write.rs
Normal file
37
crates/nu-ansi-term/src/write.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
pub trait AnyWrite {
|
||||
type Wstr: ?Sized;
|
||||
type Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn fmt::Write + 'a {
|
||||
type Wstr = str;
|
||||
type Error = fmt::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_str(self, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn io::Write + 'a {
|
||||
type Wstr = [u8];
|
||||
type Error = io::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
io::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
io::Write::write_all(self, s)
|
||||
}
|
||||
}
|
@ -5,28 +5,28 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.27.0"
|
||||
version = "0.30.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-command = { version = "0.27.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.27.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.27.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.27.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.27.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.27.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.27.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.27.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.27.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.27.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.27.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.27.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.27.0", path = "../nu-value-ext" }
|
||||
nu-command = { version = "0.30.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.30.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.30.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.30.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.30.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.30.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.30.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.30.0", path = "../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.30.0", path = "../nu-ansi-term" }
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
async-recursion = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
@ -77,7 +77,7 @@ rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "6.3.0", optional = true }
|
||||
rustyline = { version = "8.0.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
|
@ -1,12 +1,9 @@
|
||||
use crate::line_editor::configure_ctrl_c;
|
||||
use nu_command::commands::default_context::create_default_context;
|
||||
#[allow(unused_imports)]
|
||||
use nu_command::maybe_print_errors;
|
||||
use nu_engine::run_block;
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_command::script::{process_script, LineResult};
|
||||
pub(crate) use nu_engine::script::{process_script, LineResult};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::line_editor::{
|
||||
@ -18,23 +15,85 @@ use crate::line_editor::{
|
||||
use nu_data::config;
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_stream::InputStream;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use nu_command::script::{print_err, run_script_standalone};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{self, error::ReadlineError};
|
||||
|
||||
use crate::EnvironmentSyncer;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::{hir::ExternalRedirection, UntaggedValue, Value};
|
||||
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
|
||||
|
||||
use log::trace;
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Options {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
|
||||
let text = content
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: text,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
@ -64,73 +123,85 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub async fn run_script_file(
|
||||
file_contents: String,
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(false)?;
|
||||
let config = syncer.get_config();
|
||||
|
||||
context.configure(&config, |_, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
pub fn run_script_file(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
|
||||
let _ = register_plugins(ctx);
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
});
|
||||
let _ = register_plugins(&context);
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
let script = options
|
||||
.scripts
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
||||
|
||||
run_script_standalone(file_contents, redirect_stdin, &context, true).await?;
|
||||
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let configuration = syncer.get_config();
|
||||
pub fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
|
||||
let startup_commands_start_time = std::time::Instant::now();
|
||||
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION",
|
||||
format!("{:?}", startup_commands_start_time.elapsed()),
|
||||
);
|
||||
trace!(
|
||||
"startup commands took {:?}",
|
||||
startup_commands_start_time.elapsed()
|
||||
);
|
||||
|
||||
//Configure rustyline
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
let history_path = if let Some(cfg) = &context.configs.lock().global_config {
|
||||
let _ = configure_rustyline_editor(&mut rl, cfg);
|
||||
let helper = Some(nu_line_editor_helper(&context, cfg));
|
||||
rl.set_helper(helper);
|
||||
nu_data::config::path::history_path_or_default(cfg)
|
||||
} else {
|
||||
nu_data::config::path::default_history_path()
|
||||
};
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
// Don't load history if it's not necessary
|
||||
if options.save_history {
|
||||
let _ = rl.load_history(&history_path);
|
||||
}
|
||||
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
//set vars from cfg if present
|
||||
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs.lock().global_config {
|
||||
(
|
||||
cfg.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false),
|
||||
cfg.var("prompt"),
|
||||
)
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||
rl.set_helper(helper);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
//Check whether dir we start in contains local cfg file and if so load it.
|
||||
load_local_cfg_if_present(&context);
|
||||
|
||||
// Give ourselves a scope to work in
|
||||
context.scope.enter_scope();
|
||||
|
||||
let history_path = nu_engine::history_path(&configuration);
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let mut session_text = String::new();
|
||||
let mut line_start: usize = 0;
|
||||
|
||||
let skip_welcome_message = configuration
|
||||
.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
@ -140,7 +211,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
let _ = nu_ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
let mut ctrlcbreak = false;
|
||||
@ -154,26 +225,36 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = configuration.var("prompt") {
|
||||
if let Some(prompt) = &prompt {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
context.scope.enter_scope();
|
||||
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
||||
|
||||
prompt_block.set_redirect(ExternalRedirection::Stdout);
|
||||
if let Some(block) =
|
||||
std::sync::Arc::<nu_protocol::hir::Block>::get_mut(&mut prompt_block)
|
||||
{
|
||||
block.set_redirect(ExternalRedirection::Stdout);
|
||||
}
|
||||
|
||||
if err.is_some() {
|
||||
context.scope.exit_scope();
|
||||
|
||||
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
|
||||
format!(
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix().to_string(),
|
||||
cwd,
|
||||
nu_ansi_term::ansi::RESET,
|
||||
Color::Cyan.bold().prefix().to_string(),
|
||||
current_branch(),
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
} else {
|
||||
// let env = context.get_env();
|
||||
|
||||
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
|
||||
let run_result = run_block(&prompt_block, &context, InputStream::empty());
|
||||
context.scope.exit_scope();
|
||||
|
||||
match run_result {
|
||||
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
||||
Ok(result) => match result.collect_string(Tag::unknown()) {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
maybe_print_errors(&context, Text::from(prompt_line));
|
||||
@ -186,14 +267,14 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.host.lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
crate::cli::print_err(e, &Text::from(prompt_line));
|
||||
context.host.lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".to_string()
|
||||
@ -201,7 +282,15 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
|
||||
format!(
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix().to_string(),
|
||||
cwd,
|
||||
nu_ansi_term::ansi::RESET,
|
||||
Color::Cyan.bold().prefix().to_string(),
|
||||
current_branch(),
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@ -227,65 +316,69 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
session_text.push('\n');
|
||||
}
|
||||
|
||||
// start time for command duration
|
||||
let cmd_start_time = std::time::Instant::now();
|
||||
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(_) => {
|
||||
process_script(
|
||||
LineResult::Success(_) => process_script(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
),
|
||||
x => x,
|
||||
};
|
||||
|
||||
// Check the config to see if we need to update the path
|
||||
// TODO: make sure config is cached so we don't path this load every call
|
||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
if syncer.did_config_change() {
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
}
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
});
|
||||
// Store cmd duration in an env var
|
||||
context
|
||||
.scope
|
||||
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed()));
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
if options.save_history {
|
||||
rl.clear_history();
|
||||
let _ = rl.save_history(&history_path);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(session_text.clone()));
|
||||
});
|
||||
context
|
||||
.host
|
||||
.lock()
|
||||
.print_err(err, &Text::from(session_text.clone()));
|
||||
|
||||
// I am not so sure, we don't need maybe_print_errors here (as we printed an err
|
||||
// above), because maybe_print_errors also clears the errors.
|
||||
// TODO Analyze where above err comes from, and whether we need to clear
|
||||
// context.errors here
|
||||
// Or just be consistent and return errors always in context.errors...
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = config::config(Tag::unknown())?
|
||||
.get("ctrlc_exit")
|
||||
.map(|s| s.value.is_true())
|
||||
let config_ctrlc_exit = context
|
||||
.configs
|
||||
.lock()
|
||||
.global_config
|
||||
.as_ref()
|
||||
.map(|cfg| cfg.var("ctrlc_exit"))
|
||||
.flatten()
|
||||
.map(|ctrl_c| ctrl_c.is_true())
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
@ -293,7 +386,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
let _ = rl.save_history(&history_path);
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
@ -317,12 +412,49 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
let _ = rl.save_history(&history_path);
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||
pub fn load_local_cfg_if_present(context: &EvaluationContext) {
|
||||
trace!("Loading local cfg if present");
|
||||
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager.path())) {
|
||||
Ok(Some(cfg_path)) => {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
|
||||
context.host.lock().print_err(err, &Text::from(""))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
//Report error while checking for local cfg file
|
||||
context.host.lock().print_err(e, &Text::from(""))
|
||||
}
|
||||
Ok(None) => {
|
||||
//No local cfg file present in start dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
|
||||
context.host.lock().print_err(err, &Text::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_global_cfg(context: &EvaluationContext) {
|
||||
match config::default_path() {
|
||||
Ok(path) => {
|
||||
load_cfg_as_global_cfg(context, path);
|
||||
}
|
||||
Err(e) => {
|
||||
context.host.lock().print_err(e, &Text::from(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_plugins(context: &EvaluationContext) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
@ -335,35 +467,7 @@ pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellErro
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_startup_commands(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(commands) = config.var("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
let mut script_file = String::new();
|
||||
for pipeline in pipelines {
|
||||
script_file.push_str(&pipeline.as_string()?);
|
||||
script_file.push('\n');
|
||||
}
|
||||
let _ = run_script_standalone(script_file, false, context, false).await;
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"expected a table of pipeline strings as startup commands",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
||||
pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
||||
// FIXME: do we still need this?
|
||||
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||
s
|
||||
@ -380,13 +484,11 @@ pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<Strin
|
||||
}
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
ctx.scope.add_env(env);
|
||||
|
||||
let result = run_block(&classified_block, ctx, input_stream).await;
|
||||
let result = run_block(&classified_block, ctx, input_stream);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
result?.collect_string(Tag::unknown()).await.map(|x| x.item)
|
||||
result?.collect_string(Tag::unknown()).map(|x| x.item)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -254,6 +254,8 @@ pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec<Complet
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
use nu_parser::{classify_block, lex, parse_block, ParserScope};
|
||||
@ -285,9 +287,9 @@ mod tests {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn add_definition(&self, _block: Block) {}
|
||||
fn add_definition(&self, _block: Arc<Block>) {}
|
||||
|
||||
fn get_definitions(&self) -> Vec<Block> {
|
||||
fn get_definitions(&self) -> Vec<Arc<Block>> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ impl matchers::Matcher for Matcher {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: check some unicode matches if this becomes relevant
|
||||
// TODO: check some Unicode matches if this becomes relevant
|
||||
|
||||
// FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test
|
||||
#[test]
|
||||
|
@ -15,7 +15,8 @@ pub struct PathSuggestion {
|
||||
impl PathCompleter {
|
||||
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
||||
let expanded = nu_parser::expand_ndots(partial);
|
||||
let expanded = expanded.as_ref();
|
||||
let expanded = expanded.replace(std::path::is_separator, &SEP.to_string());
|
||||
let expanded: &str = expanded.as_ref();
|
||||
|
||||
let (base_dir_name, partial) = match expanded.rfind(SEP) {
|
||||
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
|
||||
|
@ -1,3 +0,0 @@
|
||||
pub(crate) mod directory_specific_environment;
|
||||
pub(crate) mod environment;
|
||||
pub(crate) mod environment_syncer;
|
@ -1,259 +0,0 @@
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use nu_command::commands::autoenv;
|
||||
use nu_errors::ShellError;
|
||||
use serde::Deserialize;
|
||||
use std::env::*;
|
||||
use std::process::Command;
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt::Debug,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
//Tests reside in /nushell/tests/shell/pipeline/commands/internal.rs
|
||||
|
||||
type EnvKey = String;
|
||||
type EnvVal = OsString;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DirectorySpecificEnvironment {
|
||||
pub last_seen_directory: PathBuf,
|
||||
//If an environment var has been added from a .nu in a directory, we track it here so we can remove it when the user leaves the directory.
|
||||
//If setting the var overwrote some value, we save the old value in an option so we can restore it later.
|
||||
added_vars: IndexMap<PathBuf, IndexMap<EnvKey, Option<EnvVal>>>,
|
||||
|
||||
//We track directories that we have read .nu-env from. This is different from the keys in added_vars since sometimes a file only wants to run scripts.
|
||||
visited_dirs: IndexSet<PathBuf>,
|
||||
exitscripts: IndexMap<PathBuf, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct NuEnvDoc {
|
||||
pub env: Option<IndexMap<String, String>>,
|
||||
pub scriptvars: Option<IndexMap<String, String>>,
|
||||
pub scripts: Option<IndexMap<String, Vec<String>>>,
|
||||
pub entryscripts: Option<Vec<String>>,
|
||||
pub exitscripts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl DirectorySpecificEnvironment {
|
||||
pub fn new() -> DirectorySpecificEnvironment {
|
||||
let root_dir = if cfg!(target_os = "windows") {
|
||||
PathBuf::from("c:\\")
|
||||
} else {
|
||||
PathBuf::from("/")
|
||||
};
|
||||
DirectorySpecificEnvironment {
|
||||
last_seen_directory: root_dir,
|
||||
added_vars: IndexMap::new(),
|
||||
visited_dirs: IndexSet::new(),
|
||||
exitscripts: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn toml_if_trusted(&mut self, nu_env_file: &Path) -> Result<NuEnvDoc, ShellError> {
|
||||
let content = std::fs::read(&nu_env_file)?;
|
||||
|
||||
if autoenv::file_is_trusted(&nu_env_file, &content)? {
|
||||
let mut doc: NuEnvDoc = toml::de::from_slice(&content)
|
||||
.map_err(|e| ShellError::untagged_runtime_error(format!("{:?}", e)))?;
|
||||
|
||||
if let Some(scripts) = doc.scripts.as_ref() {
|
||||
for (k, v) in scripts {
|
||||
if k == "entryscripts" {
|
||||
doc.entryscripts = Some(v.clone());
|
||||
} else if k == "exitscripts" {
|
||||
doc.exitscripts = Some(v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(doc);
|
||||
}
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
format!("{:?} is untrusted. Run 'autoenv trust {:?}' to trust it.\nThis needs to be done after each change to the file.", nu_env_file, nu_env_file.parent().unwrap_or_else(|| &Path::new("")))))
|
||||
}
|
||||
|
||||
pub fn maintain_autoenv(&mut self) -> Result<(), ShellError> {
|
||||
let mut dir = current_dir()?;
|
||||
|
||||
if self.last_seen_directory == dir {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
//We track which keys we set as we go up the directory hierarchy, so that we don't overwrite a value we set in a subdir.
|
||||
let mut added_keys = IndexSet::new();
|
||||
|
||||
let mut new_visited_dirs = IndexSet::new();
|
||||
let mut popped = true;
|
||||
while popped {
|
||||
let nu_env_file = dir.join(".nu-env");
|
||||
if nu_env_file.exists() && !self.visited_dirs.contains(&dir) {
|
||||
let nu_env_doc = self.toml_if_trusted(&nu_env_file)?;
|
||||
|
||||
//add regular variables from the [env section]
|
||||
if let Some(env) = nu_env_doc.env {
|
||||
for (env_key, env_val) in env {
|
||||
self.maybe_add_key(&mut added_keys, &dir, &env_key, &env_val);
|
||||
}
|
||||
}
|
||||
|
||||
//Add variables that need to evaluate scripts to run, from [scriptvars] section
|
||||
if let Some(sv) = nu_env_doc.scriptvars {
|
||||
for (key, script) in sv {
|
||||
self.maybe_add_key(
|
||||
&mut added_keys,
|
||||
&dir,
|
||||
&key,
|
||||
value_from_script(&script)?.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(es) = nu_env_doc.entryscripts {
|
||||
for s in es {
|
||||
run(s.as_str(), None)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(es) = nu_env_doc.exitscripts {
|
||||
self.exitscripts.insert(dir.clone(), es);
|
||||
}
|
||||
}
|
||||
new_visited_dirs.insert(dir.clone());
|
||||
popped = dir.pop();
|
||||
}
|
||||
|
||||
//Time to clear out vars set by directories that we have left.
|
||||
let mut new_vars = IndexMap::new();
|
||||
for (dir, dirmap) in self.added_vars.drain(..) {
|
||||
if new_visited_dirs.contains(&dir) {
|
||||
new_vars.insert(dir, dirmap);
|
||||
} else {
|
||||
for (k, v) in dirmap {
|
||||
if let Some(v) = v {
|
||||
std::env::set_var(k, v);
|
||||
} else {
|
||||
std::env::remove_var(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Run exitscripts, can not be done in same loop as new vars as some files can contain only exitscripts
|
||||
let mut new_exitscripts = IndexMap::new();
|
||||
for (dir, scripts) in self.exitscripts.drain(..) {
|
||||
if new_visited_dirs.contains(&dir) {
|
||||
new_exitscripts.insert(dir, scripts);
|
||||
} else {
|
||||
for s in scripts {
|
||||
run(s.as_str(), Some(&dir))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.visited_dirs = new_visited_dirs;
|
||||
self.exitscripts = new_exitscripts;
|
||||
self.added_vars = new_vars;
|
||||
self.last_seen_directory = current_dir()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn maybe_add_key(
|
||||
&mut self,
|
||||
seen_vars: &mut IndexSet<EnvKey>,
|
||||
dir: &Path,
|
||||
key: &str,
|
||||
val: &str,
|
||||
) {
|
||||
//This condition is to make sure variables in parent directories don't overwrite variables set by subdirectories.
|
||||
if !seen_vars.contains(key) {
|
||||
seen_vars.insert(key.to_string());
|
||||
self.added_vars
|
||||
.entry(PathBuf::from(dir))
|
||||
.or_insert(IndexMap::new())
|
||||
.insert(key.to_string(), var_os(key));
|
||||
|
||||
std::env::set_var(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user recently ran autoenv untrust on a file, we clear the environment variables it set and make sure to not run any possible exitscripts.
|
||||
pub fn clear_recently_untrusted_file(&mut self) -> Result<(), ShellError> {
|
||||
// Figure out which file was untrusted
|
||||
// Remove all vars set by it
|
||||
let current_trusted_files: IndexSet<PathBuf> = autoenv::read_trusted()?
|
||||
.files
|
||||
.iter()
|
||||
.map(|(k, _)| PathBuf::from(k))
|
||||
.collect();
|
||||
|
||||
// We figure out which file(s) the user untrusted by taking the set difference of current trusted files in .config/nu/nu-env.toml and the files tracked by self.added_env_vars
|
||||
// If a file is in self.added_env_vars but not in nu-env.toml, it was just untrusted.
|
||||
let untrusted_files: IndexSet<PathBuf> = self
|
||||
.added_vars
|
||||
.iter()
|
||||
.filter_map(|(path, _)| {
|
||||
if !current_trusted_files.contains(path) {
|
||||
return Some(path.clone());
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
for path in untrusted_files {
|
||||
if let Some(added_keys) = self.added_vars.get(&path) {
|
||||
for (key, _) in added_keys {
|
||||
remove_var(key);
|
||||
}
|
||||
}
|
||||
self.exitscripts.remove(&path);
|
||||
self.added_vars.remove(&path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
|
||||
if cfg!(target_os = "windows") {
|
||||
if let Some(dir) = dir {
|
||||
let command = format!("cd {} & {}", dir.to_string_lossy(), cmd);
|
||||
Command::new("cmd")
|
||||
.args(&["/C", command.as_str()])
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("cmd").args(&["/C", cmd]).output()?
|
||||
}
|
||||
} else if let Some(dir) = dir {
|
||||
// FIXME: When nu scripting is added, cding like might not be a good idea. If nu decides to execute entryscripts when entering the dir this way, it will cause troubles.
|
||||
// For now only standard shell scripts are used, so this is an issue for the future.
|
||||
Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("cd {:?}; {}", dir, cmd))
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(&cmd).output()?
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn value_from_script(cmd: &str) -> Result<String, ShellError> {
|
||||
let command = if cfg!(target_os = "windows") {
|
||||
Command::new("cmd").args(&["/C", cmd]).output()?
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(&cmd).output()?
|
||||
};
|
||||
if command.stdout.is_empty() {
|
||||
return Err(ShellError::untagged_runtime_error(format!(
|
||||
"{:?} did not return any output",
|
||||
cmd
|
||||
)));
|
||||
}
|
||||
let response = std::str::from_utf8(&command.stdout[..command.stdout.len()]).map_err(|e| {
|
||||
ShellError::untagged_runtime_error(format!(
|
||||
"Couldn't parse stdout from command {:?}: {:?}",
|
||||
command, e
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(response.trim().to_string())
|
||||
}
|
274
crates/nu-cli/src/env/environment.rs
vendored
274
crates/nu-cli/src/env/environment.rs
vendored
@ -1,274 +0,0 @@
|
||||
use crate::env::directory_specific_environment::*;
|
||||
use indexmap::{indexmap, IndexSet};
|
||||
use nu_data::config::Conf;
|
||||
use nu_engine::Env;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Environment {
|
||||
environment_vars: Option<Value>,
|
||||
path_vars: Option<Value>,
|
||||
pub autoenv: DirectorySpecificEnvironment,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Environment {
|
||||
Environment {
|
||||
environment_vars: None,
|
||||
path_vars: None,
|
||||
autoenv: DirectorySpecificEnvironment::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
|
||||
let env = configuration.env();
|
||||
let path = configuration.path();
|
||||
Environment {
|
||||
environment_vars: env,
|
||||
path_vars: path,
|
||||
autoenv: DirectorySpecificEnvironment::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoenv(&mut self, reload_trusted: bool) -> Result<(), ShellError> {
|
||||
self.autoenv.maintain_autoenv()?;
|
||||
if reload_trusted {
|
||||
self.autoenv.clear_recently_untrusted_file()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn morph<T: Conf>(&mut self, configuration: &T) {
|
||||
self.environment_vars = configuration.env();
|
||||
self.path_vars = configuration.path();
|
||||
}
|
||||
}
|
||||
|
||||
impl Env for Environment {
|
||||
fn env(&self) -> Option<Value> {
|
||||
if let Some(vars) = &self.environment_vars {
|
||||
return Some(vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
if let Some(vars) = &self.path_vars {
|
||||
return Some(vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str) {
|
||||
let value = UntaggedValue::string(value);
|
||||
|
||||
let new_envs = {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Row(ref envs),
|
||||
ref tag,
|
||||
}) = self.environment_vars
|
||||
{
|
||||
let mut new_envs = envs.clone();
|
||||
|
||||
if !new_envs.contains_key(key) {
|
||||
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Row(new_envs),
|
||||
tag: tag.clone(),
|
||||
}
|
||||
} else {
|
||||
UntaggedValue::Row(indexmap! { key.into() => value.into_untagged_value() }.into())
|
||||
.into_untagged_value()
|
||||
}
|
||||
};
|
||||
|
||||
self.environment_vars = Some(new_envs);
|
||||
}
|
||||
|
||||
fn add_path(&mut self, paths: std::ffi::OsString) {
|
||||
let new_paths = {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(ref current_paths),
|
||||
ref tag,
|
||||
}) = self.path_vars
|
||||
{
|
||||
let mut new_paths = current_paths.clone();
|
||||
|
||||
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
|
||||
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
|
||||
});
|
||||
|
||||
new_paths.extend(new_path_candidates);
|
||||
|
||||
let paths: IndexSet<Value> = new_paths.into_iter().collect();
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Table(paths.into_iter().collect()),
|
||||
tag: tag.clone(),
|
||||
}
|
||||
} else {
|
||||
let p = paths.into_string().unwrap_or_else(|_| String::from(""));
|
||||
let p = UntaggedValue::string(p).into_untagged_value();
|
||||
UntaggedValue::Table(vec![p]).into_untagged_value()
|
||||
}
|
||||
};
|
||||
|
||||
self.path_vars = Some(new_paths);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Env, Environment};
|
||||
use nu_data::config::{tests::FakeConfig, Conf};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
||||
#[test]
|
||||
fn picks_up_environment_variables_from_configuration() {
|
||||
Playground::setup("environment_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
mosquetero_1 = "Andrés N. Robalino"
|
||||
mosquetero_2 = "Jonathan Turner"
|
||||
mosquetero_3 = "Yehuda katz"
|
||||
mosquetero_4 = "Jason Gedge"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let actual = Environment::from_config(&fake_config);
|
||||
|
||||
assert_eq!(actual.env(), fake_config.env());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picks_up_path_variables_from_configuration() {
|
||||
Playground::setup("environment_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let actual = Environment::from_config(&fake_config);
|
||||
|
||||
assert_eq!(actual.path(), fake_config.path());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_env_variable() {
|
||||
Playground::setup("environment_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = Environment::from_config(&fake_config);
|
||||
|
||||
actual.add_env("USER", "NUNO");
|
||||
|
||||
assert_eq!(
|
||||
actual.env(),
|
||||
Some(
|
||||
UntaggedValue::row(
|
||||
indexmap! {
|
||||
"USER".into() => UntaggedValue::string("NUNO").into_untagged_value(),
|
||||
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
|
||||
}
|
||||
).into_untagged_value()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_update_env_variable_if_it_exists() {
|
||||
Playground::setup("environment_test_4", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = Environment::from_config(&fake_config);
|
||||
|
||||
actual.add_env("SHELL", "/usr/bin/sh");
|
||||
|
||||
assert_eq!(
|
||||
actual.env(),
|
||||
Some(
|
||||
UntaggedValue::row(
|
||||
indexmap! {
|
||||
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
|
||||
}
|
||||
).into_untagged_value()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_path_variable() {
|
||||
Playground::setup("environment_test_5", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = Environment::from_config(&fake_config);
|
||||
|
||||
actual.add_path(std::ffi::OsString::from("/path/to/be/added"));
|
||||
|
||||
assert_eq!(
|
||||
actual.path(),
|
||||
Some(
|
||||
UntaggedValue::table(&[
|
||||
UntaggedValue::string("/Users/andresrobalino/.volta/bin")
|
||||
.into_untagged_value(),
|
||||
UntaggedValue::string("/users/mosqueteros/bin").into_untagged_value(),
|
||||
UntaggedValue::string("/path/to/be/added").into_untagged_value(),
|
||||
])
|
||||
.into_untagged_value()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
449
crates/nu-cli/src/env/environment_syncer.rs
vendored
449
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,449 +0,0 @@
|
||||
use crate::env::environment::Environment;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_engine::Env;
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_errors::ShellError;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
|
||||
pub struct EnvironmentSyncer {
|
||||
pub env: Arc<Mutex<Box<Environment>>>,
|
||||
pub config: Arc<Mutex<Box<dyn Conf>>>,
|
||||
}
|
||||
|
||||
impl Default for EnvironmentSyncer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvironmentSyncer {
|
||||
pub fn with_config(config: Box<dyn Conf>) -> Self {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> EnvironmentSyncer {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_config(&mut self, config: Box<dyn Conf>) {
|
||||
self.config = Arc::new(Mutex::new(config));
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Box<dyn Conf> {
|
||||
let config = self.config.lock();
|
||||
|
||||
config.clone_box()
|
||||
}
|
||||
|
||||
pub fn load_environment(&mut self) {
|
||||
let config = self.config.lock();
|
||||
|
||||
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
|
||||
}
|
||||
|
||||
pub fn did_config_change(&mut self) -> bool {
|
||||
let config = self.config.lock();
|
||||
config.is_modified().unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
let mut config = self.config.lock();
|
||||
config.reload();
|
||||
|
||||
let mut environment = self.env.lock();
|
||||
environment.morph(&*config);
|
||||
}
|
||||
|
||||
pub fn autoenv(&self, ctx: &mut EvaluationContext) -> Result<(), ShellError> {
|
||||
let mut environment = self.env.lock();
|
||||
let recently_used = ctx
|
||||
.user_recently_used_autoenv_untrust
|
||||
.load(Ordering::SeqCst);
|
||||
let auto = environment.autoenv(recently_used);
|
||||
ctx.user_recently_used_autoenv_untrust
|
||||
.store(false, Ordering::SeqCst);
|
||||
auto
|
||||
}
|
||||
|
||||
pub fn sync_env_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
let nu_env_vars = ctx.scope.get_env_vars();
|
||||
|
||||
let environment = self.env.lock();
|
||||
if let Some(variables) = environment.env() {
|
||||
for var in variables.row_entries() {
|
||||
if let Ok(string) = var.1.as_string() {
|
||||
if var.0 != "path" && var.0 != "PATH" && !nu_env_vars.contains_key(var.0) {
|
||||
ctx.scope.add_env_var(var.0, string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nu_env_vars = ctx.scope.get_env_vars();
|
||||
|
||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||
if name != "path" && name != "PATH" && !nu_env_vars.contains_key(&name) {
|
||||
ctx.scope.add_env_var(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
let native_paths = ctx.with_host(|host| host.env_get(std::ffi::OsString::from("PATH")));
|
||||
|
||||
if let Some(native_paths) = native_paths {
|
||||
for path in std::env::split_paths(&native_paths) {
|
||||
environment.add_path(path.as_os_str().to_os_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_paths) = environment.path() {
|
||||
let prepared = std::env::join_paths(
|
||||
new_paths
|
||||
.table_entries()
|
||||
.map(|p| p.as_string())
|
||||
.filter_map(Result::ok),
|
||||
);
|
||||
|
||||
if let Ok(paths_ready) = prepared {
|
||||
ctx.scope
|
||||
.add_env_var("PATH", paths_ready.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn clear_env_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
for (key, _value) in ctx.with_host(|host| host.vars()) {
|
||||
if key != "path" && key != "PATH" {
|
||||
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn clear_path_var(&mut self, ctx: &mut EvaluationContext) {
|
||||
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from("PATH")));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EnvironmentSyncer;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::config::tests::FakeConfig;
|
||||
use nu_engine::basic_evaluation_context;
|
||||
use nu_errors::ShellError;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
// This test fails on Linux.
|
||||
// It's possible it has something to do with the fake configuration
|
||||
// TODO: More tests.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
);
|
||||
|
||||
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
),
|
||||
FileWithContent(
|
||||
"updated_configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
USER = "NUNO"
|
||||
"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let file = dirs.test().join("configuration.toml");
|
||||
let new_file = dirs.test().join("updated_configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
// Nu loads the environment variables from the configuration file
|
||||
actual.load_environment();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
assert!(!actual.did_config_change());
|
||||
|
||||
// Replacing the newer configuration file to the existing one.
|
||||
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
|
||||
std::fs::write(&file, &new_config_contents).expect("Failed");
|
||||
|
||||
// A change has happened
|
||||
assert!(actual.did_config_change());
|
||||
|
||||
// Syncer should reload and add new envs
|
||||
actual.reload();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
let env_vars = ctx.scope.get_env_vars();
|
||||
let result = env_vars.get("SHELL").unwrap();
|
||||
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
|
||||
|
||||
let result = env_vars.get("USER").unwrap();
|
||||
assert_eq!(result, "NUNO");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
);
|
||||
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||
|
||||
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::new();
|
||||
actual.set_config(Box::new(fake_config));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file (if any)
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
// We explicitly simulate and add the USER variable to the current
|
||||
// session's environment variables with the value "NUNO".
|
||||
ctx.with_host(|test_host| {
|
||||
test_host.env_set(
|
||||
std::ffi::OsString::from("USER"),
|
||||
std::ffi::OsString::from("NUNO"),
|
||||
)
|
||||
});
|
||||
|
||||
// Nu loads the environment variables from the configuration file (if any)
|
||||
actual.load_environment();
|
||||
|
||||
// By this point, Nu has already loaded the environment variables
|
||||
// stored in the configuration file. Before continuing we check
|
||||
// if any new environment variables have been added from the ones loaded
|
||||
// in the configuration file.
|
||||
//
|
||||
// Nu sees the missing "USER" variable and accounts for it.
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
let env_vars = ctx.scope.get_env_vars();
|
||||
let result = env_vars.get("SHELL").unwrap();
|
||||
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
|
||||
|
||||
let result = env_vars.get("USER").unwrap();
|
||||
assert_eq!(result, "NUNO");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::new();
|
||||
actual.set_config(Box::new(fake_config));
|
||||
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
ctx.with_host(|test_host| {
|
||||
test_host.env_set(
|
||||
std::ffi::OsString::from("SHELL"),
|
||||
std::ffi::OsString::from("/usr/bin/sh"),
|
||||
)
|
||||
});
|
||||
|
||||
actual.load_environment();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
let env_vars = ctx.scope.get_env_vars();
|
||||
let result = env_vars.get("SHELL").unwrap();
|
||||
|
||||
assert_eq!(result, "/usr/bin/you_already_made_the_nu_choice");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
|
||||
) -> Result<(), ShellError> {
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let expected = std::env::join_paths(vec![
|
||||
PathBuf::from("/Users/andresrobalino/.volta/bin"),
|
||||
PathBuf::from("/Users/mosqueteros/bin"),
|
||||
PathBuf::from("/path/to/be/added"),
|
||||
])
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
Playground::setup("syncs_path_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::new();
|
||||
actual.set_config(Box::new(fake_config));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file (if any)
|
||||
actual.clear_path_var(&mut ctx);
|
||||
|
||||
// We explicitly simulate and add the PATH variable to the current
|
||||
// session with the path "/path/to/be/added".
|
||||
ctx.with_host(|test_host| {
|
||||
test_host.env_set(
|
||||
std::ffi::OsString::from("PATH"),
|
||||
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
|
||||
.expect("Couldn't join paths."),
|
||||
)
|
||||
});
|
||||
|
||||
// Nu loads the path variables from the configuration file (if any)
|
||||
actual.load_environment();
|
||||
|
||||
// By this point, Nu has already loaded environment path variable
|
||||
// stored in the configuration file. Before continuing we check
|
||||
// if any new paths have been added from the ones loaded in the
|
||||
// configuration file.
|
||||
//
|
||||
// Nu sees the missing "/path/to/be/added" and accounts for it.
|
||||
actual.sync_path_vars(&mut ctx);
|
||||
|
||||
let env_vars = ctx.scope.get_env_vars();
|
||||
let paths = env_vars.get("PATH").unwrap();
|
||||
|
||||
assert_eq!(paths, &expected);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
|
||||
) -> Result<(), ShellError> {
|
||||
let mut ctx = basic_evaluation_context()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
|
||||
|
||||
let expected = std::env::join_paths(vec![
|
||||
PathBuf::from("/Users/andresrobalino/.volta/bin"),
|
||||
PathBuf::from("/Users/mosqueteros/bin"),
|
||||
PathBuf::from("/path/to/be/added"),
|
||||
])
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
Playground::setup("syncs_path_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let mut file = dirs.test().clone();
|
||||
file.push("configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::new();
|
||||
actual.set_config(Box::new(fake_config));
|
||||
|
||||
actual.clear_path_var(&mut ctx);
|
||||
|
||||
ctx.with_host(|test_host| {
|
||||
test_host.env_set(
|
||||
std::ffi::OsString::from("PATH"),
|
||||
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
|
||||
.expect("Couldn't join paths."),
|
||||
)
|
||||
});
|
||||
|
||||
actual.load_environment();
|
||||
actual.sync_path_vars(&mut ctx);
|
||||
|
||||
let env_vars = ctx.scope.get_env_vars();
|
||||
let paths = env_vars.get("PATH").unwrap();
|
||||
|
||||
assert_eq!(paths, &expected);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,38 +1,64 @@
|
||||
use rustyline::{KeyCode, Modifiers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn convert_keypress(keypress: KeyPress) -> rustyline::KeyPress {
|
||||
match keypress {
|
||||
KeyPress::UnknownEscSeq => rustyline::KeyPress::UnknownEscSeq,
|
||||
KeyPress::Backspace => rustyline::KeyPress::Backspace,
|
||||
KeyPress::BackTab => rustyline::KeyPress::BackTab,
|
||||
KeyPress::BracketedPasteStart => rustyline::KeyPress::BracketedPasteStart,
|
||||
KeyPress::BracketedPasteEnd => rustyline::KeyPress::BracketedPasteEnd,
|
||||
KeyPress::Char(c) => rustyline::KeyPress::Char(c),
|
||||
KeyPress::ControlDown => rustyline::KeyPress::ControlDown,
|
||||
KeyPress::ControlLeft => rustyline::KeyPress::ControlLeft,
|
||||
KeyPress::ControlRight => rustyline::KeyPress::ControlRight,
|
||||
KeyPress::ControlUp => rustyline::KeyPress::ControlUp,
|
||||
KeyPress::Ctrl(c) => rustyline::KeyPress::Ctrl(c),
|
||||
KeyPress::Delete => rustyline::KeyPress::Delete,
|
||||
KeyPress::Down => rustyline::KeyPress::Down,
|
||||
KeyPress::End => rustyline::KeyPress::End,
|
||||
KeyPress::Enter => rustyline::KeyPress::Enter,
|
||||
KeyPress::Esc => rustyline::KeyPress::Esc,
|
||||
KeyPress::F(u) => rustyline::KeyPress::F(u),
|
||||
KeyPress::Home => rustyline::KeyPress::Home,
|
||||
KeyPress::Insert => rustyline::KeyPress::Insert,
|
||||
KeyPress::Left => rustyline::KeyPress::Left,
|
||||
KeyPress::Meta(c) => rustyline::KeyPress::Meta(c),
|
||||
KeyPress::Null => rustyline::KeyPress::Null,
|
||||
KeyPress::PageDown => rustyline::KeyPress::PageDown,
|
||||
KeyPress::PageUp => rustyline::KeyPress::PageUp,
|
||||
KeyPress::Right => rustyline::KeyPress::Right,
|
||||
KeyPress::ShiftDown => rustyline::KeyPress::ShiftDown,
|
||||
KeyPress::ShiftLeft => rustyline::KeyPress::ShiftLeft,
|
||||
KeyPress::ShiftRight => rustyline::KeyPress::ShiftRight,
|
||||
KeyPress::ShiftUp => rustyline::KeyPress::ShiftUp,
|
||||
KeyPress::Tab => rustyline::KeyPress::Tab,
|
||||
KeyPress::Up => rustyline::KeyPress::Up,
|
||||
pub fn convert_keyevent(key_event: KeyEvent) -> rustyline::KeyEvent {
|
||||
match key_event {
|
||||
KeyEvent::UnknownEscSeq => convert_to_rl_keyevent(rustyline::KeyCode::UnknownEscSeq, None),
|
||||
KeyEvent::Backspace => convert_to_rl_keyevent(rustyline::KeyCode::Backspace, None),
|
||||
KeyEvent::BackTab => convert_to_rl_keyevent(rustyline::KeyCode::BackTab, None),
|
||||
KeyEvent::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteStart, None)
|
||||
}
|
||||
KeyEvent::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteEnd, None)
|
||||
}
|
||||
KeyEvent::Char(c) => convert_to_rl_keyevent(rustyline::KeyCode::Char(c), None),
|
||||
KeyEvent::ControlDown => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlLeft => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlRight => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::ControlUp => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::CTRL))
|
||||
}
|
||||
KeyEvent::Ctrl(c) => rustyline::KeyEvent::ctrl(c),
|
||||
KeyEvent::Delete => convert_to_rl_keyevent(rustyline::KeyCode::Delete, None),
|
||||
KeyEvent::Down => convert_to_rl_keyevent(rustyline::KeyCode::Down, None),
|
||||
KeyEvent::End => convert_to_rl_keyevent(rustyline::KeyCode::End, None),
|
||||
KeyEvent::Enter => convert_to_rl_keyevent(rustyline::KeyCode::Enter, None),
|
||||
KeyEvent::Esc => convert_to_rl_keyevent(rustyline::KeyCode::Esc, None),
|
||||
KeyEvent::F(u) => convert_to_rl_keyevent(rustyline::KeyCode::F(u), None),
|
||||
KeyEvent::Home => convert_to_rl_keyevent(rustyline::KeyCode::Home, None),
|
||||
KeyEvent::Insert => convert_to_rl_keyevent(rustyline::KeyCode::Insert, None),
|
||||
KeyEvent::Left => convert_to_rl_keyevent(rustyline::KeyCode::Left, None),
|
||||
KeyEvent::Meta(c) => rustyline::KeyEvent::new(c, Modifiers::NONE),
|
||||
KeyEvent::Null => convert_to_rl_keyevent(rustyline::KeyCode::Null, None),
|
||||
KeyEvent::PageDown => convert_to_rl_keyevent(rustyline::KeyCode::PageDown, None),
|
||||
KeyEvent::PageUp => convert_to_rl_keyevent(rustyline::KeyCode::PageUp, None),
|
||||
KeyEvent::Right => convert_to_rl_keyevent(rustyline::KeyCode::Right, None),
|
||||
KeyEvent::ShiftDown => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftLeft => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftRight => {
|
||||
convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::SHIFT))
|
||||
}
|
||||
KeyEvent::ShiftUp => convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::SHIFT)),
|
||||
KeyEvent::Tab => convert_to_rl_keyevent(rustyline::KeyCode::Tab, None),
|
||||
KeyEvent::Up => convert_to_rl_keyevent(rustyline::KeyCode::Up, None),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_rl_keyevent(key_event: KeyCode, modifier: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
rustyline::KeyEvent {
|
||||
0: key_event,
|
||||
1: modifier.unwrap_or(Modifiers::NONE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +123,9 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
match cmd {
|
||||
Cmd::Abort => rustyline::Cmd::Abort,
|
||||
Cmd::AcceptLine => rustyline::Cmd::AcceptLine,
|
||||
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine,
|
||||
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine {
|
||||
accept_in_the_middle: false,
|
||||
},
|
||||
Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory,
|
||||
Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord,
|
||||
Cmd::ClearScreen => rustyline::Cmd::ClearScreen,
|
||||
@ -140,18 +168,18 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyPress, rustyline::Cmd) {
|
||||
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
|
||||
(
|
||||
convert_keypress(keybinding.key),
|
||||
convert_keyevent(keybinding.key),
|
||||
convert_cmd(keybinding.binding),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum KeyPress {
|
||||
pub enum KeyEvent {
|
||||
/// Unsupported escape sequence (on unix platform)
|
||||
UnknownEscSeq,
|
||||
/// ⌫ or `KeyPress::Ctrl('H')`
|
||||
/// ⌫ or `KeyEvent::Ctrl('H')`
|
||||
Backspace,
|
||||
/// ⇤ (usually Shift-Tab)
|
||||
BackTab,
|
||||
@ -177,9 +205,9 @@ pub enum KeyPress {
|
||||
Down,
|
||||
/// ⇲
|
||||
End,
|
||||
/// ↵ or `KeyPress::Ctrl('M')`
|
||||
/// ↵ or `KeyEvent::Ctrl('M')`
|
||||
Enter,
|
||||
/// Escape or `KeyPress::Ctrl('[')`
|
||||
/// Escape or `KeyEvent::Ctrl('[')`
|
||||
Esc,
|
||||
/// Function key
|
||||
F(u8),
|
||||
@ -191,7 +219,7 @@ pub enum KeyPress {
|
||||
Left,
|
||||
/// Escape-char or Alt-char
|
||||
Meta(char),
|
||||
/// `KeyPress::Char('\0')`
|
||||
/// `KeyEvent::Char('\0')`
|
||||
Null,
|
||||
/// ⇟
|
||||
PageDown,
|
||||
@ -207,7 +235,7 @@ pub enum KeyPress {
|
||||
ShiftRight,
|
||||
/// Shift-↑
|
||||
ShiftUp,
|
||||
/// ⇥ or `KeyPress::Ctrl('I')`
|
||||
/// ⇥ or `KeyEvent::Ctrl('I')`
|
||||
Tab,
|
||||
/// ↑ arrow key
|
||||
Up,
|
||||
@ -399,7 +427,7 @@ pub type RepeatCount = usize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Keybinding {
|
||||
key: KeyPress,
|
||||
key: KeyEvent,
|
||||
binding: Cmd,
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
#![recursion_limit = "2048"]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate indexmap;
|
||||
|
||||
#[macro_use]
|
||||
mod prelude;
|
||||
|
||||
@ -16,7 +12,6 @@ extern crate quickcheck_macros;
|
||||
mod cli;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod completion;
|
||||
mod env;
|
||||
mod format;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
@ -28,14 +23,14 @@ pub mod types;
|
||||
pub use crate::cli::cli;
|
||||
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
pub use crate::cli::{NuScript, Options};
|
||||
|
||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||
pub use nu_command::commands::default_context::create_default_context;
|
||||
pub use nu_data::config;
|
||||
pub use nu_data::dict::TaggedListBuilder;
|
||||
pub use nu_data::primitive;
|
||||
pub use nu_data::value;
|
||||
pub use nu_stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub use nu_stream::{ActionStream, InputStream, InterruptibleStream};
|
||||
pub use nu_value_ext::ValueExt;
|
||||
pub use num_traits::cast::ToPrimitive;
|
||||
|
||||
|
@ -5,7 +5,10 @@ use std::error::Error;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_command::script::LineResult;
|
||||
use nu_engine::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::keybinding::{convert_keyevent, KeyEvent};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
@ -16,7 +19,7 @@ use rustyline::{
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||
At, Cmd, Editor, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
@ -40,22 +43,27 @@ pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let config = Config::builder()
|
||||
.check_cursor_position(true)
|
||||
.color_mode(ColorMode::Forced)
|
||||
.build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
convert_keyevent(KeyEvent::ControlLeft),
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
convert_keyevent(KeyEvent::ControlRight),
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
|
||||
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyEvent::BracketedPasteStart),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
@ -194,7 +202,7 @@ pub fn configure_rustyline_editor(
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn nu_line_editor_helper(
|
||||
context: &mut EvaluationContext,
|
||||
context: &EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
@ -216,7 +224,7 @@ pub fn rustyline_hinter(
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
@ -8,25 +8,10 @@ macro_rules! return_err {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! stream {
|
||||
($($expr:expr),*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
|
||||
$(
|
||||
v.push_back($expr);
|
||||
)*
|
||||
|
||||
v
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_out_stream {
|
||||
(target: $target:tt, $desc:tt = $expr:expr) => {{
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
@ -46,13 +31,12 @@ macro_rules! trace_out_stream {
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use futures::{Stream, StreamExt};
|
||||
pub(crate) use nu_engine::Host;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_errors::ShellError;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_protocol::outln;
|
||||
pub(crate) use nu_stream::OutputStream;
|
||||
pub(crate) use nu_stream::ActionStream;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_value_ext::ValueExt;
|
||||
#[allow(unused_imports)]
|
||||
@ -60,33 +44,16 @@ pub(crate) use std::sync::atomic::Ordering;
|
||||
|
||||
#[allow(clippy::clippy::wrong_self_convention)]
|
||||
pub trait FromInputStream {
|
||||
fn from_input_stream(self) -> OutputStream;
|
||||
fn from_input_stream(self) -> ActionStream;
|
||||
}
|
||||
|
||||
impl<T> FromInputStream for T
|
||||
where
|
||||
T: Stream<Item = nu_protocol::Value> + Send + 'static,
|
||||
T: Iterator<Item = nu_protocol::Value> + Send + Sync + 'static,
|
||||
{
|
||||
fn from_input_stream(self) -> OutputStream {
|
||||
OutputStream {
|
||||
values: self.map(nu_protocol::ReturnSuccess::value).boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::clippy::wrong_self_convention)]
|
||||
pub trait ToOutputStream {
|
||||
fn to_output_stream(self) -> OutputStream;
|
||||
}
|
||||
|
||||
impl<T, U> ToOutputStream for T
|
||||
where
|
||||
T: Stream<Item = U> + Send + 'static,
|
||||
U: Into<nu_protocol::ReturnValue>,
|
||||
{
|
||||
fn to_output_stream(self) -> OutputStream {
|
||||
OutputStream {
|
||||
values: self.map(|item| item.into()).boxed(),
|
||||
fn from_input_stream(self) -> ActionStream {
|
||||
ActionStream {
|
||||
values: Box::new(self.map(nu_protocol::ReturnSuccess::value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::completion;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
@ -57,6 +58,7 @@ impl rustyline::completion::Completer for Helper {
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
type Hint = String;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
|
||||
}
|
||||
@ -78,7 +80,7 @@ impl rustyline::highlight::Highlighter for Helper {
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
|
||||
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
|
@ -285,7 +285,7 @@ fn get_shape_of_expr(expr: &SpannedExpression) -> Option<SyntaxShape> {
|
||||
nu_protocol::hir::Literal::Bare(_) => Some(SyntaxShape::String),
|
||||
}
|
||||
}
|
||||
//Synthetic are expressions that are generated by the parser and not inputed by the user
|
||||
//Synthetic are expressions that are generated by the parser and not inputted by the user
|
||||
//ExternalWord is anything sent to external commands (?)
|
||||
Expression::ExternalWord => Some(SyntaxShape::String),
|
||||
Expression::Synthetic(_) => Some(SyntaxShape::String),
|
||||
@ -387,7 +387,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
|
||||
fn infer_shape(&mut self, block: &Block, scope: &Scope) -> Result<(), ShellError> {
|
||||
trace!("Infering vars in shape");
|
||||
trace!("Inferring vars in shape");
|
||||
for group in &block.block {
|
||||
for pipeline in &group.pipelines {
|
||||
self.infer_pipeline(pipeline, scope)?;
|
||||
@ -397,7 +397,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
|
||||
pub fn infer_pipeline(&mut self, pipeline: &Pipeline, scope: &Scope) -> Result<(), ShellError> {
|
||||
trace!("Infering vars in pipeline");
|
||||
trace!("Inferring vars in pipeline");
|
||||
for (cmd_pipeline_idx, classified) in pipeline.list.iter().enumerate() {
|
||||
match &classified {
|
||||
ClassifiedCommand::Internal(internal) => {
|
||||
@ -429,7 +429,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
}
|
||||
if let Some(named) = &internal.args.named {
|
||||
trace!("Infering vars in named exprs");
|
||||
trace!("Inferring vars in named exprs");
|
||||
for (_name, val) in named.iter() {
|
||||
if let NamedValue::Value(_, named_expr) = val {
|
||||
self.infer_shapes_in_expr(
|
||||
@ -443,7 +443,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
ClassifiedCommand::Expr(spanned_expr) => {
|
||||
trace!(
|
||||
"Infering shapes in ClassifiedCommand::Expr: {:?}",
|
||||
"Inferring shapes in ClassifiedCommand::Expr: {:?}",
|
||||
spanned_expr
|
||||
);
|
||||
self.infer_shapes_in_expr((cmd_pipeline_idx, pipeline), spanned_expr, scope)?;
|
||||
@ -459,7 +459,7 @@ impl VarSyntaxShapeDeductor {
|
||||
positionals: &[SpannedExpression],
|
||||
signature: &Signature,
|
||||
) -> Result<(), ShellError> {
|
||||
trace!("Infering vars in positionals");
|
||||
trace!("Inferring vars in positionals");
|
||||
//TODO currently correct inference for optional positionals is not implemented.
|
||||
// See https://github.com/nushell/nushell/pull/2486 for a discussion about this
|
||||
// For now we assume every variable in an optional positional is used as this optional
|
||||
@ -500,7 +500,7 @@ impl VarSyntaxShapeDeductor {
|
||||
named: &NamedArguments,
|
||||
signature: &Signature,
|
||||
) -> Result<(), ShellError> {
|
||||
trace!("Infering vars in named");
|
||||
trace!("Inferring vars in named");
|
||||
for (name, val) in named.iter() {
|
||||
if let NamedValue::Value(span, spanned_expr) = &val {
|
||||
if let Expression::Variable(var_name, _) = &spanned_expr.expr {
|
||||
@ -534,15 +534,15 @@ impl VarSyntaxShapeDeductor {
|
||||
) -> Result<(), ShellError> {
|
||||
match &spanned_expr.expr {
|
||||
Expression::Binary(_) => {
|
||||
trace!("Infering vars in bin expr");
|
||||
trace!("Inferring vars in bin expr");
|
||||
self.infer_shapes_in_binary_expr((pipeline_idx, pipeline), spanned_expr, scope)?;
|
||||
}
|
||||
Expression::Block(b) => {
|
||||
trace!("Infering vars in block");
|
||||
trace!("Inferring vars in block");
|
||||
self.infer_shape(&b, scope)?;
|
||||
}
|
||||
Expression::Path(path) => {
|
||||
trace!("Infering vars in path");
|
||||
trace!("Inferring vars in path");
|
||||
match &path.head.expr {
|
||||
//PathMember can't be var yet (?)
|
||||
//TODO Iterate over path parts and find var when implemented
|
||||
@ -560,7 +560,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
}
|
||||
Expression::Range(range) => {
|
||||
trace!("Infering vars in range");
|
||||
trace!("Inferring vars in range");
|
||||
if let Some(range_left) = &range.left {
|
||||
if let Expression::Variable(var_name, _) = &range_left.expr {
|
||||
self.checked_insert(
|
||||
@ -585,13 +585,13 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
}
|
||||
Expression::List(inner_exprs) => {
|
||||
trace!("Infering vars in list");
|
||||
trace!("Inferring vars in list");
|
||||
for expr in inner_exprs {
|
||||
self.infer_shapes_in_expr((pipeline_idx, pipeline), expr, scope)?;
|
||||
}
|
||||
}
|
||||
Expression::Invocation(invoc) => {
|
||||
trace!("Infering vars in invocation: {:?}", invoc);
|
||||
trace!("Inferring vars in invocation: {:?}", invoc);
|
||||
self.infer_shape(invoc, scope)?;
|
||||
}
|
||||
Expression::Table(header, _rows) => {
|
||||
@ -738,7 +738,7 @@ impl VarSyntaxShapeDeductor {
|
||||
(pipeline_idx, pipeline): (usize, &Pipeline),
|
||||
scope: &Scope,
|
||||
) -> Result<(), ShellError> {
|
||||
trace!("Infering shapes between var {:?} and expr {:?}", var, expr);
|
||||
trace!("Inferring shapes between var {:?} and expr {:?}", var, expr);
|
||||
let bin = spanned_to_binary(bin_spanned);
|
||||
if let Expression::Literal(Literal::Operator(op)) = bin.op.expr {
|
||||
match &op {
|
||||
|
@ -5,30 +5,28 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.27.0"
|
||||
version = "0.30.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = { version = "0.27.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.27.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.27.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.27.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.27.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.27.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.27.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.27.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.27.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.27.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.27.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.27.0", path = "../nu-value-ext" }
|
||||
nu-data = { version = "0.30.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.30.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.30.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.30.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.30.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.30.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.30.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.30.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.30.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.30.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.30.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.30.0", path = "../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.30.0", path = "../nu-ansi-term" }
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
async-recursion = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||
byte-unit = "4.0.9"
|
||||
@ -38,6 +36,7 @@ chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.11.0"
|
||||
crossterm = { version = "0.19.0", optional = true }
|
||||
csv = "1.1.3"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
derive-new = "0.5.8"
|
||||
@ -50,8 +49,6 @@ encoding_rs = "0.8.28"
|
||||
filesize = "0.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.12"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
glob = "0.3.0"
|
||||
htmlescape = "0.3.1"
|
||||
@ -61,7 +58,9 @@ indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.14"
|
||||
md5 = "0.7.0"
|
||||
meval = "0.2.0"
|
||||
minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] }
|
||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
@ -76,7 +75,7 @@ rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "7.1.0", optional = true }
|
||||
rustyline = { version = "8.0.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
@ -122,6 +121,7 @@ shadow-rs = "0.5"
|
||||
[dev-dependencies]
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
|
||||
[features]
|
||||
clipboard-cli = ["arboard"]
|
||||
@ -130,3 +130,4 @@ stable = []
|
||||
trash-support = ["trash"]
|
||||
directories = ["directories-next"]
|
||||
dirs = ["dirs-next"]
|
||||
table-pager = ["minus", "crossterm"]
|
||||
|
@ -4,7 +4,9 @@ pub(crate) mod macros;
|
||||
mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod all;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod any;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub mod autoenv;
|
||||
@ -20,11 +22,9 @@ pub(crate) mod chart;
|
||||
pub(crate) mod classified;
|
||||
#[cfg(feature = "clipboard-cli")]
|
||||
pub(crate) mod clip;
|
||||
pub mod command;
|
||||
pub(crate) mod compact;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod constants;
|
||||
pub(crate) mod count;
|
||||
pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
@ -70,9 +70,10 @@ pub(crate) mod histogram;
|
||||
pub(crate) mod history;
|
||||
pub(crate) mod if_;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod into_int;
|
||||
pub(crate) mod into;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod length;
|
||||
pub(crate) mod let_;
|
||||
pub(crate) mod let_env;
|
||||
pub(crate) mod lines;
|
||||
@ -98,6 +99,8 @@ pub(crate) mod reject;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod reverse;
|
||||
pub(crate) mod rm;
|
||||
pub(crate) mod roll;
|
||||
pub(crate) mod rotate;
|
||||
pub(crate) mod run_external;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod select;
|
||||
@ -139,6 +142,7 @@ pub(crate) use autoview::Autoview;
|
||||
pub(crate) use cd::Cd;
|
||||
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use ansi::AnsiStrip;
|
||||
pub(crate) use append::Command as Append;
|
||||
pub(crate) use autoenv::Autoenv;
|
||||
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||
@ -150,9 +154,8 @@ pub(crate) use char_::Char;
|
||||
pub(crate) use chart::Chart;
|
||||
pub(crate) use compact::Compact;
|
||||
pub(crate) use config::{
|
||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
Config, ConfigClear, ConfigGet, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||
};
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||
pub(crate) use debug::Debug;
|
||||
@ -160,7 +163,7 @@ pub(crate) use def::Def;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use describe::Describe;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use drop::{Drop, DropColumn};
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
pub(crate) use each::EachGroup;
|
||||
@ -168,6 +171,8 @@ pub(crate) use each::EachWindow;
|
||||
pub(crate) use echo::Echo;
|
||||
pub(crate) use empty::Command as Empty;
|
||||
pub(crate) use if_::If;
|
||||
pub(crate) use into::Into;
|
||||
pub(crate) use into::IntoInt;
|
||||
pub(crate) use nu::NuPlugin;
|
||||
pub(crate) use update::Command as Update;
|
||||
pub(crate) mod kill;
|
||||
@ -175,6 +180,8 @@ pub(crate) use kill::Kill;
|
||||
pub(crate) mod clear;
|
||||
pub(crate) use clear::Clear;
|
||||
pub(crate) mod touch;
|
||||
pub(crate) use all::Command as All;
|
||||
pub(crate) use any::Command as Any;
|
||||
pub(crate) use enter::Enter;
|
||||
pub(crate) use every::Every;
|
||||
pub(crate) use exec::Exec;
|
||||
@ -183,40 +190,41 @@ pub(crate) use first::First;
|
||||
pub(crate) use flatten::Command as Flatten;
|
||||
pub(crate) use format::{FileSize, Format};
|
||||
pub(crate) use from::From;
|
||||
pub(crate) use from_csv::FromCSV;
|
||||
pub(crate) use from_eml::FromEML;
|
||||
pub(crate) use from_csv::FromCsv;
|
||||
pub(crate) use from_eml::FromEml;
|
||||
pub(crate) use from_ics::FromIcs;
|
||||
pub(crate) use from_ini::FromINI;
|
||||
pub(crate) use from_json::FromJSON;
|
||||
pub(crate) use from_ods::FromODS;
|
||||
pub(crate) use from_ssv::FromSSV;
|
||||
pub(crate) use from_toml::FromTOML;
|
||||
pub(crate) use from_tsv::FromTSV;
|
||||
pub(crate) use from_url::FromURL;
|
||||
pub(crate) use from_ini::FromIni;
|
||||
pub(crate) use from_json::FromJson;
|
||||
pub(crate) use from_ods::FromOds;
|
||||
pub(crate) use from_ssv::FromSsv;
|
||||
pub(crate) use from_toml::FromToml;
|
||||
pub(crate) use from_tsv::FromTsv;
|
||||
pub(crate) use from_url::FromUrl;
|
||||
pub(crate) use from_vcf::FromVcf;
|
||||
pub(crate) use from_xlsx::FromXLSX;
|
||||
pub(crate) use from_xml::FromXML;
|
||||
pub(crate) use from_yaml::FromYAML;
|
||||
pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use from_xlsx::FromXlsx;
|
||||
pub(crate) use from_xml::FromXml;
|
||||
pub(crate) use from_yaml::FromYaml;
|
||||
pub(crate) use from_yaml::FromYml;
|
||||
pub(crate) use get::Command as Get;
|
||||
pub(crate) use group_by::Command as GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use hash_::{Hash, HashBase64};
|
||||
pub(crate) use hash_::{Hash, HashBase64, HashMd5};
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Command as Insert;
|
||||
pub(crate) use into_int::IntoInt;
|
||||
pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use length::Length;
|
||||
pub(crate) use let_::Let;
|
||||
pub(crate) use let_env::LetEnv;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAbs, MathAverage, MathCeil, MathEval, MathFloor, MathMaximum, MathMedian,
|
||||
MathMinimum, MathMode, MathProduct, MathRound, MathStddev, MathSummation, MathVariance,
|
||||
MathMinimum, MathMode, MathProduct, MathRound, MathSqrt, MathStddev, MathSummation,
|
||||
MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
@ -226,7 +234,7 @@ pub(crate) use nth::Nth;
|
||||
pub(crate) use open::Open;
|
||||
pub(crate) use parse::Parse;
|
||||
pub(crate) use path::{
|
||||
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
|
||||
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathJoin, PathParse, PathSplit,
|
||||
PathType,
|
||||
};
|
||||
pub(crate) use pivot::Pivot;
|
||||
@ -244,6 +252,8 @@ pub(crate) use reject::Reject;
|
||||
pub(crate) use rename::Rename;
|
||||
pub(crate) use reverse::Reverse;
|
||||
pub(crate) use rm::Remove;
|
||||
pub(crate) use roll::{Roll, RollColumn, RollUp};
|
||||
pub(crate) use rotate::{Rotate, RotateCounterClockwise};
|
||||
pub(crate) use run_external::RunExternalCommand;
|
||||
pub(crate) use save::Save;
|
||||
pub(crate) use select::Command as Select;
|
||||
@ -268,20 +278,20 @@ pub(crate) use table::Table;
|
||||
pub(crate) use tags::Tags;
|
||||
pub(crate) use termsize::TermSize;
|
||||
pub(crate) use to::To;
|
||||
pub(crate) use to_csv::ToCSV;
|
||||
pub(crate) use to_html::ToHTML;
|
||||
pub(crate) use to_json::ToJSON;
|
||||
pub(crate) use to_csv::ToCsv;
|
||||
pub(crate) use to_html::ToHtml;
|
||||
pub(crate) use to_json::ToJson;
|
||||
pub(crate) use to_md::Command as ToMarkdown;
|
||||
pub(crate) use to_toml::ToTOML;
|
||||
pub(crate) use to_tsv::ToTSV;
|
||||
pub(crate) use to_url::ToURL;
|
||||
pub(crate) use to_xml::ToXML;
|
||||
pub(crate) use to_yaml::ToYAML;
|
||||
pub(crate) use to_toml::ToToml;
|
||||
pub(crate) use to_tsv::ToTsv;
|
||||
pub(crate) use to_url::ToUrl;
|
||||
pub(crate) use to_xml::ToXml;
|
||||
pub(crate) use to_yaml::ToYaml;
|
||||
pub(crate) use touch::Touch;
|
||||
pub(crate) use uniq::Uniq;
|
||||
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};
|
||||
pub(crate) use version::Version;
|
||||
pub(crate) use where_::Where;
|
||||
pub(crate) use where_::Command as Where;
|
||||
pub(crate) use which_::Which;
|
||||
pub(crate) use with_env::WithEnv;
|
||||
pub(crate) use wrap::Wrap;
|
||||
|
136
crates/nu-command/src/commands/all.rs
Normal file
136
crates/nu-command/src/commands/all.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::evaluate_baseline_expr;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
|
||||
};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
struct AllArgs {
|
||||
predicate: CapturedBlock,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"all?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("all?").required(
|
||||
"condition",
|
||||
SyntaxShape::RowCondition,
|
||||
"the condition that must match",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find if the table rows matches the condition."
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
all(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_protocol::Value;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Find if services are running",
|
||||
example: "echo [[status]; [UP] [UP]] | all? status == UP",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
Example {
|
||||
description: "Check that all values are even",
|
||||
example: "echo [2 4 6 8] | all? $(= $it mod 2) == 0",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn all(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let args = args.evaluate_once()?;
|
||||
let all_args = AllArgs {
|
||||
predicate: args.req(0)?,
|
||||
};
|
||||
|
||||
let err = Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
args.call_info.name_tag.clone(),
|
||||
));
|
||||
|
||||
//This seems a little odd. Can't we have predicates with pipelines/multiple statements?
|
||||
let condition = {
|
||||
if all_args.predicate.block.block.len() != 1 {
|
||||
return err;
|
||||
}
|
||||
match all_args.predicate.block.block[0].pipelines.get(0) {
|
||||
Some(item) => match item.list.get(0) {
|
||||
Some(ClassifiedCommand::Expr(expr)) => expr.clone(),
|
||||
_ => {
|
||||
return err;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let scope = args.scope.clone();
|
||||
|
||||
let init = Ok(InputStream::one(
|
||||
UntaggedValue::boolean(true).into_value(&tag),
|
||||
));
|
||||
|
||||
// Variables in nu are immutable. Having the same variable accross invocations
|
||||
// of evaluate_baseline_expr does not mutate the variables and those each
|
||||
// invocations are independent of each other!
|
||||
scope.enter_scope();
|
||||
scope.add_vars(&all_args.predicate.captured.entries);
|
||||
let result = args.input.fold(init, move |acc, row| {
|
||||
let condition = condition.clone();
|
||||
let ctx = ctx.clone();
|
||||
ctx.scope.add_var("$it", row);
|
||||
|
||||
let condition = evaluate_baseline_expr(&condition, &ctx);
|
||||
|
||||
let curr = acc?.drain_vec();
|
||||
let curr = curr
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to check with"))?;
|
||||
let cond = curr.as_bool()?;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => Ok(InputStream::one(
|
||||
UntaggedValue::boolean(cond && b).into_value(&curr.tag),
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
});
|
||||
scope.exit_scope();
|
||||
|
||||
Ok(result?.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
@ -1,21 +1,13 @@
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_ansi_term::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Ansi;
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
code: Value,
|
||||
escape: Option<Tagged<String>>,
|
||||
osc: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
@ -42,9 +34,11 @@ impl WholeStreamCommand for Ansi {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
r#"Output ANSI codes to change color
|
||||
"Output ANSI codes to change color."
|
||||
}
|
||||
|
||||
For escape sequences:
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"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
|
||||
@ -118,8 +112,12 @@ Format: #
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { code, escape, osc }, _) = args.process().await?;
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
|
||||
let code: Option<Tagged<String>> = args.opt(0)?;
|
||||
let escape: Option<Tagged<String>> = args.get_flag("escape")?;
|
||||
let osc: Option<Tagged<String>> = args.get_flag("osc")?;
|
||||
|
||||
if let Some(e) = escape {
|
||||
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||
@ -131,7 +129,7 @@ Format: #
|
||||
));
|
||||
}
|
||||
let output = format!("\x1b[{}", e.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
return Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(e.tag()),
|
||||
)));
|
||||
}
|
||||
@ -149,16 +147,16 @@ Format: #
|
||||
//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(
|
||||
return Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(o.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
let code_string = code.as_string()?;
|
||||
let ansi_code = str_to_ansi(code_string);
|
||||
if let Some(code) = code {
|
||||
let ansi_code = str_to_ansi(&code.item);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(code.tag()),
|
||||
)))
|
||||
} else {
|
||||
@ -168,59 +166,134 @@ Format: #
|
||||
code.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected ansi code",
|
||||
"expect ansi code",
|
||||
args.call_info.name_tag.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_ansi(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
pub fn str_to_ansi(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"g" | "green" => Some(Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
|
||||
|
||||
"lg" | "light_green" => Some(Color::LightGreen.prefix().to_string()),
|
||||
"lgb" | "light_green_bold" => Some(Color::LightGreen.bold().prefix().to_string()),
|
||||
"lgu" | "light_green_underline" => Some(Color::LightGreen.underline().prefix().to_string()),
|
||||
"lgi" | "light_green_italic" => Some(Color::LightGreen.italic().prefix().to_string()),
|
||||
"lgd" | "light_green_dimmed" => Some(Color::LightGreen.dimmed().prefix().to_string()),
|
||||
"lgr" | "light_green_reverse" => Some(Color::LightGreen.reverse().prefix().to_string()),
|
||||
|
||||
"r" | "red" => Some(Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
|
||||
|
||||
"lr" | "light_red" => Some(Color::LightRed.prefix().to_string()),
|
||||
"lrb" | "light_red_bold" => Some(Color::LightRed.bold().prefix().to_string()),
|
||||
"lru" | "light_red_underline" => Some(Color::LightRed.underline().prefix().to_string()),
|
||||
"lri" | "light_red_italic" => Some(Color::LightRed.italic().prefix().to_string()),
|
||||
"lrd" | "light_red_dimmed" => Some(Color::LightRed.dimmed().prefix().to_string()),
|
||||
"lrr" | "light_red_reverse" => Some(Color::LightRed.reverse().prefix().to_string()),
|
||||
|
||||
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
|
||||
|
||||
"lu" | "light_blue" => Some(Color::LightBlue.prefix().to_string()),
|
||||
"lub" | "light_blue_bold" => Some(Color::LightBlue.bold().prefix().to_string()),
|
||||
"luu" | "light_blue_underline" => Some(Color::LightBlue.underline().prefix().to_string()),
|
||||
"lui" | "light_blue_italic" => Some(Color::LightBlue.italic().prefix().to_string()),
|
||||
"lud" | "light_blue_dimmed" => Some(Color::LightBlue.dimmed().prefix().to_string()),
|
||||
"lur" | "light_blue_reverse" => Some(Color::LightBlue.reverse().prefix().to_string()),
|
||||
|
||||
"b" | "black" => Some(Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
|
||||
|
||||
"ligr" | "light_gray" => Some(Color::LightGray.prefix().to_string()),
|
||||
"ligrb" | "light_gray_bold" => Some(Color::LightGray.bold().prefix().to_string()),
|
||||
"ligru" | "light_gray_underline" => Some(Color::LightGray.underline().prefix().to_string()),
|
||||
"ligri" | "light_gray_italic" => Some(Color::LightGray.italic().prefix().to_string()),
|
||||
"ligrd" | "light_gray_dimmed" => Some(Color::LightGray.dimmed().prefix().to_string()),
|
||||
"ligrr" | "light_gray_reverse" => Some(Color::LightGray.reverse().prefix().to_string()),
|
||||
|
||||
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
|
||||
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
|
||||
|
||||
"ly" | "light_yellow" => Some(Color::LightYellow.prefix().to_string()),
|
||||
"lyb" | "light_yellow_bold" => Some(Color::LightYellow.bold().prefix().to_string()),
|
||||
"lyu" | "light_yellow_underline" => {
|
||||
Some(Color::LightYellow.underline().prefix().to_string())
|
||||
}
|
||||
"lyi" | "light_yellow_italic" => Some(Color::LightYellow.italic().prefix().to_string()),
|
||||
"lyd" | "light_yellow_dimmed" => Some(Color::LightYellow.dimmed().prefix().to_string()),
|
||||
"lyr" | "light_yellow_reverse" => Some(Color::LightYellow.reverse().prefix().to_string()),
|
||||
|
||||
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
|
||||
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
|
||||
|
||||
"lp" | "light_purple" => Some(Color::LightPurple.prefix().to_string()),
|
||||
"lpb" | "light_purple_bold" => Some(Color::LightPurple.bold().prefix().to_string()),
|
||||
"lpu" | "light_purple_underline" => {
|
||||
Some(Color::LightPurple.underline().prefix().to_string())
|
||||
}
|
||||
"lpi" | "light_purple_italic" => Some(Color::LightPurple.italic().prefix().to_string()),
|
||||
"lpd" | "light_purple_dimmed" => Some(Color::LightPurple.dimmed().prefix().to_string()),
|
||||
"lpr" | "light_purple_reverse" => Some(Color::LightPurple.reverse().prefix().to_string()),
|
||||
|
||||
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
|
||||
|
||||
"lc" | "light_cyan" => Some(Color::LightCyan.prefix().to_string()),
|
||||
"lcb" | "light_cyan_bold" => Some(Color::LightCyan.bold().prefix().to_string()),
|
||||
"lcu" | "light_cyan_underline" => Some(Color::LightCyan.underline().prefix().to_string()),
|
||||
"lci" | "light_cyan_italic" => Some(Color::LightCyan.italic().prefix().to_string()),
|
||||
"lcd" | "light_cyan_dimmed" => Some(Color::LightCyan.dimmed().prefix().to_string()),
|
||||
"lcr" | "light_cyan_reverse" => Some(Color::LightCyan.reverse().prefix().to_string()),
|
||||
|
||||
"w" | "white" => Some(Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
|
||||
|
||||
"dgr" | "dark_gray" => Some(Color::DarkGray.prefix().to_string()),
|
||||
"dgrb" | "dark_gray_bold" => Some(Color::DarkGray.bold().prefix().to_string()),
|
||||
"dgru" | "dark_gray_underline" => Some(Color::DarkGray.underline().prefix().to_string()),
|
||||
"dgri" | "dark_gray_italic" => Some(Color::DarkGray.italic().prefix().to_string()),
|
||||
"dgrd" | "dark_gray_dimmed" => Some(Color::DarkGray.dimmed().prefix().to_string()),
|
||||
"dgrr" | "dark_gray_reverse" => Some(Color::DarkGray.reverse().prefix().to_string()),
|
||||
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
@ -228,8 +301,6 @@ pub fn str_to_ansi(s: String) -> Option<String> {
|
||||
|
||||
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
// Ansi Erase Sequences
|
||||
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||
@ -262,7 +333,7 @@ pub fn str_to_ansi(s: String) -> Option<String> {
|
||||
|
||||
// Ansi RGB - Needs to be 32;2;r;g;b or 48;2;r;g;b
|
||||
// assuming the rgb will be passed via command and no here
|
||||
"rgb_fg" => Some("\x1b[32;2;".to_string()),
|
||||
"rgb_fg" => Some("\x1b[38;2;".to_string()),
|
||||
"rgb_bg" => Some("\x1b[48;2;".to_string()),
|
||||
|
||||
// Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255
|
||||
@ -279,13 +350,13 @@ pub fn str_to_ansi(s: String) -> Option<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
5
crates/nu-command/src/commands/ansi/mod.rs
Normal file
5
crates/nu-command/src/commands/ansi/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod command;
|
||||
mod strip;
|
||||
|
||||
pub use command::Command as Ansi;
|
||||
pub use strip::SubCommand as AnsiStrip;
|
120
crates/nu-command/src/commands/ansi/strip.rs
Normal file
120
crates/nu-command/src/commands/ansi/strip.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use strip_ansi_escapes::strip;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"ansi strip"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi strip").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally, remove ansi sequences by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"strip ansi escape sequences from string"
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
operate(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "strip ansi escape sequences from string",
|
||||
example: "echo [$(ansi gb) 'hello' $(ansi reset)] | str collect | ansi strip",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (Arguments { rest }, input) = args.process()?;
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::String(astring)) => {
|
||||
let stripped_string = {
|
||||
if let Ok(bytes) = strip(&astring) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
astring.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UntaggedValue::string(stripped_string).into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, SubCommand};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stripping() {
|
||||
let input_string =
|
||||
UntaggedValue::string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld")
|
||||
.into_untagged_value();
|
||||
let expected = UntaggedValue::string("Hello Nu World").into_untagged_value();
|
||||
|
||||
let actual = action(&input_string, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
134
crates/nu-command/src/commands/any.rs
Normal file
134
crates/nu-command/src/commands/any.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::evaluate_baseline_expr;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
|
||||
};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
block: CapturedBlock,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"any?"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("any?").required(
|
||||
"condition",
|
||||
SyntaxShape::RowCondition,
|
||||
"the condition that must match",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find if the table rows matches the condition."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
any(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_protocol::Value;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Find if a service is not running",
|
||||
example: "echo [[status]; [UP] [DOWN] [UP]] | any? status == DOWN",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
Example {
|
||||
description: "Check if any of the values is odd",
|
||||
example: "echo [2 4 1 6 8] | any? $(= $it mod 2) == 1",
|
||||
result: Some(vec![Value::from(true)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn any(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = Arc::new(EvaluationContext::from_args(&args));
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (Arguments { block }, input) = args.process()?;
|
||||
|
||||
let condition = {
|
||||
if block.block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block.block[0].pipelines.get(0) {
|
||||
Some(item) => match item.list.get(0) {
|
||||
Some(ClassifiedCommand::Expr(expr)) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cond = Ok(InputStream::one(
|
||||
UntaggedValue::boolean(false).into_value(&tag),
|
||||
));
|
||||
|
||||
Ok(input
|
||||
.fold(cond, move |cond, row| {
|
||||
let condition = condition.clone();
|
||||
let ctx = ctx.clone();
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_vars(&block.captured.entries);
|
||||
ctx.scope.add_var("$it", row);
|
||||
|
||||
let condition = evaluate_baseline_expr(&condition, &*ctx);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let curr = cond?.drain_vec();
|
||||
let curr = curr
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to check with"))?;
|
||||
let cond = curr.as_bool()?;
|
||||
|
||||
match condition {
|
||||
Ok(condition) => match condition.as_bool() {
|
||||
Ok(b) => Ok(InputStream::one(
|
||||
UntaggedValue::boolean(cond || b).into_value(&curr.tag),
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
})?
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Command {})
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
@ -10,7 +10,6 @@ struct Arguments {
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"append"
|
||||
@ -25,16 +24,17 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Append a row to the table"
|
||||
"Append a row to the table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { mut value }, input) = args.process().await?;
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { mut value }, mut input) = args.process()?;
|
||||
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
let mut prepend = vec![];
|
||||
|
||||
if let Some(first) = input.get(0) {
|
||||
if let Some(first) = input.next() {
|
||||
value.tag = first.tag();
|
||||
prepend.push(first);
|
||||
}
|
||||
|
||||
// Checks if we are trying to append a row literal
|
||||
@ -48,18 +48,13 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
input
|
||||
Ok(prepend
|
||||
.into_iter()
|
||||
.chain(vec![value])
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.chain(input.into_iter().chain(vec![value]))
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_protocol::row;
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Add values to the end of the table",
|
||||
|
@ -2,68 +2,32 @@ use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
pub struct Autoenv;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct Trusted {
|
||||
pub files: IndexMap<String, Vec<u8>>,
|
||||
}
|
||||
impl Trusted {
|
||||
pub fn new() -> Self {
|
||||
Trusted {
|
||||
files: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn file_is_trusted(nu_env_file: &Path, content: &[u8]) -> Result<bool, ShellError> {
|
||||
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
|
||||
let nufile = std::fs::canonicalize(nu_env_file)?;
|
||||
|
||||
let trusted = read_trusted()?;
|
||||
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
|
||||
}
|
||||
|
||||
pub fn read_trusted() -> Result<Trusted, ShellError> {
|
||||
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(config_path)
|
||||
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
|
||||
let mut doc = String::new();
|
||||
file.read_to_string(&mut doc)?;
|
||||
|
||||
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||
Ok(allowed)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Autoenv {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv"
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
"Manage directory specific environment variables and scripts."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
|
||||
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
|
||||
The file can contain several optional sections:
|
||||
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
|
||||
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
|
||||
scripts: scripts to run when entering the directory or leaving it."#
|
||||
r#"Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell load it when entering the directory.
|
||||
The .nu-env file has the same format as your $HOME/nu/config.toml file. By loading a .nu-env file the following applies:
|
||||
- environment variables (section \"[env]\") are loaded from the .nu-env file. Those env variables only exist in this directory (and children directories)
|
||||
- the \"startup\" commands are run when entering the directory
|
||||
- the \"on_exit\" commands are run when leaving the directory
|
||||
"#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv")
|
||||
}
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_full_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
||||
@ -71,15 +35,12 @@ The file can contain several optional sections:
|
||||
vec![Example {
|
||||
description: "Example .nu-env file",
|
||||
example: r#"cat .nu-env
|
||||
startup = ["echo ...entering the directory", "echo 1 2 3"]
|
||||
on_exit = ["echo ...leaving the directory"]
|
||||
|
||||
[env]
|
||||
mykey = "myvalue"
|
||||
|
||||
[scriptvars]
|
||||
myscript = "echo myval"
|
||||
|
||||
[scripts]
|
||||
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||
exitscripts = ["touch bye.txt"]"#,
|
||||
"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::autoenv::read_trusted;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::read_trusted;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::SyntaxShape;
|
||||
@ -8,7 +8,6 @@ use sha2::{Digest, Sha256};
|
||||
use std::{fs, path::PathBuf};
|
||||
pub struct AutoenvTrust;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AutoenvTrust {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv trust"
|
||||
@ -22,11 +21,11 @@ impl WholeStreamCommand for AutoenvTrust {
|
||||
"Trust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
|
||||
let file_to_trust = match args.call_info.evaluate(&ctx).await?.args.nth(0) {
|
||||
let file_to_trust = match args.call_info.evaluate(&ctx)?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
@ -56,7 +55,7 @@ impl WholeStreamCommand for AutoenvTrust {
|
||||
})?;
|
||||
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(".nu-env trusted!").into_value(tag),
|
||||
)))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::autoenv::Trusted;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::Trusted;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::SyntaxShape;
|
||||
@ -8,7 +8,6 @@ use std::io::Read;
|
||||
use std::{fs, path::PathBuf};
|
||||
pub struct AutoenvUnTrust;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for AutoenvUnTrust {
|
||||
fn name(&self) -> &str {
|
||||
"autoenv untrust"
|
||||
@ -26,10 +25,10 @@ impl WholeStreamCommand for AutoenvUnTrust {
|
||||
"Untrust a .nu-env file in the current or given directory"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let file_to_untrust = match args.call_info.evaluate(&ctx).await?.args.nth(0) {
|
||||
let file_to_untrust = match args.call_info.evaluate(&ctx)?.args.nth(0) {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||
tag: _,
|
||||
@ -80,7 +79,7 @@ impl WholeStreamCommand for AutoenvUnTrust {
|
||||
})?;
|
||||
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(".nu-env untrusted!").into_value(tag),
|
||||
)))
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewConfiguration};
|
||||
use crate::commands::autoview::options::ConfigExtensions;
|
||||
use crate::prelude::*;
|
||||
use crate::primitive::get_color_config;
|
||||
use nu_data::value::format_leaf;
|
||||
@ -7,12 +7,9 @@ use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
|
||||
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
|
||||
use nu_table::TextStyle;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"autoview"
|
||||
@ -26,17 +23,8 @@ impl WholeStreamCommand for Command {
|
||||
"View the contents of the pipeline as a table or list."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
autoview(RunnableContext {
|
||||
input: args.input,
|
||||
scope: args.scope.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
})
|
||||
.await
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
autoview(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -55,57 +43,28 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContextWithoutInput {
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub scope: Scope,
|
||||
pub name: Tag,
|
||||
}
|
||||
pub fn autoview(context: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let configuration = context.configs.lock().global_config();
|
||||
|
||||
impl RunnableContextWithoutInput {
|
||||
pub fn convert(context: RunnableContext) -> (InputStream, RunnableContextWithoutInput) {
|
||||
let new_context = RunnableContextWithoutInput {
|
||||
shell_manager: context.shell_manager,
|
||||
host: context.host,
|
||||
ctrl_c: context.ctrl_c,
|
||||
current_errors: context.current_errors,
|
||||
scope: context.scope,
|
||||
name: context.name,
|
||||
};
|
||||
(context.input, new_context)
|
||||
}
|
||||
}
|
||||
let binary = context.scope.get_command("binaryview");
|
||||
let text = context.scope.get_command("textview");
|
||||
let table = context.scope.get_command("table");
|
||||
|
||||
pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let configuration = AutoViewConfiguration::new();
|
||||
let (mut input_stream, context) = context.split();
|
||||
|
||||
let binary = context.get_command("binaryview");
|
||||
let text = context.get_command("textview");
|
||||
let table = context.get_command("table");
|
||||
|
||||
let pivot_mode = configuration.pivot_mode();
|
||||
|
||||
let (mut input_stream, context) = RunnableContextWithoutInput::convert(context);
|
||||
let term_width = context.host.lock().width();
|
||||
let color_hm = get_color_config();
|
||||
|
||||
if let Some(x) = input_stream.next().await {
|
||||
match input_stream.next().await {
|
||||
if let Some(x) = input_stream.next() {
|
||||
match input_stream.next() {
|
||||
Some(y) => {
|
||||
let ctrl_c = context.ctrl_c.clone();
|
||||
let xy = vec![x, y];
|
||||
let xy_stream = futures::stream::iter(xy)
|
||||
.chain(input_stream)
|
||||
.interruptible(ctrl_c);
|
||||
let xy_stream = xy.into_iter().chain(input_stream).interruptible(ctrl_c);
|
||||
|
||||
let stream = InputStream::from_stream(xy_stream);
|
||||
|
||||
if let Some(table) = table {
|
||||
let command_args = create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
let result = table.run(command_args)?;
|
||||
let _ = result.collect::<Vec<_>>();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@ -121,8 +80,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = text.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
let result = text.run_with_actions(command_args)?;
|
||||
let _ = result.collect::<Vec<_>>();
|
||||
} else {
|
||||
out!("{}", s);
|
||||
}
|
||||
@ -203,8 +162,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = binary.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
let result = binary.run_with_actions(command_args)?;
|
||||
let _ = result.collect::<Vec<_>>();
|
||||
} else {
|
||||
use pretty_hex::*;
|
||||
out!("{:?}", b.hex_dump());
|
||||
@ -219,9 +178,13 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
}
|
||||
|
||||
Value {
|
||||
value: UntaggedValue::Row(row),
|
||||
value: UntaggedValue::Row(ref row),
|
||||
..
|
||||
} if pivot_mode.is_always()
|
||||
} => {
|
||||
let pivot_mode = configuration.pivot_mode();
|
||||
|
||||
let term_width = context.host.lock().width();
|
||||
if pivot_mode.is_always()
|
||||
|| (pivot_mode.is_auto()
|
||||
&& (row
|
||||
.entries
|
||||
@ -231,7 +194,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
.iter()
|
||||
.fold(0usize, |acc, len| acc + len.len())
|
||||
+ row.entries.iter().count() * 2)
|
||||
> term_width) =>
|
||||
> term_width)
|
||||
{
|
||||
let mut entries = vec![];
|
||||
for (key, value) in row.entries.iter() {
|
||||
@ -240,7 +203,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
key.to_string(),
|
||||
TextStyle::new()
|
||||
.alignment(nu_table::Alignment::Left)
|
||||
.fg(ansi_term::Color::Green)
|
||||
.fg(nu_ansi_term::Color::Green)
|
||||
.bold(Some(true)),
|
||||
),
|
||||
nu_table::StyledString::new(
|
||||
@ -250,10 +213,22 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
]);
|
||||
}
|
||||
|
||||
let color_hm = get_color_config(&configuration);
|
||||
|
||||
let table =
|
||||
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
|
||||
|
||||
nu_table::draw_table(&table, term_width, &color_hm);
|
||||
println!("{}", nu_table::draw_table(&table, term_width, &color_hm));
|
||||
} else if let Some(table) = table {
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args)?;
|
||||
let _ = result.collect::<Vec<_>>();
|
||||
} else {
|
||||
out!("{:?}", row);
|
||||
}
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
@ -269,8 +244,8 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
stream.push_back(x);
|
||||
let command_args =
|
||||
create_default_command_args(&context).with_input(stream);
|
||||
let result = table.run(command_args).await?;
|
||||
result.collect::<Vec<_>>().await;
|
||||
let result = table.run(command_args)?;
|
||||
let _ = result.collect::<Vec<_>>();
|
||||
} else {
|
||||
out!("{:?}", item);
|
||||
}
|
||||
@ -280,7 +255,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
Ok(InputStream::empty())
|
||||
}
|
||||
|
||||
fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawCommandArgs {
|
||||
@ -288,6 +263,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
|
||||
RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
configs: context.configs.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
|
@ -23,7 +23,6 @@ struct BenchmarkArgs {
|
||||
passthrough: Option<CapturedBlock>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Benchmark {
|
||||
fn name(&self) -> &str {
|
||||
"benchmark"
|
||||
@ -45,11 +44,11 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block and returns the time it took to execute it"
|
||||
"Runs a block and returns the time it took to execute it."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
benchmark(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
benchmark(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -68,11 +67,11 @@ impl WholeStreamCommand for Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn benchmark(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = raw_args.call_info.args.span;
|
||||
let mut context = EvaluationContext::from_args(&raw_args);
|
||||
let scope = raw_args.scope.clone();
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process().await?;
|
||||
let (BenchmarkArgs { block, passthrough }, input) = raw_args.process()?;
|
||||
|
||||
let env = scope.get_env_vars();
|
||||
let name = generate_free_name(&env);
|
||||
@ -82,15 +81,15 @@ async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let start_time = Instant::now();
|
||||
|
||||
// #[cfg(feature = "rich-benchmark")]
|
||||
// let start = time().await;
|
||||
// let start = time();
|
||||
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&block.block, &context, input).await;
|
||||
let result = run_block(&block.block, &context, input);
|
||||
context.scope.exit_scope();
|
||||
let output = result?.into_vec().await;
|
||||
let output = result?.into_vec();
|
||||
|
||||
// #[cfg(feature = "rich-benchmark")]
|
||||
// let end = time().await;
|
||||
// let end = time();
|
||||
|
||||
let end_time = Instant::now();
|
||||
context.clear_errors();
|
||||
@ -102,7 +101,7 @@ async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
|
||||
let real_time = into_big_int(end_time - start_time);
|
||||
indexmap.insert("real time".to_string(), real_time);
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context).await
|
||||
benchmark_output(indexmap, output, passthrough, &tag, &mut context)
|
||||
}
|
||||
// return advanced stats
|
||||
// #[cfg(feature = "rich-benchmark")]
|
||||
@ -121,7 +120,7 @@ async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
// let idle_time = into_big_int(end.idle() - start.idle());
|
||||
// indexmap.insert("idle time".to_string(), idle_time);
|
||||
|
||||
// benchmark_output(indexmap, output, passthrough, &tag, &mut context).await
|
||||
// benchmark_output(indexmap, output, passthrough, &tag, &mut context)
|
||||
// } else {
|
||||
// Err(ShellError::untagged_runtime_error(
|
||||
// "Could not retrieve CPU time",
|
||||
@ -129,16 +128,16 @@ async fn benchmark(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
// }
|
||||
}
|
||||
|
||||
async fn benchmark_output<T, Output>(
|
||||
fn benchmark_output<T, Output>(
|
||||
indexmap: IndexMap<String, BigInt>,
|
||||
block_output: Output,
|
||||
passthrough: Option<CapturedBlock>,
|
||||
tag: T,
|
||||
context: &mut EvaluationContext,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
) -> Result<ActionStream, ShellError>
|
||||
where
|
||||
T: Into<Tag> + Copy,
|
||||
Output: Into<OutputStream>,
|
||||
Output: Into<ActionStream>,
|
||||
{
|
||||
let value = UntaggedValue::Row(Dictionary::from(
|
||||
indexmap
|
||||
@ -155,19 +154,20 @@ where
|
||||
let time_block = add_implicit_autoview(time_block.block);
|
||||
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&time_block, context, benchmark_output).await;
|
||||
let result = run_block(&time_block, context, benchmark_output);
|
||||
context.scope.exit_scope();
|
||||
result?;
|
||||
context.clear_errors();
|
||||
|
||||
Ok(block_output.into())
|
||||
} else {
|
||||
let benchmark_output = OutputStream::one(value);
|
||||
let benchmark_output = ActionStream::one(value);
|
||||
Ok(benchmark_output)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_implicit_autoview(mut block: Block) -> Block {
|
||||
fn add_implicit_autoview(mut block: Arc<Block>) -> Arc<Block> {
|
||||
if let Some(block) = std::sync::Arc::<nu_protocol::hir::Block>::get_mut(&mut block) {
|
||||
if block.block.is_empty() {
|
||||
let group = Group::new(
|
||||
vec![{
|
||||
@ -183,6 +183,7 @@ fn add_implicit_autoview(mut block: Block) -> Block {
|
||||
);
|
||||
block.push(group);
|
||||
}
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,8 @@ use nu_data::value::format_leaf;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildStringArgs {
|
||||
rest: Vec<Value>,
|
||||
}
|
||||
|
||||
pub struct BuildString;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for BuildString {
|
||||
fn name(&self) -> &str {
|
||||
"build-string"
|
||||
@ -24,12 +18,13 @@ impl WholeStreamCommand for BuildString {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Builds a string from the arguments"
|
||||
"Builds a string from the arguments."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (BuildStringArgs { rest }, _) = args.process().await?;
|
||||
let args = args.evaluate_once()?;
|
||||
let rest: Vec<Value> = args.rest(0)?;
|
||||
|
||||
let mut output_string = String::new();
|
||||
|
||||
@ -37,7 +32,7 @@ impl WholeStreamCommand for BuildString {
|
||||
output_string.push_str(&format_leaf(&r).plain_string(100_000))
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(tag),
|
||||
)))
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use nu_protocol::{Dictionary, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Cal;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cal {
|
||||
fn name(&self) -> &str {
|
||||
"cal"
|
||||
@ -41,8 +40,8 @@ impl WholeStreamCommand for Cal {
|
||||
"Display a calendar."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
cal(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
cal(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -66,8 +65,8 @@ impl WholeStreamCommand for Cal {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cal(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
pub fn cal(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let mut calendar_vec_deque = VecDeque::new();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
@ -76,7 +75,7 @@ pub async fn cal(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let mut selected_year: i32 = current_year;
|
||||
let mut current_day_option: Option<u32> = Some(current_day);
|
||||
|
||||
let month_range = if let Some(full_year_value) = args.get("full-year") {
|
||||
let month_range = if let Some(full_year_value) = args.call_info.args.get("full-year") {
|
||||
if let Ok(year_u64) = full_year_value.as_u64() {
|
||||
selected_year = year_u64 as i32;
|
||||
|
||||
@ -102,7 +101,7 @@ pub async fn cal(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
current_day_option,
|
||||
)?;
|
||||
|
||||
Ok(futures::stream::iter(calendar_vec_deque).to_output_stream())
|
||||
Ok(calendar_vec_deque.into_iter().to_action_stream())
|
||||
}
|
||||
|
||||
fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
|
||||
@ -210,7 +209,7 @@ fn add_month_to_table(
|
||||
|
||||
let month_helper = match month_helper_result {
|
||||
Ok(month_helper) => month_helper,
|
||||
Err(()) => match args.get("full-year") {
|
||||
Err(()) => match args.call_info.args.get("full-year") {
|
||||
Some(full_year_value) => {
|
||||
return Err(get_invalid_year_shell_error(&full_year_value.tag()))
|
||||
}
|
||||
@ -236,7 +235,7 @@ fn add_month_to_table(
|
||||
|
||||
let mut week_start_day = days_of_the_week[0].to_string();
|
||||
|
||||
if let Some(week_start_value) = args.get("week-start") {
|
||||
if let Some(week_start_value) = args.call_info.args.get("week-start") {
|
||||
if let Ok(day) = week_start_value.as_string() {
|
||||
if days_of_the_week.contains(&day.as_str()) {
|
||||
week_start_day = day;
|
||||
@ -265,10 +264,10 @@ fn add_month_to_table(
|
||||
let mut day_number: u32 = 1;
|
||||
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
|
||||
|
||||
let should_show_year_column = args.has("year");
|
||||
let should_show_quarter_column = args.has("quarter");
|
||||
let should_show_month_column = args.has("month");
|
||||
let should_show_month_names = args.has("month-names");
|
||||
let should_show_year_column = args.has_flag("year");
|
||||
let should_show_quarter_column = args.has_flag("quarter");
|
||||
let should_show_month_column = args.has_flag("month");
|
||||
let should_show_month_names = args.has_flag("month-names");
|
||||
|
||||
while day_number <= day_limit {
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
@ -7,7 +7,6 @@ use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Cd;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cd {
|
||||
fn name(&self) -> &str {
|
||||
"cd"
|
||||
@ -25,10 +24,10 @@ impl WholeStreamCommand for Cd {
|
||||
"Change to a new path."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let (args, _): (CdArgs, _) = args.process().await?;
|
||||
let (args, _): (CdArgs, _) = args.process()?;
|
||||
shell_manager.cd(args, name)
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_engine::{FromValue, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Char;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
unicode: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Char {
|
||||
fn name(&self) -> &str {
|
||||
"char"
|
||||
@ -25,11 +18,12 @@ impl WholeStreamCommand for Char {
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||
.rest(SyntaxShape::String, "multiple Unicode bytes")
|
||||
.switch("unicode", "Unicode string i.e. 1f378", Some('u'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output special characters (eg. 'newline')"
|
||||
"Output special characters (eg. 'newline')."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -49,33 +43,67 @@ impl WholeStreamCommand for Char {
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Output unicode character",
|
||||
description: "Output Unicode character",
|
||||
example: r#"char -u 1f378"#,
|
||||
result: Some(vec![Value::from("\u{1f378}")]),
|
||||
},
|
||||
Example {
|
||||
description: "Output multi-byte Unicode character",
|
||||
example: r#"char -u 1F468 200D 1F466 200D 1F466"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name, unicode }, _) = args.process().await?;
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
|
||||
let name: Tagged<String> = args.req(0)?;
|
||||
let rest: Vec<Value> = args.rest(1)?;
|
||||
let unicode = args.has_flag("unicode");
|
||||
|
||||
if unicode {
|
||||
let decoded_char = string_to_unicode_char(&name.item);
|
||||
if let Some(output) = decoded_char {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
if !rest.is_empty() {
|
||||
// Setup a new buffer to put all the Unicode bytes in
|
||||
let mut multi_byte = String::new();
|
||||
// Get the first byte
|
||||
let decoded_char = string_to_unicode_char(&name.item, &name.tag);
|
||||
match decoded_char {
|
||||
Ok(ch) => multi_byte.push(ch),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
// Get the rest of the bytes
|
||||
for byte_part in rest {
|
||||
let byte_part: Tagged<String> = FromValue::from_value(&byte_part)?;
|
||||
let decoded_char = string_to_unicode_char(&byte_part, &byte_part.tag);
|
||||
match decoded_char {
|
||||
Ok(ch) => multi_byte.push(ch),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(multi_byte).into_value(name.tag),
|
||||
)))
|
||||
} else {
|
||||
let decoded_char = string_to_unicode_char(&name.item, &name.tag);
|
||||
if let Ok(ch) = decoded_char {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(ch).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding unicode character",
|
||||
"error decoding unicode character",
|
||||
"error decoding Unicode character",
|
||||
"error decoding Unicode character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let special_character = str_to_character(&name.item);
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
@ -89,10 +117,20 @@ impl WholeStreamCommand for Char {
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||
u32::from_str_radix(s, 16)
|
||||
fn string_to_unicode_char(s: &str, t: &Tag) -> Result<char, ShellError> {
|
||||
let decoded_char = u32::from_str_radix(s, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32)
|
||||
.and_then(std::char::from_u32);
|
||||
|
||||
if let Some(ch) = decoded_char {
|
||||
Ok(ch)
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding Unicode character",
|
||||
"error decoding Unicode character",
|
||||
t,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
@ -130,6 +168,9 @@ fn str_to_character(s: &str) -> Option<String> {
|
||||
"snowy" | "snow" => Some("❄️".to_string()),
|
||||
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
#[derive(Clone)]
|
||||
pub struct Chart;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Chart {
|
||||
fn name(&self) -> &str {
|
||||
"chart"
|
||||
@ -20,15 +19,15 @@ impl WholeStreamCommand for Chart {
|
||||
"Displays charts."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
if args.scope.get_command("chart bar").is_none() {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"nu_plugin_chart not installed.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&Chart, &args.scope)).into_value(Tag::unknown()),
|
||||
Ok(ActionStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_full_help(&Chart, &args.scope)).into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
use crate::futures::ThreadedReceiver;
|
||||
use crate::prelude::*;
|
||||
use nu_engine::evaluate_baseline_expr;
|
||||
use nu_engine::{evaluate_baseline_expr, BufCodecReader};
|
||||
use nu_engine::{MaybeTextCodec, StringOrBinary};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::{borrow::Cow, io::BufReader};
|
||||
|
||||
use futures::executor::block_on_stream;
|
||||
use futures_codec::FramedRead;
|
||||
use log::trace;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
@ -18,9 +16,8 @@ use nu_protocol::hir::Expression;
|
||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use nu_stream::trace_stream;
|
||||
|
||||
pub(crate) async fn run_external_command(
|
||||
pub(crate) fn run_external_command(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
@ -28,18 +25,19 @@ pub(crate) async fn run_external_command(
|
||||
) -> Result<InputStream, ShellError> {
|
||||
trace!(target: "nu::run::external", "-> {}", command.name);
|
||||
|
||||
if !did_find_command(&command.name) {
|
||||
context.sync_path_to_env();
|
||||
if !context.host.lock().is_external_cmd(&command.name) {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Command not found",
|
||||
"command not found",
|
||||
format!("command {} not found", &command.name),
|
||||
&command.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
run_with_stdin(command, context, input, external_redirection).await
|
||||
run_with_stdin(command, context, input, external_redirection)
|
||||
}
|
||||
|
||||
async fn run_with_stdin(
|
||||
fn run_with_stdin(
|
||||
command: ExternalCommand,
|
||||
context: &mut EvaluationContext,
|
||||
input: InputStream,
|
||||
@ -47,12 +45,10 @@ async fn run_with_stdin(
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let path = context.shell_manager.path();
|
||||
|
||||
let input = trace_stream!(target: "nu::trace_stream::external::stdin", "input" = input);
|
||||
|
||||
let mut command_args = vec![];
|
||||
for arg in command.args.iter() {
|
||||
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||
let value = evaluate_baseline_expr(arg, context).await?;
|
||||
let value = evaluate_baseline_expr(arg, context)?;
|
||||
|
||||
// Skip any arguments that don't really exist, treating them as optional
|
||||
// FIXME: we may want to preserve the gap in the future, though it's hard to say
|
||||
@ -219,7 +215,7 @@ fn spawn(
|
||||
.take()
|
||||
.expect("Internal error: could not get stdin pipe for external command");
|
||||
|
||||
for value in block_on_stream(input) {
|
||||
for value in input {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => continue,
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
@ -274,10 +270,12 @@ fn spawn(
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stdout);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
// let file = futures::io::AllowStdIo::new(stdout);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_read = BufReader::new(stdout);
|
||||
let buf_codec = BufCodecReader::new(buf_read, MaybeTextCodec::default());
|
||||
|
||||
for line in block_on_stream(stream) {
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
@ -345,10 +343,12 @@ fn spawn(
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stderr);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
// let file = futures::io::AllowStdIo::new(stderr);
|
||||
// let stream = FramedRead::new(file, MaybeTextCodec::default());
|
||||
let buf_reader = BufReader::new(stderr);
|
||||
let buf_codec = BufCodecReader::new(buf_reader, MaybeTextCodec::default());
|
||||
|
||||
for line in block_on_stream(stream) {
|
||||
for line in buf_codec {
|
||||
match line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
@ -432,7 +432,7 @@ fn spawn(
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let stream = ThreadedReceiver::new(rx);
|
||||
let stream = ChannelReceiver::new(rx);
|
||||
Ok(stream.to_input_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
@ -443,31 +443,26 @@ fn spawn(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
|
||||
#[cfg(not(feature = "which"))]
|
||||
{
|
||||
// we can't perform this check, so just assume it can be found
|
||||
true
|
||||
struct ChannelReceiver {
|
||||
rx: Arc<Mutex<mpsc::Receiver<Result<Value, ShellError>>>>,
|
||||
}
|
||||
|
||||
impl ChannelReceiver {
|
||||
pub fn new(rx: mpsc::Receiver<Result<Value, ShellError>>) -> Self {
|
||||
Self {
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "which", unix))]
|
||||
{
|
||||
which::which(name).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "which", windows))]
|
||||
{
|
||||
if which::which(name).is_ok() {
|
||||
true
|
||||
} else {
|
||||
// Reference: https://ss64.com/nt/syntax-internal.html
|
||||
let cmd_builtins = [
|
||||
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
|
||||
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
|
||||
"rmdir", "start", "time", "title", "type", "ver", "verify", "vol",
|
||||
];
|
||||
impl Iterator for ChannelReceiver {
|
||||
type Item = Result<Value, ShellError>;
|
||||
|
||||
cmd_builtins.contains(&name)
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rx = self.rx.lock();
|
||||
match rx.recv() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -535,16 +530,13 @@ mod tests {
|
||||
#[cfg(feature = "which")]
|
||||
use super::{run_external_command, InputStream};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use futures::executor::block_on;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_engine::basic_evaluation_context;
|
||||
#[cfg(feature = "which")]
|
||||
use nu_errors::ShellError;
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
use nu_test_support::commands::ExternalBuilder;
|
||||
// async fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next().await {
|
||||
// fn read(mut stream: OutputStream) -> Option<Value> {
|
||||
// match stream.try_next() {
|
||||
// Ok(val) => {
|
||||
// if let Some(val) = val {
|
||||
// val.raw_value()
|
||||
@ -557,7 +549,7 @@ mod tests {
|
||||
// }
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
async fn non_existent_run() -> Result<(), ShellError> {
|
||||
fn non_existent_run() {
|
||||
use nu_protocol::hir::ExternalRedirection;
|
||||
let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build();
|
||||
|
||||
@ -565,24 +557,18 @@ mod tests {
|
||||
let mut ctx =
|
||||
basic_evaluation_context().expect("There was a problem creating a basic context.");
|
||||
|
||||
assert!(
|
||||
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
assert!(run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout).is_err());
|
||||
}
|
||||
|
||||
// async fn failure_run() -> Result<(), ShellError> {
|
||||
// fn failure_run() -> Result<(), ShellError> {
|
||||
// let cmd = ExternalBuilder::for_name("fail").build();
|
||||
|
||||
// let mut ctx = crate::cli::basic_evaluation_context().expect("There was a problem creating a basic context.");
|
||||
// let stream = run_external_command(cmd, &mut ctx, None, false)
|
||||
// .await?
|
||||
// ?
|
||||
// .expect("There was a problem running the external command.");
|
||||
|
||||
// match read(stream.into()).await {
|
||||
// match read(stream.into()) {
|
||||
// Some(Value {
|
||||
// value: UntaggedValue::Error(_),
|
||||
// ..
|
||||
@ -600,8 +586,8 @@ mod tests {
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
#[test]
|
||||
fn identifies_command_not_found() -> Result<(), ShellError> {
|
||||
block_on(non_existent_run())
|
||||
fn identifies_command_not_found() {
|
||||
non_existent_run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -6,7 +6,6 @@ use std::process::Command;
|
||||
|
||||
pub struct Clear;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Clear {
|
||||
fn name(&self) -> &str {
|
||||
"clear"
|
||||
@ -17,10 +16,10 @@ impl WholeStreamCommand for Clear {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Clears the terminal"
|
||||
"Clears the terminal."
|
||||
}
|
||||
|
||||
async fn run(&self, _: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run(&self, _: CommandArgs) -> Result<InputStream, ShellError> {
|
||||
if cfg!(windows) {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", "cls"])
|
||||
@ -32,7 +31,7 @@ impl WholeStreamCommand for Clear {
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
Ok(InputStream::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, Value};
|
||||
@ -8,7 +8,6 @@ use arboard::Clipboard;
|
||||
|
||||
pub struct Clip;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Clip {
|
||||
fn name(&self) -> &str {
|
||||
"clip"
|
||||
@ -19,11 +18,11 @@ impl WholeStreamCommand for Clip {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Copy the contents of the pipeline to the copy/paste buffer"
|
||||
"Copy the contents of the pipeline to the copy/paste buffer."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clip(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
clip(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -42,10 +41,10 @@ impl WholeStreamCommand for Clip {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
pub fn clip(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let input = args.input;
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let values: Vec<Value> = input.collect().await;
|
||||
let name = args.call_info.name_tag;
|
||||
let values: Vec<Value> = input.collect();
|
||||
|
||||
if let Ok(mut clip_context) = Clipboard::new() {
|
||||
let mut new_copy_data = String::new();
|
||||
@ -89,7 +88,7 @@ pub async fn clip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
name,
|
||||
));
|
||||
}
|
||||
Ok(OutputStream::empty())
|
||||
Ok(ActionStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,21 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::Command;
|
||||
use nu_errors::ShellError;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub scope: Scope,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.scope.get_command(name)
|
||||
}
|
||||
}
|
@ -1,19 +1,16 @@
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Compact;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CompactArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
columns: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Compact {
|
||||
fn name(&self) -> &str {
|
||||
"compact"
|
||||
@ -24,11 +21,11 @@ impl WholeStreamCommand for Compact {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a table with non-empty rows"
|
||||
"Creates a table with non-empty rows."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
compact(args).await
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
compact(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -40,34 +37,28 @@ impl WholeStreamCommand for Compact {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CompactArgs { rest: columns }, input) = args.process().await?;
|
||||
pub fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (args, input) = args.extract(|params| {
|
||||
Ok(CompactArgs {
|
||||
columns: params.rest(0)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(input
|
||||
.filter_map(move |item| {
|
||||
future::ready(if columns.is_empty() {
|
||||
if !item.is_empty() {
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
match item {
|
||||
Value {
|
||||
.filter(move |item| {
|
||||
if args.columns.is_empty() {
|
||||
!item.is_empty()
|
||||
} else if let Value {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => {
|
||||
if columns
|
||||
} = item
|
||||
{
|
||||
args.columns
|
||||
.iter()
|
||||
.all(|field| r.get_data(field).borrow().is_some())
|
||||
{
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config clear"
|
||||
@ -19,8 +18,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clear(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
clear(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -32,18 +31,23 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
pub fn clear(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.clear();
|
||||
|
||||
config::write(&result, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
|
||||
let result = if let Some(global_cfg) = &mut args.configs.lock().global_config {
|
||||
global_cfg.vars.clear();
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(global_cfg.vars.clone().into()).into_value(args.call_info.name_tag),
|
||||
)))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ use nu_engine::CommandArgs;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_stream::OutputStream;
|
||||
use nu_stream::ActionStream;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"config"
|
||||
@ -21,14 +20,22 @@ impl WholeStreamCommand for Command {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
if let Some(global_cfg) = &args.configs.lock().global_config {
|
||||
let result = global_cfg.vars.clone();
|
||||
Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream())
|
||||
)]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,10 @@ use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedVal
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
path: ColumnPath,
|
||||
pub struct Arguments {
|
||||
column_path: ColumnPath,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config get"
|
||||
@ -28,8 +27,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
get(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -41,28 +40,29 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (GetArgs { path }, _) = args.process().await?;
|
||||
pub fn get(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
|
||||
|
||||
let value = crate::commands::get::get_column_path(&path, &result)?;
|
||||
let (Arguments { column_path }, _) = args.process()?;
|
||||
|
||||
let result = if let Some(global_cfg) = &ctx.configs.lock().global_config {
|
||||
let result = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
|
||||
let value = crate::commands::get::get_column_path(&column_path, &result)?;
|
||||
Ok(match value {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => {
|
||||
let list: Vec<_> = list
|
||||
.iter()
|
||||
.map(|x| ReturnSuccess::value(x.clone()))
|
||||
.collect();
|
||||
|
||||
futures::stream::iter(list).to_output_stream()
|
||||
}
|
||||
x => OutputStream::one(ReturnSuccess::value(x)),
|
||||
} => list.into_iter().to_action_stream(),
|
||||
x => ActionStream::one(ReturnSuccess::value(x)),
|
||||
})
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoadArgs {
|
||||
load: Tagged<PathBuf>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config load"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config load").required(
|
||||
"load",
|
||||
SyntaxShape::FilePath,
|
||||
"Path to load the config from",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Loads the config from the path given"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (LoadArgs { load }, _) = args.process().await?;
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream())
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
pub mod clear;
|
||||
pub mod command;
|
||||
pub mod get;
|
||||
pub mod load;
|
||||
pub mod path;
|
||||
pub mod remove;
|
||||
pub mod set;
|
||||
@ -10,8 +9,13 @@ pub mod set_into;
|
||||
pub use clear::SubCommand as ConfigClear;
|
||||
pub use command::Command as Config;
|
||||
pub use get::SubCommand as ConfigGet;
|
||||
pub use load::SubCommand as ConfigLoad;
|
||||
pub use path::SubCommand as ConfigPath;
|
||||
pub use remove::SubCommand as ConfigRemove;
|
||||
pub use set::SubCommand as ConfigSet;
|
||||
pub use set_into::SubCommand as ConfigSetInto;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
|
||||
pub fn err_no_global_cfg_present() -> ShellError {
|
||||
ShellError::untagged_runtime_error("No global config found!")
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config path"
|
||||
@ -19,8 +18,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
path(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
path(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -32,10 +31,16 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let path = config::default_path()?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::FilePath(path)).into_value(args.call_info.name_tag),
|
||||
pub fn path(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
if let Some(global_cfg) = &mut args.configs.lock().global_config {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::FilePath(global_cfg.file_path.clone())),
|
||||
)))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ use nu_source::Tagged;
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveArgs {
|
||||
pub struct Arguments {
|
||||
remove: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config remove"
|
||||
@ -29,8 +28,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
remove(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -42,21 +41,22 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process().await?;
|
||||
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
pub fn remove(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (Arguments { remove }, _) = args.process()?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
if result.contains_key(&key) {
|
||||
result.swap_remove(&key);
|
||||
config::write(&result, &None)?;
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(remove.tag()),
|
||||
)])
|
||||
.to_output_stream())
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
if global_cfg.vars.contains_key(&key) {
|
||||
global_cfg.vars.swap_remove(&key);
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
Ok(vec![ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(remove.tag()),
|
||||
)]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
@ -64,4 +64,13 @@ pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -6,12 +6,11 @@ use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedVal
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
path: ColumnPath,
|
||||
pub struct Arguments {
|
||||
column_path: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set"
|
||||
@ -27,8 +26,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
set(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -57,14 +56,19 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (SetArgs { path, mut value }, _) = args.process().await?;
|
||||
pub fn set(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (
|
||||
Arguments {
|
||||
column_path,
|
||||
mut value,
|
||||
},
|
||||
_,
|
||||
) = args.process()?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let raw_entries = nu_data::config::read(&name_tag, &None)?;
|
||||
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
let configuration = UntaggedValue::row(global_cfg.vars.clone()).into_value(&name);
|
||||
|
||||
if let UntaggedValue::Table(rows) = &value.value {
|
||||
if rows.len() == 1 && rows[0].is_row() {
|
||||
@ -72,18 +76,29 @@ pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
}
|
||||
}
|
||||
|
||||
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
||||
match configuration.forgiving_insert_data_at_column_path(&column_path, value) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Row(changes),
|
||||
..
|
||||
}) => {
|
||||
config::write(&changes.entries, &None)?;
|
||||
global_cfg.vars = changes.entries;
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(changes).into_value(name_tag),
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(name),
|
||||
)))
|
||||
}
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Ok(_) => Ok(ActionStream::empty()),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ use nu_source::Tagged;
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetIntoArgs {
|
||||
pub struct Arguments {
|
||||
set_into: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set_into"
|
||||
@ -29,8 +28,8 @@ impl WholeStreamCommand for SubCommand {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set_into(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
set_into(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -42,23 +41,16 @@ impl WholeStreamCommand for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
pub fn set_into(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let (Arguments { set_into: v }, input) = args.process()?;
|
||||
|
||||
let (SetIntoArgs { set_into: v }, input) = args.process().await?;
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
// In the original code, this is set to `Some` if the `load flag is set`
|
||||
let configuration = None;
|
||||
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
let rows: Vec<Value> = input.collect();
|
||||
let key = v.to_string();
|
||||
|
||||
Ok(if rows.is_empty() {
|
||||
let result = if let Some(global_cfg) = &mut ctx.configs.lock().global_config {
|
||||
if rows.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No values given for set_into",
|
||||
"needs value(s) from pipeline",
|
||||
@ -68,23 +60,27 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
// A single value
|
||||
let value = &rows[0];
|
||||
|
||||
result.insert(key, value.clone());
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
global_cfg.vars.insert(key, value.clone());
|
||||
} else {
|
||||
// Take in the pipeline as a table
|
||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||
|
||||
result.insert(key, value);
|
||||
global_cfg.vars.insert(key, value);
|
||||
}
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
global_cfg.write()?;
|
||||
ctx.reload_config(global_cfg)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
})
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::row(global_cfg.vars.clone()).into_value(name),
|
||||
)))
|
||||
} else {
|
||||
Ok(vec![ReturnSuccess::value(UntaggedValue::Error(
|
||||
crate::commands::config::err_no_global_cfg_present(),
|
||||
))]
|
||||
.into_iter()
|
||||
.to_action_stream())
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Count;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CountArgs {
|
||||
column: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Count {
|
||||
fn name(&self) -> &str {
|
||||
"count"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("count").switch(
|
||||
"column",
|
||||
"Calculate number of columns in table",
|
||||
Some('c'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show the total number of rows or items."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (CountArgs { column }, input) = args.process().await?;
|
||||
let rows: Vec<Value> = input.collect().await;
|
||||
|
||||
let count = if column {
|
||||
if rows.is_empty() {
|
||||
0
|
||||
} else {
|
||||
match &rows[0].value {
|
||||
UntaggedValue::Row(dictionary) => dictionary.length(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Cannot obtain column count",
|
||||
"cannot obtain column count",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rows.len()
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(UntaggedValue::int(count).into_value(tag)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Count the number of entries in a list",
|
||||
example: "echo [1 2 3 4 5] | count",
|
||||
result: Some(vec![UntaggedValue::int(5).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Count the number of columns in the calendar table",
|
||||
example: "cal | count -c",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Count;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Count {})
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Cpy;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Cpy {
|
||||
fn name(&self) -> &str {
|
||||
"cp"
|
||||
@ -26,10 +25,10 @@ impl WholeStreamCommand for Cpy {
|
||||
"Copy files."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _) = args.process().await?;
|
||||
let (args, _) = args.process()?;
|
||||
shell_manager.cp(args, name)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"date"
|
||||
@ -16,12 +15,12 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Apply date function"
|
||||
"Apply date function."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Ok(ActionStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ pub struct FormatArgs {
|
||||
table: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date format"
|
||||
@ -31,8 +30,8 @@ impl WholeStreamCommand for Date {
|
||||
"Format a given date using the given format string."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
format(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
format(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -51,9 +50,9 @@ impl WholeStreamCommand for Date {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn format(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
pub fn format(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (FormatArgs { format, table }, input) = args.process().await?;
|
||||
let (FormatArgs { format, table }, input) = args.process()?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
@ -92,7 +91,7 @@ pub async fn format(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -7,7 +7,6 @@ use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date list-timezone"
|
||||
@ -21,8 +20,8 @@ impl WholeStreamCommand for Date {
|
||||
"List supported time zones."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
list_timezone(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
list_timezone(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -41,8 +40,8 @@ impl WholeStreamCommand for Date {
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
fn list_timezone(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let list = TZ_VARIANTS.iter().map(move |tz| {
|
||||
@ -58,7 +57,7 @@ async fn list_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
))
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(list).to_output_stream())
|
||||
Ok(list.into_iter().to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -6,7 +6,6 @@ use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date now"
|
||||
@ -20,20 +19,20 @@ impl WholeStreamCommand for Date {
|
||||
"Get the current date."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
now(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
now(args)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn now(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
pub fn now(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let now: DateTime<Local> = Local::now();
|
||||
|
||||
let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
Ok(ActionStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -7,7 +7,6 @@ use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-table"
|
||||
@ -21,8 +20,8 @@ impl WholeStreamCommand for Date {
|
||||
"Print the date in a structured table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
to_table(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
to_table(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -34,8 +33,8 @@ impl WholeStreamCommand for Date {
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_table(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
fn to_table(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let input = args.input;
|
||||
|
||||
@ -88,7 +87,7 @@ async fn to_table(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -12,7 +12,6 @@ struct DateToTimeZoneArgs {
|
||||
timezone: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-timezone"
|
||||
@ -27,14 +26,15 @@ impl WholeStreamCommand for Date {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert a date to a given time zone.
|
||||
|
||||
Use `date list-timezone` to list all supported time zones.
|
||||
"
|
||||
"Convert a date to a given time zone."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
to_timezone(args).await
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Use 'date list-timezone' to list all supported time zones."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
to_timezone(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -58,9 +58,9 @@ Use `date list-timezone` to list all supported time zones.
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn to_timezone(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DateToTimeZoneArgs { timezone }, input) = args.process().await?;
|
||||
let (DateToTimeZoneArgs { timezone }, input) = args.process()?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
@ -85,7 +85,7 @@ async fn to_timezone(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
fn error_message(err: ParseErrorKind) -> &'static str {
|
||||
|
@ -8,7 +8,6 @@ use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date utc"
|
||||
@ -22,13 +21,13 @@ impl WholeStreamCommand for Date {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
utc(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
utc(args)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
pub fn utc(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
|
@ -10,7 +10,6 @@ pub struct DebugArgs {
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Debug {
|
||||
fn name(&self) -> &str {
|
||||
"debug"
|
||||
@ -21,16 +20,16 @@ impl WholeStreamCommand for Debug {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the Rust debug representation of the values"
|
||||
"Print the Rust debug representation of the values."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
debug_value(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
debug_value(args)
|
||||
}
|
||||
}
|
||||
|
||||
async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DebugArgs { raw }, input) = args.process().await?;
|
||||
fn debug_value(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (DebugArgs { raw }, input) = args.process()?;
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if raw {
|
||||
@ -41,7 +40,7 @@ async fn debug_value(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
ReturnSuccess::debug_value(v)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -14,7 +14,6 @@ pub struct DefArgs {
|
||||
pub block: CapturedBlock,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Def {
|
||||
fn name(&self) -> &str {
|
||||
"def"
|
||||
@ -35,11 +34,11 @@ impl WholeStreamCommand for Def {
|
||||
"Create a command and set it to a definition."
|
||||
}
|
||||
|
||||
async fn run(&self, _args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, _args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
// Currently, we don't do anything here because we should have already
|
||||
// installed the definition as we entered the scope
|
||||
// We just create a command so that we can get proper coloring
|
||||
Ok(OutputStream::empty())
|
||||
Ok(ActionStream::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -13,7 +13,6 @@ struct DefaultArgs {
|
||||
|
||||
pub struct Default;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Default {
|
||||
fn name(&self) -> &str {
|
||||
"default"
|
||||
@ -33,8 +32,8 @@ impl WholeStreamCommand for Default {
|
||||
"Sets a default row's column if missing."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
default(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
default(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -46,8 +45,8 @@ impl WholeStreamCommand for Default {
|
||||
}
|
||||
}
|
||||
|
||||
async fn default(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DefaultArgs { column, value }, input) = args.process().await?;
|
||||
fn default(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (DefaultArgs { column, value }, input) = args.process()?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
@ -68,7 +67,7 @@ async fn default(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
ReturnSuccess::value(item)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -29,7 +29,6 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(ConfigSet),
|
||||
whole_stream_command(ConfigSetInto),
|
||||
whole_stream_command(ConfigClear),
|
||||
whole_stream_command(ConfigLoad),
|
||||
whole_stream_command(ConfigRemove),
|
||||
whole_stream_command(ConfigPath),
|
||||
whole_stream_command(Help),
|
||||
@ -57,7 +56,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(Sleep),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
whole_stream_command(Length),
|
||||
whole_stream_command(Benchmark),
|
||||
// Metadata
|
||||
whole_stream_command(Tags),
|
||||
@ -75,6 +74,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
// Text manipulation
|
||||
whole_stream_command(Hash),
|
||||
whole_stream_command(HashBase64),
|
||||
whole_stream_command(HashMd5),
|
||||
whole_stream_command(Split),
|
||||
whole_stream_command(SplitColumn),
|
||||
whole_stream_command(SplitRow),
|
||||
@ -111,17 +111,22 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(StrScreamingSnakeCase),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
whole_stream_command(AnsiStrip),
|
||||
whole_stream_command(Char),
|
||||
// Column manipulation
|
||||
whole_stream_command(DropColumn),
|
||||
whole_stream_command(Move),
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
whole_stream_command(Get),
|
||||
whole_stream_command(Update),
|
||||
whole_stream_command(Insert),
|
||||
whole_stream_command(Into),
|
||||
whole_stream_command(IntoInt),
|
||||
whole_stream_command(SplitBy),
|
||||
// Row manipulation
|
||||
whole_stream_command(All),
|
||||
whole_stream_command(Any),
|
||||
whole_stream_command(Reverse),
|
||||
whole_stream_command(Append),
|
||||
whole_stream_command(Prepend),
|
||||
@ -161,6 +166,11 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(Pivot),
|
||||
whole_stream_command(Headers),
|
||||
whole_stream_command(Reduce),
|
||||
whole_stream_command(Roll),
|
||||
whole_stream_command(RollColumn),
|
||||
whole_stream_command(RollUp),
|
||||
whole_stream_command(Rotate),
|
||||
whole_stream_command(RotateCounterClockwise),
|
||||
// Data processing
|
||||
whole_stream_command(Histogram),
|
||||
whole_stream_command(Autoenv),
|
||||
@ -181,32 +191,33 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(MathRound),
|
||||
whole_stream_command(MathFloor),
|
||||
whole_stream_command(MathCeil),
|
||||
whole_stream_command(MathSqrt),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
whole_stream_command(ToHTML),
|
||||
whole_stream_command(ToJSON),
|
||||
whole_stream_command(ToCsv),
|
||||
whole_stream_command(ToHtml),
|
||||
whole_stream_command(ToJson),
|
||||
whole_stream_command(ToMarkdown),
|
||||
whole_stream_command(ToTOML),
|
||||
whole_stream_command(ToTSV),
|
||||
whole_stream_command(ToURL),
|
||||
whole_stream_command(ToYAML),
|
||||
whole_stream_command(ToXML),
|
||||
whole_stream_command(ToToml),
|
||||
whole_stream_command(ToTsv),
|
||||
whole_stream_command(ToUrl),
|
||||
whole_stream_command(ToYaml),
|
||||
whole_stream_command(ToXml),
|
||||
// File format input
|
||||
whole_stream_command(From),
|
||||
whole_stream_command(FromCSV),
|
||||
whole_stream_command(FromEML),
|
||||
whole_stream_command(FromTSV),
|
||||
whole_stream_command(FromSSV),
|
||||
whole_stream_command(FromINI),
|
||||
whole_stream_command(FromJSON),
|
||||
whole_stream_command(FromODS),
|
||||
whole_stream_command(FromTOML),
|
||||
whole_stream_command(FromURL),
|
||||
whole_stream_command(FromXLSX),
|
||||
whole_stream_command(FromXML),
|
||||
whole_stream_command(FromYAML),
|
||||
whole_stream_command(FromYML),
|
||||
whole_stream_command(FromCsv),
|
||||
whole_stream_command(FromEml),
|
||||
whole_stream_command(FromTsv),
|
||||
whole_stream_command(FromSsv),
|
||||
whole_stream_command(FromIni),
|
||||
whole_stream_command(FromJson),
|
||||
whole_stream_command(FromOds),
|
||||
whole_stream_command(FromToml),
|
||||
whole_stream_command(FromUrl),
|
||||
whole_stream_command(FromXlsx),
|
||||
whole_stream_command(FromXml),
|
||||
whole_stream_command(FromYaml),
|
||||
whole_stream_command(FromYml),
|
||||
whole_stream_command(FromIcs),
|
||||
whole_stream_command(FromVcf),
|
||||
// "Private" commands (not intended to be accessed directly)
|
||||
@ -226,8 +237,9 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(PathDirname),
|
||||
whole_stream_command(PathExists),
|
||||
whole_stream_command(PathExpand),
|
||||
whole_stream_command(PathExtension),
|
||||
whole_stream_command(PathFilestem),
|
||||
whole_stream_command(PathJoin),
|
||||
whole_stream_command(PathParse),
|
||||
whole_stream_command(PathSplit),
|
||||
whole_stream_command(PathType),
|
||||
// Url
|
||||
whole_stream_command(UrlCommand),
|
||||
|
@ -9,7 +9,6 @@ pub struct Describe;
|
||||
#[derive(Deserialize)]
|
||||
pub struct DescribeArgs {}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Describe {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
@ -23,12 +22,12 @@ impl WholeStreamCommand for Describe {
|
||||
"Describes the objects in the stream."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
describe(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
describe(args)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn describe(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
pub fn describe(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(|row| {
|
||||
@ -37,7 +36,7 @@ pub async fn describe(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
UntaggedValue::string(name).into_value(Tag::unknown_anchor(row.tag.span)),
|
||||
)
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -12,7 +12,6 @@ struct DoArgs {
|
||||
ignore_errors: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Do {
|
||||
fn name(&self) -> &str {
|
||||
"do"
|
||||
@ -29,11 +28,11 @@ impl WholeStreamCommand for Do {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block, optionally ignoring errors"
|
||||
"Runs a block, optionally ignoring errors."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
do_(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
do_(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -52,7 +51,7 @@ impl WholeStreamCommand for Do {
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn do_(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let external_redirection = raw_args.call_info.args.external_redirection;
|
||||
|
||||
let context = EvaluationContext::from_args(&raw_args);
|
||||
@ -62,7 +61,7 @@ async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
mut block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process().await?;
|
||||
) = raw_args.process()?;
|
||||
|
||||
let block_redirection = match external_redirection {
|
||||
ExternalRedirection::None => {
|
||||
@ -82,9 +81,11 @@ async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
x => x,
|
||||
};
|
||||
|
||||
block.block.set_redirect(block_redirection);
|
||||
if let Some(block) = std::sync::Arc::<nu_protocol::hir::Block>::get_mut(&mut block.block) {
|
||||
block.set_redirect(block_redirection);
|
||||
}
|
||||
context.scope.enter_scope();
|
||||
let result = run_block(&block.block, &context, input).await;
|
||||
let result = run_block(&block.block, &context, input);
|
||||
context.scope.exit_scope();
|
||||
|
||||
if ignore_errors {
|
||||
@ -93,14 +94,14 @@ async fn do_(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec().await;
|
||||
let output = stream.drain_vec();
|
||||
context.clear_errors();
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
Ok(output.into_iter().to_action_stream())
|
||||
}
|
||||
Err(_) => Ok(OutputStream::empty()),
|
||||
Err(_) => Ok(ActionStream::empty()),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_output_stream())
|
||||
result.map(|x| x.to_action_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
85
crates/nu-command/src/commands/drop/column.rs
Normal file
85
crates/nu-command/src/commands/drop/column.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
columns: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"drop column"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("drop column").optional(
|
||||
"columns",
|
||||
SyntaxShape::Number,
|
||||
"starting from the end, the number of columns to remove",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove the last number of columns. If you want to remove columns by name, try 'reject'."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
drop(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
use nu_protocol::Value;
|
||||
|
||||
vec![Example {
|
||||
description: "Remove the last column of a table",
|
||||
example: "echo [[lib, extension]; [nu-lib, rs] [nu-core, rb]] | drop column",
|
||||
result: Some(vec![
|
||||
row! { "lib".into() => Value::from("nu-lib") },
|
||||
row! { "lib".into() => Value::from("nu-core") },
|
||||
]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (Arguments { columns }, input) = args.process()?;
|
||||
|
||||
let to_drop = if let Some(quantity) = columns {
|
||||
*quantity as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(input
|
||||
.map(move |item| {
|
||||
let headers = item.data_descriptors();
|
||||
|
||||
let descs = match headers.len() {
|
||||
0 => &headers[..],
|
||||
n if to_drop > n => &[],
|
||||
n => &headers[..n - to_drop],
|
||||
};
|
||||
|
||||
select_fields(&item, descs, item.tag())
|
||||
})
|
||||
.map(ReturnSuccess::value)
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -4,15 +4,14 @@ use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Drop;
|
||||
pub struct Command;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DropArgs {
|
||||
pub struct Arguments {
|
||||
rows: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Drop {
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"drop"
|
||||
}
|
||||
@ -26,11 +25,11 @@ impl WholeStreamCommand for Drop {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove the last number of rows. If you want to remove columns, try 'reject'."
|
||||
"Remove the last number of rows or columns."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
drop(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
drop(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -52,9 +51,9 @@ impl WholeStreamCommand for Drop {
|
||||
}
|
||||
}
|
||||
|
||||
async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (DropArgs { rows }, input) = args.process().await?;
|
||||
let v: Vec<_> = input.into_vec().await;
|
||||
fn drop(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (Arguments { rows }, input) = args.process()?;
|
||||
let v: Vec<_> = input.into_vec();
|
||||
|
||||
let rows_to_drop = if let Some(quantity) = rows {
|
||||
*quantity as usize
|
||||
@ -63,7 +62,7 @@ async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
};
|
||||
|
||||
Ok(if rows_to_drop == 0 {
|
||||
futures::stream::iter(v).to_output_stream()
|
||||
v.into_iter().to_action_stream()
|
||||
} else {
|
||||
let k = if v.len() < rows_to_drop {
|
||||
0
|
||||
@ -73,19 +72,6 @@ async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
|
||||
let iter = v.into_iter().take(k);
|
||||
|
||||
futures::stream::iter(iter).to_output_stream()
|
||||
iter.to_action_stream()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Drop;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Drop {})
|
||||
}
|
||||
}
|
5
crates/nu-command/src/commands/drop/mod.rs
Normal file
5
crates/nu-command/src/commands/drop/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod column;
|
||||
mod command;
|
||||
|
||||
pub use column::SubCommand as DropColumn;
|
||||
pub use command::Command as Drop;
|
@ -28,7 +28,6 @@ pub struct DuArgs {
|
||||
min_size: Option<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Du {
|
||||
fn name(&self) -> &str {
|
||||
NAME
|
||||
@ -68,11 +67,11 @@ impl WholeStreamCommand for Du {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find disk usage sizes of specified items"
|
||||
"Find disk usage sizes of specified items."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
du(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
du(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -84,12 +83,12 @@ impl WholeStreamCommand for Du {
|
||||
}
|
||||
}
|
||||
|
||||
async fn du(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn du(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c.clone();
|
||||
let ctrl_c_copy = ctrl_c.clone();
|
||||
|
||||
let (args, _): (DuArgs, _) = args.process().await?;
|
||||
let (args, _): (DuArgs, _) = args.process()?;
|
||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||
Pattern::new(&x.item)
|
||||
.map(Option::Some)
|
||||
@ -131,7 +130,7 @@ async fn du(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
all,
|
||||
};
|
||||
|
||||
let inp = futures::stream::iter(paths);
|
||||
let inp = paths;
|
||||
|
||||
Ok(inp
|
||||
.flat_map(move |path| match path {
|
||||
@ -146,12 +145,12 @@ async fn du(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
output.push(Ok(ReturnSuccess::Value(v.into())));
|
||||
}
|
||||
}
|
||||
futures::stream::iter(output)
|
||||
output
|
||||
}
|
||||
Err(e) => futures::stream::iter(vec![Err(e)]),
|
||||
Err(e) => vec![Err(e)],
|
||||
})
|
||||
.interruptible(ctrl_c_copy)
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
fn glob_err_into(e: GlobError) -> ShellError {
|
||||
|
@ -2,22 +2,13 @@ use crate::prelude::*;
|
||||
use nu_engine::run_block;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Each;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachArgs {
|
||||
block: CapturedBlock,
|
||||
numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Each {
|
||||
fn name(&self) -> &str {
|
||||
"each"
|
||||
@ -37,8 +28,8 @@ impl WholeStreamCommand for Each {
|
||||
"Run a block on each row of the table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
each(args).await
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
each(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -67,7 +58,7 @@ impl WholeStreamCommand for Each {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_row(
|
||||
pub fn process_row(
|
||||
captured_block: Arc<Box<CapturedBlock>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
@ -80,7 +71,7 @@ pub async fn process_row(
|
||||
let input_stream = if !captured_block.block.params.positional.is_empty() {
|
||||
InputStream::empty()
|
||||
} else {
|
||||
once(async { Ok(input_clone) }).to_input_stream()
|
||||
vec![Ok(input_clone)].into_iter().to_input_stream()
|
||||
};
|
||||
|
||||
context.scope.enter_scope();
|
||||
@ -95,11 +86,11 @@ pub async fn process_row(
|
||||
context.scope.add_var("$it", input);
|
||||
}
|
||||
|
||||
let result = run_block(&captured_block.block, &*context, input_stream).await;
|
||||
let result = run_block(&captured_block.block, &*context, input_stream);
|
||||
|
||||
context.scope.exit_scope();
|
||||
|
||||
Ok(result?.to_output_stream())
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
@ -110,40 +101,41 @@ pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value {
|
||||
dict.into_value()
|
||||
}
|
||||
|
||||
async fn each(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn each(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_args(&raw_args));
|
||||
let args = raw_args.evaluate_once()?;
|
||||
|
||||
let (each_args, input): (EachArgs, _) = raw_args.process().await?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
let block: CapturedBlock = args.req(0)?;
|
||||
let numbered: bool = args.has_flag("numbered");
|
||||
|
||||
if each_args.numbered.item {
|
||||
Ok(input
|
||||
let block = Arc::new(Box::new(block));
|
||||
|
||||
if numbered {
|
||||
Ok(args
|
||||
.input
|
||||
.enumerate()
|
||||
.then(move |input| {
|
||||
.map(move |input| {
|
||||
let block = block.clone();
|
||||
let context = context.clone();
|
||||
let row = make_indexed_item(input.0, input.1);
|
||||
|
||||
async {
|
||||
match process_row(block, context, row).await {
|
||||
match process_row(block, context, row) {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
Err(e) => OutputStream::one(Value::error(e)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
} else {
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |input| {
|
||||
let block = block.clone();
|
||||
let context = context.clone();
|
||||
|
||||
async {
|
||||
match process_row(block, context, input).await {
|
||||
match process_row(block, context, input) {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
Err(e) => OutputStream::one(Value::error(e)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
|
@ -2,9 +2,7 @@ use crate::commands::each::process_row;
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_protocol::{hir::CapturedBlock, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
@ -17,7 +15,6 @@ pub struct EachGroupArgs {
|
||||
//numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachGroup {
|
||||
fn name(&self) -> &str {
|
||||
"each group"
|
||||
@ -45,16 +42,54 @@ impl WholeStreamCommand for EachGroup {
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(&self, raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_args(&raw_args));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process().await?;
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process()?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
|
||||
Ok(input
|
||||
.chunks(each_args.group_size.item)
|
||||
.then(move |input| run_block_on_vec(input, block.clone(), context.clone()))
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
let each_group_iterator = EachGroupIterator {
|
||||
block,
|
||||
context,
|
||||
group_size: each_args.group_size.item,
|
||||
input,
|
||||
};
|
||||
|
||||
Ok(each_group_iterator.flatten().to_action_stream())
|
||||
}
|
||||
}
|
||||
|
||||
struct EachGroupIterator {
|
||||
block: Arc<Box<CapturedBlock>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
group_size: usize,
|
||||
input: InputStream,
|
||||
}
|
||||
|
||||
impl Iterator for EachGroupIterator {
|
||||
type Item = OutputStream;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut group = vec![];
|
||||
let mut current_count = 0;
|
||||
|
||||
while let Some(next) = self.input.next() {
|
||||
group.push(next);
|
||||
|
||||
current_count += 1;
|
||||
if current_count >= self.group_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if group.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(run_block_on_vec(
|
||||
group,
|
||||
self.block.clone(),
|
||||
self.context.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,43 +97,32 @@ pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Box<CapturedBlock>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
) -> OutputStream {
|
||||
let value = Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag: Tag::unknown(),
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, context, value).await {
|
||||
match process_row(block, context, value) {
|
||||
Ok(s) => {
|
||||
// We need to handle this differently depending on whether process_row
|
||||
// returned just 1 value or if it returned multiple as a stream.
|
||||
let vec = s.collect::<Vec<_>>().await;
|
||||
let vec = s.collect::<Vec<_>>();
|
||||
|
||||
// If it returned just one value, just take that value
|
||||
if vec.len() == 1 {
|
||||
return OutputStream::one(vec.into_iter().next().expect(
|
||||
"This should be impossible, we just checked that vec.len() == 1.",
|
||||
));
|
||||
return OutputStream::one(
|
||||
vec.into_iter()
|
||||
.next()
|
||||
.expect("This should be impossible, we just checked that vec.len() == 1."),
|
||||
);
|
||||
}
|
||||
|
||||
// If it returned multiple values, we need to put them into a table and
|
||||
// return that.
|
||||
let result = vec.into_iter().collect::<Result<Vec<ReturnSuccess>, _>>();
|
||||
let result_table = match result {
|
||||
Ok(t) => t,
|
||||
Err(e) => return OutputStream::one(Err(e)),
|
||||
};
|
||||
|
||||
let table = result_table
|
||||
.into_iter()
|
||||
.filter_map(|x| x.raw_value())
|
||||
.collect();
|
||||
|
||||
OutputStream::one(Ok(ReturnSuccess::Value(UntaggedValue::Table(table).into())))
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
OutputStream::one(UntaggedValue::Table(vec).into_untagged_value())
|
||||
}
|
||||
Err(e) => OutputStream::one(Value::error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ pub struct EachWindowArgs {
|
||||
stride: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachWindow {
|
||||
fn name(&self) -> &str {
|
||||
"each window"
|
||||
@ -50,16 +49,15 @@ impl WholeStreamCommand for EachWindow {
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(&self, raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn run_with_actions(&self, raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_args(&raw_args));
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process().await?;
|
||||
let (each_args, mut input): (EachWindowArgs, _) = raw_args.process()?;
|
||||
let block = Arc::new(Box::new(each_args.block));
|
||||
|
||||
let mut window: Vec<_> = input
|
||||
.by_ref()
|
||||
.take(*each_args.window_size - 1)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// `window` must start with dummy values, which will be removed on the first iteration
|
||||
let stride = each_args.stride.map(|x| *x).unwrap_or(1);
|
||||
@ -67,7 +65,7 @@ impl WholeStreamCommand for EachWindow {
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.then(move |(i, input)| {
|
||||
.map(move |(i, input)| {
|
||||
// This would probably be more efficient if `last` was a VecDeque
|
||||
// But we can't have that because it needs to be put into a Table
|
||||
window.remove(0);
|
||||
@ -77,17 +75,15 @@ impl WholeStreamCommand for EachWindow {
|
||||
let context = context.clone();
|
||||
let local_window = window.clone();
|
||||
|
||||
async move {
|
||||
if i % stride == 0 {
|
||||
Some(run_block_on_vec(local_window, block, context).await)
|
||||
Some(run_block_on_vec(local_window, block, context))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|x| async { x })
|
||||
.filter_map(|x| x)
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,12 @@
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::Zero;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{
|
||||
Primitive, Range, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_protocol::{Primitive, Range, RangeInclusion, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Echo;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Echo {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
@ -27,8 +20,8 @@ impl WholeStreamCommand for Echo {
|
||||
"Echo the arguments back to the user."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
echo(args).await
|
||||
fn run(&self, args: CommandArgs) -> Result<InputStream, ShellError> {
|
||||
echo(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -47,36 +40,37 @@ impl WholeStreamCommand for Echo {
|
||||
}
|
||||
}
|
||||
|
||||
async fn echo(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (args, _): (EchoArgs, _) = args.process().await?;
|
||||
fn echo(args: CommandArgs) -> Result<InputStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
let rest: Vec<Value> = args.rest(0)?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i.as_string() {
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||
))),
|
||||
let stream = rest.into_iter().map(|i| match i.as_string() {
|
||||
Ok(s) => InputStream::one(UntaggedValue::string(s).into_value(i.tag.clone())),
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
} => InputStream::from_stream(table.into_iter()),
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||
tag,
|
||||
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
|
||||
x => OutputStream::one(Ok(ReturnSuccess::Value(x))),
|
||||
} => InputStream::from_stream(RangeIterator::new(*range, tag)),
|
||||
x => InputStream::one(x),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
Ok(InputStream::from_stream(stream.flatten()))
|
||||
}
|
||||
|
||||
struct RangeIterator {
|
||||
curr: Primitive,
|
||||
end: Primitive,
|
||||
curr: UntaggedValue,
|
||||
end: UntaggedValue,
|
||||
tag: Tag,
|
||||
is_end_inclusive: bool,
|
||||
moves_up: bool,
|
||||
one: UntaggedValue,
|
||||
negative_one: UntaggedValue,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
@ -93,98 +87,104 @@ impl RangeIterator {
|
||||
|
||||
RangeIterator {
|
||||
moves_up: start <= end,
|
||||
curr: start,
|
||||
end,
|
||||
curr: UntaggedValue::Primitive(start),
|
||||
end: UntaggedValue::Primitive(end),
|
||||
tag,
|
||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||
one: UntaggedValue::int(1),
|
||||
negative_one: UntaggedValue::int(-1),
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Result<ReturnSuccess, ShellError>;
|
||||
type Item = Value;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let ordering = if self.end == Primitive::Nothing {
|
||||
use std::cmp::Ordering;
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ordering = if self.end == UntaggedValue::Primitive(Primitive::Nothing) {
|
||||
Ordering::Less
|
||||
} else {
|
||||
let result =
|
||||
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
match (&self.curr, &self.end) {
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Int(x)),
|
||||
UntaggedValue::Primitive(Primitive::Int(y)),
|
||||
) => x.cmp(y),
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Decimal(x)),
|
||||
UntaggedValue::Primitive(Primitive::Decimal(y)),
|
||||
) => x.cmp(y),
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Decimal(x)),
|
||||
UntaggedValue::Primitive(Primitive::Int(y)),
|
||||
) => x.cmp(&(BigDecimal::zero() + y)),
|
||||
(
|
||||
UntaggedValue::Primitive(Primitive::Int(x)),
|
||||
UntaggedValue::Primitive(Primitive::Decimal(y)),
|
||||
) => (BigDecimal::zero() + x).cmp(y),
|
||||
_ => {
|
||||
self.done = true;
|
||||
return Some(
|
||||
UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Cannot create range",
|
||||
"unsupported range",
|
||||
self.tag.span,
|
||||
)
|
||||
});
|
||||
|
||||
if let Err(result) = result {
|
||||
return Some(Err(result));
|
||||
))
|
||||
.into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = result
|
||||
.expect("Internal error: the error case was already protected, but that failed");
|
||||
|
||||
result.compare()
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
if self.moves_up
|
||||
&& (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal)
|
||||
{
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
let next_value = nu_data::value::compute_values(Operator::Plus, &self.curr, &self.one);
|
||||
|
||||
let next_value = nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
);
|
||||
let mut next = match next_value {
|
||||
Ok(result) => result,
|
||||
|
||||
self.curr = match next_value {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
return Some(Err(ShellError::unimplemented(
|
||||
"Internal error: expected a primitive result from increment",
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
return Some(Err(ShellError::coerce_error(
|
||||
self.done = true;
|
||||
return Some(
|
||||
UntaggedValue::Error(ShellError::coerce_error(
|
||||
left_type.spanned(self.tag.span),
|
||||
right_type.spanned(self.tag.span),
|
||||
)));
|
||||
))
|
||||
.into_untagged_value(),
|
||||
);
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
std::mem::swap(&mut self.curr, &mut next);
|
||||
|
||||
Some(next.into_value(self.tag.clone()))
|
||||
} else if !self.moves_up
|
||||
&& (ordering == Ordering::Greater
|
||||
|| self.is_end_inclusive && ordering == Ordering::Equal)
|
||||
{
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
let next_value =
|
||||
nu_data::value::compute_values(Operator::Plus, &self.curr, &self.negative_one);
|
||||
|
||||
let next_value = nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(-1),
|
||||
);
|
||||
|
||||
self.curr = match next_value {
|
||||
Ok(result) => match result {
|
||||
UntaggedValue::Primitive(p) => p,
|
||||
_ => {
|
||||
return Some(Err(ShellError::unimplemented(
|
||||
"Internal error: expected a primitive result from increment",
|
||||
)));
|
||||
}
|
||||
},
|
||||
let mut next = match next_value {
|
||||
Ok(result) => result,
|
||||
Err((left_type, right_type)) => {
|
||||
return Some(Err(ShellError::coerce_error(
|
||||
self.done = true;
|
||||
return Some(
|
||||
UntaggedValue::Error(ShellError::coerce_error(
|
||||
left_type.spanned(self.tag.span),
|
||||
right_type.spanned(self.tag.span),
|
||||
)));
|
||||
))
|
||||
.into_untagged_value(),
|
||||
);
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
std::mem::swap(&mut self.curr, &mut next);
|
||||
|
||||
Some(next.into_value(self.tag.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use nu_protocol::{
|
||||
};
|
||||
|
||||
use crate::utils::arguments::arguments;
|
||||
use futures::stream::once;
|
||||
use nu_value_ext::{as_string, ValueExt};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -18,7 +17,6 @@ pub struct Arguments {
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"empty?"
|
||||
@ -32,11 +30,11 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check for empty values"
|
||||
"Check for empty values."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
is_empty(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
is_empty(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -81,81 +79,75 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_empty(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn is_empty(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let name_tag = Arc::new(args.call_info.name_tag.clone());
|
||||
let context = Arc::new(EvaluationContext::from_args(&args));
|
||||
let (Arguments { mut rest }, input) = args.process().await?;
|
||||
let (Arguments { mut rest }, input) = args.process()?;
|
||||
let (columns, default_block): (Vec<ColumnPath>, Option<Box<CapturedBlock>>) =
|
||||
arguments(&mut rest)?;
|
||||
let default_block = Arc::new(default_block);
|
||||
|
||||
if input.is_empty() {
|
||||
let stream = futures::stream::iter(vec![
|
||||
UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)
|
||||
]);
|
||||
let stream = vec![UntaggedValue::Primitive(Primitive::Nothing).into_value(tag)].into_iter();
|
||||
|
||||
return Ok(InputStream::from_stream(stream)
|
||||
.then(move |input| {
|
||||
.map(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = vec![];
|
||||
|
||||
async {
|
||||
match process_row(context, input, block, columns, tag).await {
|
||||
match process_row(context, input, block, columns, tag) {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
Err(e) => ActionStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream());
|
||||
.to_action_stream());
|
||||
}
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
.map(move |input| {
|
||||
let tag = name_tag.clone();
|
||||
let context = context.clone();
|
||||
let block = default_block.clone();
|
||||
let columns = columns.clone();
|
||||
|
||||
async {
|
||||
match process_row(context, input, block, columns, tag).await {
|
||||
match process_row(context, input, block, columns, tag) {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
Err(e) => ActionStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
fn process_row(
|
||||
context: Arc<EvaluationContext>,
|
||||
input: Value,
|
||||
default_block: Arc<Option<Box<CapturedBlock>>>,
|
||||
column_paths: Vec<ColumnPath>,
|
||||
tag: Arc<Tag>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
) -> Result<ActionStream, ShellError> {
|
||||
let _tag = &*tag;
|
||||
let mut out = Arc::new(None);
|
||||
let results = Arc::make_mut(&mut out);
|
||||
|
||||
if let Some(default_block) = &*default_block {
|
||||
let for_block = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
let input_stream = vec![Ok(for_block)].into_iter().to_input_stream();
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_vars(&default_block.captured.entries);
|
||||
context.scope.add_var("$it", input.clone());
|
||||
|
||||
let stream = run_block(&default_block.block, &*context, input_stream).await;
|
||||
let stream = run_block(&default_block.block, &*context, input_stream);
|
||||
context.scope.exit_scope();
|
||||
|
||||
let mut stream = stream?;
|
||||
*results = Some({
|
||||
let values = stream.drain_vec().await;
|
||||
let values = stream.drain_vec();
|
||||
|
||||
let errors = context.get_errors();
|
||||
|
||||
@ -186,7 +178,7 @@ async fn process_row(
|
||||
ref tag,
|
||||
} => {
|
||||
if column_paths.is_empty() {
|
||||
Ok(OutputStream::one(ReturnSuccess::value({
|
||||
Ok(ActionStream::one(ReturnSuccess::value({
|
||||
let is_empty = input.is_empty();
|
||||
|
||||
if default_block.is_some() {
|
||||
@ -229,10 +221,10 @@ async fn process_row(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(obj)))
|
||||
Ok(ActionStream::one(ReturnSuccess::value(obj)))
|
||||
}
|
||||
}
|
||||
other => Ok(OutputStream::one(ReturnSuccess::value({
|
||||
other => Ok(ActionStream::one(ReturnSuccess::value({
|
||||
if other.is_empty() {
|
||||
results
|
||||
.clone()
|
||||
|
@ -17,7 +17,6 @@ pub struct EnterArgs {
|
||||
encoding: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Enter {
|
||||
fn name(&self) -> &str {
|
||||
"enter"
|
||||
@ -39,18 +38,20 @@ impl WholeStreamCommand for Enter {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
r#"Create a new shell and begin at this path.
|
||||
"Create a new shell and begin at this path."
|
||||
}
|
||||
|
||||
Multiple encodings are supported for reading text files by using
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Multiple encodings are supported for reading text files by using
|
||||
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||
|
||||
For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
enter(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
enter(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -74,38 +75,21 @@ documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
|
||||
}
|
||||
}
|
||||
|
||||
async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn enter(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let scope = raw_args.scope.clone();
|
||||
let shell_manager = raw_args.shell_manager.clone();
|
||||
let head = raw_args.call_info.args.head.clone();
|
||||
let ctrl_c = raw_args.ctrl_c.clone();
|
||||
let configs = raw_args.configs.clone();
|
||||
let current_errors = raw_args.current_errors.clone();
|
||||
let host = raw_args.host.clone();
|
||||
let tag = raw_args.call_info.name_tag.clone();
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process().await?;
|
||||
let (EnterArgs { location, encoding }, _) = raw_args.process()?;
|
||||
let location_string = location.display().to_string();
|
||||
let location_clone = location_string.clone();
|
||||
|
||||
if location_string.starts_with("help") {
|
||||
let spec = location_string.split(':').collect::<Vec<&str>>();
|
||||
|
||||
if spec.len() == 2 {
|
||||
let (_, command) = (spec[0], spec[1]);
|
||||
|
||||
if scope.has_command(command) {
|
||||
return Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(
|
||||
UntaggedValue::string(command).into_value(Tag::unknown()),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterHelpShell(UntaggedValue::nothing().into_value(Tag::unknown())),
|
||||
)))
|
||||
} else if location.is_dir() {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterShell(location_clone),
|
||||
if location.is_dir() {
|
||||
Ok(ActionStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterShell(location_string),
|
||||
)))
|
||||
} else {
|
||||
// If it's a file, attempt to open the file as a value and enter it
|
||||
@ -116,11 +100,10 @@ async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
|
||||
let (file_extension, tagged_contents) = crate::commands::open::fetch(
|
||||
&full_path,
|
||||
&PathBuf::from(location_clone),
|
||||
&PathBuf::from(location_string),
|
||||
span,
|
||||
encoding,
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
|
||||
match tagged_contents.value {
|
||||
UntaggedValue::Primitive(Primitive::String(_)) => {
|
||||
@ -130,6 +113,7 @@ async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let new_args = RawCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
configs,
|
||||
current_errors,
|
||||
shell_manager,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
@ -140,40 +124,38 @@ async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
span: Span::unknown(),
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: tag.clone(),
|
||||
name_tag: tag,
|
||||
},
|
||||
scope: scope.clone(),
|
||||
scope,
|
||||
};
|
||||
let tag = tagged_contents.tag.clone();
|
||||
let mut result = converter
|
||||
.run(new_args.with_input(vec![tagged_contents]))
|
||||
.await?;
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
Ok(futures::stream::iter(result_vec.into_iter().map(
|
||||
move |res| match res {
|
||||
Ok(ReturnSuccess::Value(Value { value, .. })) => Ok(
|
||||
ReturnSuccess::Action(CommandAction::EnterValueShell(Value {
|
||||
let mut result =
|
||||
converter.run(new_args.with_input(vec![tagged_contents]))?;
|
||||
let result_vec: Vec<Value> = result.drain_vec();
|
||||
Ok(result_vec
|
||||
.into_iter()
|
||||
.map(move |res| {
|
||||
let Value { value, .. } = res;
|
||||
Ok(ReturnSuccess::Action(CommandAction::EnterValueShell(
|
||||
Value {
|
||||
value,
|
||||
tag: tag.clone(),
|
||||
})),
|
||||
),
|
||||
x => x,
|
||||
},
|
||||
))
|
||||
.to_output_stream())
|
||||
)))
|
||||
})
|
||||
.to_action_stream())
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
Ok(ActionStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(OutputStream::one(ReturnSuccess::action(
|
||||
Ok(ActionStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Ok(OutputStream::one(ReturnSuccess::action(
|
||||
_ => Ok(ActionStream::one(ReturnSuccess::action(
|
||||
CommandAction::EnterValueShell(tagged_contents),
|
||||
))),
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ pub struct EveryArgs {
|
||||
skip: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Every {
|
||||
fn name(&self) -> &str {
|
||||
"every"
|
||||
@ -36,8 +35,8 @@ impl WholeStreamCommand for Every {
|
||||
"Show (or skip) every n-th row, starting from the first one."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
every(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
every(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -63,15 +62,15 @@ impl WholeStreamCommand for Every {
|
||||
}
|
||||
}
|
||||
|
||||
async fn every(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (EveryArgs { stride, skip }, input) = args.process().await?;
|
||||
fn every(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (EveryArgs { stride, skip }, input) = args.process()?;
|
||||
|
||||
let stride = stride.item;
|
||||
let skip = skip.item;
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.filter_map(move |(i, value)| async move {
|
||||
.filter_map(move |(i, value)| {
|
||||
let stride_desired = if stride < 1 { 1 } else { stride } as usize;
|
||||
let should_include = skip == (i % stride_desired != 0);
|
||||
|
||||
@ -81,7 +80,7 @@ async fn every(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
None
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -13,7 +13,6 @@ pub struct ExecArgs {
|
||||
pub rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exec {
|
||||
fn name(&self) -> &str {
|
||||
"exec"
|
||||
@ -29,11 +28,11 @@ impl WholeStreamCommand for Exec {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Execute command"
|
||||
"Execute command."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
exec(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
exec(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -53,12 +52,12 @@ impl WholeStreamCommand for Exec {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn exec(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn exec(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let (args, _): (ExecArgs, _) = args.process().await?;
|
||||
let (args, _): (ExecArgs, _) = args.process()?;
|
||||
|
||||
let mut command = Command::new(args.command.item);
|
||||
for tagged_arg in args.rest {
|
||||
@ -75,7 +74,7 @@ async fn exec(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn exec(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn exec(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
Err(ShellError::labeled_error(
|
||||
"Error on exec",
|
||||
"exec is not supported on your platform",
|
||||
|
@ -1,25 +1,30 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape};
|
||||
|
||||
pub struct Exit;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Exit {
|
||||
fn name(&self) -> &str {
|
||||
"exit"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("exit").switch("now", "exit out of the shell immediately", Some('n'))
|
||||
Signature::build("exit")
|
||||
.optional(
|
||||
"code",
|
||||
SyntaxShape::Number,
|
||||
"Status code to return if this was the last shell or --now was specified",
|
||||
)
|
||||
.switch("now", "Exit out of the shell immediately", Some('n'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Exit the current shell (or all shells)"
|
||||
"Exit the current shell (or all shells)."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
exit(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
exit(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -38,16 +43,22 @@ impl WholeStreamCommand for Exit {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exit(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
pub fn exit(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let args = args.evaluate_once()?;
|
||||
|
||||
let command_action = if args.call_info.args.has("now") {
|
||||
CommandAction::Exit
|
||||
let code = if let Some(value) = args.call_info.args.nth(0) {
|
||||
value.as_i32()?
|
||||
} else {
|
||||
CommandAction::LeaveShell
|
||||
0
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::action(command_action)))
|
||||
let command_action = if args.call_info.args.has("now") {
|
||||
CommandAction::Exit(code)
|
||||
} else {
|
||||
CommandAction::LeaveShell(code)
|
||||
};
|
||||
|
||||
Ok(ActionStream::one(ReturnSuccess::action(command_action)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -11,7 +11,6 @@ pub struct FirstArgs {
|
||||
rows: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for First {
|
||||
fn name(&self) -> &str {
|
||||
"first"
|
||||
@ -29,8 +28,8 @@ impl WholeStreamCommand for First {
|
||||
"Show only the first number of rows."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
first(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
first(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -52,15 +51,15 @@ impl WholeStreamCommand for First {
|
||||
}
|
||||
}
|
||||
|
||||
async fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (FirstArgs { rows }, input) = args.process().await?;
|
||||
fn first(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (FirstArgs { rows }, input) = args.process()?;
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(input.take(rows_desired).to_output_stream())
|
||||
Ok(input.take(rows_desired).to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -13,7 +13,6 @@ pub struct Arguments {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"flatten"
|
||||
@ -27,8 +26,8 @@ impl WholeStreamCommand for Command {
|
||||
"Flatten the table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
flatten(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
flatten(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -52,14 +51,14 @@ impl WholeStreamCommand for Command {
|
||||
}
|
||||
}
|
||||
|
||||
async fn flatten(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn flatten(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (Arguments { rest: columns }, input) = args.process().await?;
|
||||
let (Arguments { rest: columns }, input) = args.process()?;
|
||||
|
||||
Ok(input
|
||||
.map(move |item| futures::stream::iter(flat_value(&columns, &item, &tag).into_iter()))
|
||||
.map(move |item| flat_value(&columns, &item, &tag).into_iter())
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
enum TableInside<'a> {
|
||||
|
@ -13,7 +13,6 @@ pub struct FormatArgs {
|
||||
pattern: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Format {
|
||||
fn name(&self) -> &str {
|
||||
"format"
|
||||
@ -31,8 +30,8 @@ impl WholeStreamCommand for Format {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
format_command(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
format_command(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -44,20 +43,19 @@ impl WholeStreamCommand for Format {
|
||||
}
|
||||
}
|
||||
|
||||
async fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn format_command(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let ctx = Arc::new(EvaluationContext::from_args(&args));
|
||||
let (FormatArgs { pattern }, input) = args.process().await?;
|
||||
let (FormatArgs { pattern }, input) = args.process()?;
|
||||
|
||||
let format_pattern = format(&pattern);
|
||||
let commands = Arc::new(format_pattern);
|
||||
|
||||
Ok(input
|
||||
.then(move |value| {
|
||||
.map(move |value| {
|
||||
let mut output = String::new();
|
||||
let commands = commands.clone();
|
||||
let ctx = ctx.clone();
|
||||
|
||||
async move {
|
||||
for command in &*commands {
|
||||
match command {
|
||||
FormatCommand::Text(s) => {
|
||||
@ -72,12 +70,11 @@ async fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_var("$it", value.clone());
|
||||
let result = evaluate_baseline_expr(&full_column_path.0, &*ctx).await;
|
||||
let result = evaluate_baseline_expr(&full_column_path.0, &*ctx);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
if let Ok(c) = result {
|
||||
output
|
||||
.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
|
||||
output.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
|
||||
} else {
|
||||
// That column doesn't match, so don't emit anything
|
||||
}
|
||||
@ -86,9 +83,8 @@ async fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
}
|
||||
|
||||
ReturnSuccess::value(UntaggedValue::string(output).into_untagged_value())
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -16,7 +16,6 @@ pub struct Arguments {
|
||||
format: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FileSize {
|
||||
fn name(&self) -> &str {
|
||||
"format filesize"
|
||||
@ -40,8 +39,8 @@ impl WholeStreamCommand for FileSize {
|
||||
"Converts a column of filesizes to some specified format"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
filesize(args).await
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
filesize(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -60,11 +59,11 @@ impl WholeStreamCommand for FileSize {
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
fn process_row(
|
||||
input: Value,
|
||||
format: Tagged<String>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
) -> Result<ActionStream, ShellError> {
|
||||
Ok({
|
||||
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||
match replace_for {
|
||||
@ -76,7 +75,7 @@ async fn process_row(
|
||||
{
|
||||
let byte_format = InlineShape::format_bytes(&fs, Some(&format.item));
|
||||
let byte_value = Value::from(byte_format.1);
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
ActionStream::one(ReturnSuccess::value(
|
||||
input.replace_data_at_column_path(&field, byte_value).expect("Given that the existence check was already done, this shouldn't trigger never"),
|
||||
))
|
||||
} else {
|
||||
@ -87,29 +86,27 @@ async fn process_row(
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
Err(e) => ActionStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn filesize(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { field, format }, input) = raw_args.process().await?;
|
||||
fn filesize(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let (Arguments { field, format }, input) = raw_args.process()?;
|
||||
let field = Arc::new(field);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
.map(move |input| {
|
||||
let format = format.clone();
|
||||
let field = field.clone();
|
||||
|
||||
async {
|
||||
match process_row(input, format, field).await {
|
||||
match process_row(input, format, field) {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
Err(e) => ActionStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
.to_action_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,11 +1,10 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct From;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for From {
|
||||
fn name(&self) -> &str {
|
||||
"from"
|
||||
@ -16,13 +15,13 @@ impl WholeStreamCommand for From {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
|
||||
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(get_help(&From, &args.scope)).into_value(Tag::unknown()),
|
||||
)))
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&From, &args.scope)).into_value(Tag::unknown()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user