mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
353 Commits
Author | SHA1 | Date | |
---|---|---|---|
7bf10b980c | |||
55c243a17b | |||
df526f73be | |||
29a77fd6ae | |||
be9ebd9e18 | |||
01f1208ad1 | |||
9dbb3e80fe | |||
a5c14ba7d4 | |||
4b11b283ac | |||
6fdfc84904 | |||
bff81f24aa | |||
ed515cbc0c | |||
87a6d7166c | |||
55baee9a9a | |||
0886afe650 | |||
872f6166e1 | |||
fe348e236f | |||
e0f083d117 | |||
48171f8e24 | |||
bcdf74562b | |||
3a5ee1aed0 | |||
d8c4b9c4fb | |||
6ae7884786 | |||
41834d16d6 | |||
1ee51f2afa | |||
65ee7aa372 | |||
ac38ee82f4 | |||
5fcc7f2328 | |||
3bcc2aad80 | |||
6165b6ae77 | |||
e335e4fddc | |||
5ab4199d71 | |||
f075e2459d | |||
94a26abf21 | |||
bcbdc33049 | |||
21ef3895b3 | |||
3e99dc01b0 | |||
3e325a1974 | |||
2b92e3e8a7 | |||
cb90b90cbf | |||
9776a252ee | |||
751de20f93 | |||
28388b4e3a | |||
4fdbf30308 | |||
722f191e82 | |||
20f6114617 | |||
3075e2cfbf | |||
e2973d2176 | |||
0ff08bb63a | |||
08c0bf52bc | |||
d0229cb96e | |||
0612e5ccfb | |||
1b4f7b34c8 | |||
86e6fcd309 | |||
dc9cd7d8b9 | |||
c0cc9ce7cd | |||
be2f66397b | |||
07760b4129 | |||
d79a3130b8 | |||
440a589f9e | |||
f5fcf9d635 | |||
9b8b1bad57 | |||
874ecd6c88 | |||
57e2fec497 | |||
0905a2c3a2 | |||
3aa00b78f9 | |||
6769d46dbb | |||
efac712f62 | |||
2bb23c57df | |||
bc699a2cc1 | |||
758c128147 | |||
3795c2a39d | |||
311c0e3f50 | |||
25a8caa9b0 | |||
c80a9585b0 | |||
e73491441a | |||
b93b80ccaa | |||
48128c9db6 | |||
6dafaa197d | |||
1634d8e087 | |||
7a583083b8 | |||
75156ab0c9 | |||
9fd6923821 | |||
91a929b2a9 | |||
0f8e31af06 | |||
bd71c2f34d | |||
001123dbd6 | |||
cfee151d4e | |||
fc59291191 | |||
4fc05cac56 | |||
cc4616f25b | |||
e82fbb7bcf | |||
8cd639f6a2 | |||
a8f555856a | |||
3792562046 | |||
d05c48a1d7 | |||
36cc5eb933 | |||
f9f74a0f7d | |||
77f42931ff | |||
73f62266c6 | |||
df2f3d25b0 | |||
599c43ce04 | |||
5c2199e7f4 | |||
3ad4e0348f | |||
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 | |||
11a9144e84 | |||
039f223b53 | |||
e1cb026184 | |||
2a96152a43 | |||
0795d56c1c | |||
48a90fea70 | |||
b202951c1d | |||
c3d2c61729 | |||
d7b707939f | |||
991ac6eb77 | |||
011b7c4a07 | |||
617341f8d5 | |||
abd2632977 | |||
5481db4079 | |||
041086d22a | |||
aa564f5072 | |||
8367f2001c | |||
1cfb228924 | |||
b403fb1275 | |||
3443ca40c5 | |||
96f95653a6 | |||
7f7e8465da | |||
e3a273cf73 | |||
233161d56e | |||
d883ab250a | |||
ef4e3f907c | |||
debeadbf3f | |||
d66baaceb9 | |||
85329f9a81 | |||
a5fefaf78b | |||
6f17662a4e | |||
c83aea3c89 | |||
67aaf4cb2d | |||
3083346884 | |||
d07789677f | |||
fb1846120d | |||
da1e1295ea | |||
ecaea57263 | |||
fa928bd25d | |||
c1981dfc26 | |||
fd41fa31d5 | |||
2c52144f41 | |||
87c7898b65 | |||
44e088c6fe | |||
7b4cbd7ce9 | |||
b052d524da | |||
47c4b8e88a | |||
d0a2a02eea | |||
b1e1dab4cb | |||
388973e9ab | |||
2129ec7558 | |||
82f122525c | |||
7c4c00f1e6 | |||
fe6c7dc10a | |||
9bc24e3b12 | |||
833baca66e | |||
9fd92512a2 | |||
b692ca7896 | |||
52dc04a35a | |||
42b1287759 | |||
5a471aa1d0 | |||
a4b8d4a098 | |||
a3be6affa4 | |||
71b99edd48 | |||
64553ddcb7 | |||
2a42482ae9 | |||
11f345a8ae | |||
fec50d8cfe | |||
05e42381df | |||
b435075e09 | |||
430da53f0b | |||
2e6d836dd1 | |||
899d324a9c | |||
576ed6a906 | |||
d744cf8437 | |||
088e662285 | |||
f9b0b81eb2 | |||
c5485c6501 | |||
d8ed01400f | |||
ebc4694e05 | |||
a9441d670e | |||
495d2ebd70 | |||
ad26adc3e3 | |||
63a62e19f9 | |||
4f2ae34df9 | |||
a636f161a4 | |||
dfb1e22559 | |||
dff85a7f70 | |||
3be198d2f5 | |||
d19314fe3a | |||
d06f457b2a | |||
7d07881d96 | |||
3e6e3a207c | |||
481c6d4511 | |||
231a445809 | |||
93e8f6c05e | |||
9de2144fc4 | |||
363dc51ba0 | |||
99117ff2ef | |||
5356cb9fbd | |||
0e13d9fbaa | |||
2dcb16870b | |||
ac9909112f | |||
eb3c2c9e76 | |||
3d29e3efbf | |||
f410fb6689 | |||
eb62fd466e | |||
b50cdd6de8 | |||
f38e2b5c6d | |||
455915ec9e | |||
2333158256 | |||
3ffa804088 | |||
98810d22b1 | |||
5e72b2a797 | |||
7e4e7fa4a6 |
@ -4,7 +4,7 @@ tasks:
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable,test-bins
|
||||
init: cargo test --all --features=stable
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
|
@ -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.
|
||||
|
||||
|
3972
Cargo.lock
generated
3972
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
185
Cargo.toml
185
Cargo.toml
@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.25.0"
|
||||
version = "0.32.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
@ -18,82 +18,108 @@ members = ["crates/*/"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-cli = {version = "0.25.0", path = "./crates/nu-cli"}
|
||||
nu-data = {version = "0.25.0", path = "./crates/nu-data"}
|
||||
nu-errors = {version = "0.25.0", path = "./crates/nu-errors"}
|
||||
nu-parser = {version = "0.25.0", path = "./crates/nu-parser"}
|
||||
nu-plugin = {version = "0.25.0", path = "./crates/nu-plugin"}
|
||||
nu-protocol = {version = "0.25.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.25.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.25.0", path = "./crates/nu-value-ext"}
|
||||
nu-cli = { version = "0.32.0", path = "./crates/nu-cli", default-features = false }
|
||||
nu-command = { version = "0.32.0", path = "./crates/nu-command" }
|
||||
nu-data = { version = "0.32.0", path = "./crates/nu-data" }
|
||||
nu-engine = { version = "0.32.0", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.32.0", path = "./crates/nu-errors" }
|
||||
nu-parser = { version = "0.32.0", path = "./crates/nu-parser" }
|
||||
nu-plugin = { version = "0.32.0", path = "./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.32.0", path = "./crates/nu-protocol" }
|
||||
nu-source = { version = "0.32.0", path = "./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.32.0", path = "./crates/nu-value-ext" }
|
||||
|
||||
nu_plugin_binaryview = {version = "0.25.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_chart = {version = "0.25.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_fetch = {version = "0.25.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.25.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
nu_plugin_from_sqlite = {version = "0.25.0", path = "./crates/nu_plugin_from_sqlite", optional = true}
|
||||
nu_plugin_inc = {version = "0.25.0", path = "./crates/nu_plugin_inc", optional = true}
|
||||
nu_plugin_match = {version = "0.25.0", path = "./crates/nu_plugin_match", optional = true}
|
||||
nu_plugin_post = {version = "0.25.0", path = "./crates/nu_plugin_post", optional = true}
|
||||
nu_plugin_ps = {version = "0.25.0", path = "./crates/nu_plugin_ps", optional = true}
|
||||
nu_plugin_s3 = {version = "0.25.0", path = "./crates/nu_plugin_s3", optional = true}
|
||||
nu_plugin_start = {version = "0.25.0", path = "./crates/nu_plugin_start", optional = true}
|
||||
nu_plugin_sys = {version = "0.25.0", path = "./crates/nu_plugin_sys", optional = true}
|
||||
nu_plugin_textview = {version = "0.25.0", path = "./crates/nu_plugin_textview", optional = true}
|
||||
nu_plugin_to_bson = {version = "0.25.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||
nu_plugin_to_sqlite = {version = "0.25.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||
nu_plugin_tree = {version = "0.25.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||
nu_plugin_xpath = {version = "0.25.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||
nu_plugin_selector = {version = "0.25.0", path = "./crates/nu_plugin_selector", optional = true}
|
||||
nu_plugin_binaryview = { version = "0.32.0", path = "./crates/nu_plugin_binaryview", optional = true }
|
||||
nu_plugin_chart = { version = "0.32.0", path = "./crates/nu_plugin_chart", optional = true }
|
||||
nu_plugin_fetch = { version = "0.32.0", path = "./crates/nu_plugin_fetch", optional = true }
|
||||
nu_plugin_from_bson = { version = "0.32.0", path = "./crates/nu_plugin_from_bson", optional = true }
|
||||
nu_plugin_from_sqlite = { version = "0.32.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
|
||||
nu_plugin_inc = { version = "0.32.0", path = "./crates/nu_plugin_inc", optional = true }
|
||||
nu_plugin_match = { version = "0.32.0", path = "./crates/nu_plugin_match", optional = true }
|
||||
nu_plugin_post = { version = "0.32.0", path = "./crates/nu_plugin_post", optional = true }
|
||||
nu_plugin_ps = { version = "0.32.0", path = "./crates/nu_plugin_ps", optional = true }
|
||||
nu_plugin_query_json = { version = "0.32.0", path = "./crates/nu_plugin_query_json", optional = true }
|
||||
nu_plugin_s3 = { version = "0.32.0", path = "./crates/nu_plugin_s3", optional = true }
|
||||
nu_plugin_selector = { version = "0.32.0", path = "./crates/nu_plugin_selector", optional = true }
|
||||
nu_plugin_start = { version = "0.32.0", path = "./crates/nu_plugin_start", optional = true }
|
||||
nu_plugin_sys = { version = "0.32.0", path = "./crates/nu_plugin_sys", optional = true }
|
||||
nu_plugin_textview = { version = "0.32.0", path = "./crates/nu_plugin_textview", optional = true }
|
||||
nu_plugin_to_bson = { version = "0.32.0", path = "./crates/nu_plugin_to_bson", optional = true }
|
||||
nu_plugin_to_sqlite = { version = "0.32.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
|
||||
nu_plugin_tree = { version = "0.32.0", path = "./crates/nu_plugin_tree", optional = true }
|
||||
nu_plugin_xpath = { version = "0.32.0", path = "./crates/nu_plugin_xpath", optional = true }
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
clap = "2.33.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
log = "0.4.11"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
itertools = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { version = "0.32.0", path = "./crates/nu-test-support" }
|
||||
dunce = "1.0.1"
|
||||
nu-test-support = {version = "0.25.0", path = "./crates/nu-test-support"}
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
directories-support = [
|
||||
"nu-cli/directories",
|
||||
"nu-cli/dirs",
|
||||
"nu-command/directories",
|
||||
"nu-command/dirs",
|
||||
"nu-data/directories",
|
||||
"nu-data/dirs",
|
||||
"nu-engine/dirs",
|
||||
]
|
||||
ptree-support = ["nu-cli/ptree", "nu-command/ptree"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-cli/term", "nu-command/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate", "nu-command/uuid_crate"]
|
||||
which-support = ["nu-cli/which", "nu-command/which", "nu-engine/which"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
"textview",
|
||||
"inc",
|
||||
"git-support",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"ptree-support",
|
||||
"term-support",
|
||||
"uuid-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"rich-benchmark",
|
||||
"zip-support"
|
||||
"nu-cli/shadow-rs",
|
||||
"sys",
|
||||
"ps",
|
||||
"directories-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"term-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"post",
|
||||
"fetch",
|
||||
"zip-support",
|
||||
]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath", "selector"]
|
||||
stable = ["default"]
|
||||
|
||||
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"inc",
|
||||
"tree",
|
||||
"ptree-support",
|
||||
"textview",
|
||||
"clipboard-cli",
|
||||
"trash-support",
|
||||
"uuid-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
"s3",
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
"query-json",
|
||||
]
|
||||
|
||||
wasi = ["inc", "match", "ptree-support", "match", "tree", "rustyline-support"]
|
||||
|
||||
trace = ["nu-parser/trace"]
|
||||
|
||||
@ -105,26 +131,46 @@ post = ["nu_plugin_post"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
zip-support = ["nu-cli/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"]
|
||||
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"]
|
||||
trash-support = [
|
||||
"nu-cli/trash-support",
|
||||
"nu-command/trash-support",
|
||||
"nu-engine/trash-support",
|
||||
]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
zip-support = ["nu-cli/zip", "nu-command/zip"]
|
||||
|
||||
#This is disabled in extra for now
|
||||
table-pager = ["nu-command/table-pager"]
|
||||
|
||||
#dataframe feature for nushell
|
||||
dataframe = [
|
||||
"nu-protocol/dataframe",
|
||||
"nu-command/dataframe",
|
||||
"nu-value-ext/dataframe",
|
||||
"nu-data/dataframe",
|
||||
"nu_plugin_post/dataframe",
|
||||
"nu_plugin_to_bson/dataframe",
|
||||
]
|
||||
|
||||
|
||||
[profile.release]
|
||||
#strip = "symbols" #Couldn't get working +nightly
|
||||
opt-level = 'z' #Optimize for size
|
||||
lto = true #Link Time Optimization
|
||||
codegen-units = 1 #Reduce parallel codegen units
|
||||
lto = true #Link Time Optimization
|
||||
# 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
|
||||
@ -176,6 +222,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"
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2020 Yehuda Katz, Jonathan Turner
|
||||
Copyright (c) 2019 - 2021 Yehuda Katz, Jonathan Turner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
83
README.md
83
README.md
@ -1,17 +1,18 @@
|
||||
# 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
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
## Status
|
||||
|
||||
@ -44,19 +45,19 @@ Try it in Gitpod.
|
||||
|
||||
### Local
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.47 or later)** version of the compiler.
|
||||
To build Nu, you will need to use the **latest stable (1.51 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
* pkg-config and libssl (only needed on Linux)
|
||||
* On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
- pkg-config and libssl (only needed on Linux)
|
||||
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
* To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
* On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
- To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
@ -139,9 +140,9 @@ Just as the Unix philosophy, Nu allows commands to output from stdout and read f
|
||||
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
* Commands that produce a stream (eg, `ls`)
|
||||
* Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
* Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
- Commands that produce a stream (eg, `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "Dir"`)
|
||||
- Commands that consume the output of the pipeline (eg, `autoview`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
|
||||
@ -219,7 +220,7 @@ We can pipeline this into a command that gets the contents of one of the columns
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.21.0
|
||||
version │ 0.32.0
|
||||
───────────────┴────────────────────────────────────
|
||||
```
|
||||
|
||||
@ -227,7 +228,7 @@ Finally, we can use commands outside of Nu once we have the data we want:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.21.0
|
||||
0.32.0
|
||||
```
|
||||
|
||||
Here we use the variable `$it` to refer to the value being piped to the external command.
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -270,40 +271,40 @@ If the plugin is a sink, it is given the full vector of final data and is given
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
* First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer first-class consistent support for Windows, macOS, and Linux.
|
||||
|
||||
* Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
|
||||
* Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
||||
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
||||
|
||||
* Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
||||
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
||||
|
||||
* Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
|
||||
## Commands
|
||||
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/documentation.html#quick-command-references).
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
|
||||
|
||||
## Progress
|
||||
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
Nu is in heavy development, and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes
|
||||
| -------- |:-----------:|:---------:|:---:|:-------:|:------:| -----
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables
|
||||
| Functions| X | | | | | No functions, yet, only aliases
|
||||
| Variables| X | | | | | Nu doesn't yet support variables
|
||||
| Completions | | X | | | | Completions are currently barebones, at best
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | X | | | | Initial implementation but lacks necessary features |
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | X | | | | Temporary environment, but no session-wide env variables |
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable |
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish |
|
||||
| Documentation | | X | | | | Book and related are barebones and lack task-based lessons |
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | X | | | | No functions, yet, only aliases |
|
||||
| Variables | | X | | | | Nu doesn't yet support variables |
|
||||
| Completions | | X | | | | Completions are currently barebones, at best |
|
||||
| Type-checking | | X | | | | Commands check basic types, but input/output isn't checked |
|
||||
|
||||
## Current Roadmap
|
||||
|
||||
@ -313,6 +314,12 @@ We've added a `Roadmap Board` to help collaboratively capture the direction we'r
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details.
|
||||
|
||||
Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
The project is made available under the MIT license. See the `LICENSE` file for more information.
|
||||
|
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.32.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,107 +5,106 @@ description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.25.0"
|
||||
version = "0.32.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = {version = "0.25.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.25.0", path = "../nu-errors"}
|
||||
nu-json = {version = "0.25.0", path = "../nu-json"}
|
||||
nu-parser = {version = "0.25.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.25.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.25.0", path = "../nu-protocol"}
|
||||
nu-source = {version = "0.25.0", path = "../nu-source"}
|
||||
nu-stream = {version = "0.25.0", path = "../nu-stream"}
|
||||
nu-table = {version = "0.25.0", path = "../nu-table"}
|
||||
nu-test-support = {version = "0.25.0", path = "../nu-test-support"}
|
||||
nu-value-ext = {version = "0.25.0", path = "../nu-value-ext"}
|
||||
nu-command = { version = "0.32.0", path = "../nu-command" }
|
||||
nu-data = { version = "0.32.0", path = "../nu-data" }
|
||||
nu-engine = { version = "0.32.0", path = "../nu-engine" }
|
||||
nu-errors = { version = "0.32.0", path = "../nu-errors" }
|
||||
nu-json = { version = "0.32.0", path = "../nu-json" }
|
||||
nu-parser = { version = "0.32.0", path = "../nu-parser" }
|
||||
nu-plugin = { version = "0.32.0", path = "../nu-plugin" }
|
||||
nu-protocol = { version = "0.32.0", path = "../nu-protocol" }
|
||||
nu-source = { version = "0.32.0", path = "../nu-source" }
|
||||
nu-stream = { version = "0.32.0", path = "../nu-stream" }
|
||||
nu-table = { version = "0.32.0", path = "../nu-table" }
|
||||
nu-test-support = { version = "0.32.0", path = "../nu-test-support" }
|
||||
nu-value-ext = { version = "0.32.0", path = "../nu-value-ext" }
|
||||
nu-ansi-term = { version = "0.32.0", path = "../nu-ansi-term" }
|
||||
nu-pretty-hex = { version = "0.32.0", path = "../nu-pretty-hex" }
|
||||
|
||||
Inflector = "0.11"
|
||||
ansi_term = "0.12.1"
|
||||
arboard = {version = "1.1.0", optional = true}
|
||||
async-recursion = "0.3.1"
|
||||
async-trait = "0.1.40"
|
||||
arboard = { version = "1.1.0", optional = true }
|
||||
async-recursion = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
base64 = "0.13.0"
|
||||
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||
byte-unit = "4.0.9"
|
||||
bytes = "0.5.6"
|
||||
calamine = "0.16.1"
|
||||
chrono = {version = "0.4.15", features = ["serde"]}
|
||||
bytes = "1.0.1"
|
||||
calamine = "0.17.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
codespan-reporting = "0.9.5"
|
||||
csv = "1.1.3"
|
||||
ctrlc = {version = "3.1.6", optional = true}
|
||||
codespan-reporting = "0.11.0"
|
||||
csv = "1.1.5"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
derive-new = "0.5.8"
|
||||
directories = {version = "3.0.1", optional = true}
|
||||
dirs = {version = "3.0.1", optional = true}
|
||||
directories-next = { version = "2.0.0", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
dtparse = "1.2.0"
|
||||
dunce = "1.0.1"
|
||||
eml-parser = "0.1.0"
|
||||
encoding_rs = "0.8.24"
|
||||
encoding_rs = "0.8.28"
|
||||
filesize = "0.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = {version = "0.3.5", features = ["compat", "io-compat"]}
|
||||
futures-util = "0.3.5"
|
||||
futures = { version = "0.3.12", features = ["compat", "io-compat"] }
|
||||
futures-util = "0.3.12"
|
||||
futures_codec = "0.4.1"
|
||||
getset = "0.1.1"
|
||||
git2 = {version = "0.13.11", default_features = false, optional = true}
|
||||
glob = "0.3.0"
|
||||
heim = {version = "0.1.0-rc.1", optional = true}
|
||||
htmlescape = "0.3.1"
|
||||
ical = "0.6.0"
|
||||
ichwh = {version = "0.3.4", optional = true}
|
||||
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||
itertools = "0.9.0"
|
||||
ical = "0.7.0"
|
||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.*"
|
||||
log = "0.4.11"
|
||||
log = "0.4.14"
|
||||
meval = "0.2.0"
|
||||
num-bigint = {version = "0.3.0", features = ["serde"]}
|
||||
num-format = {version = "0.4.0", features = ["with-num-bigint"]}
|
||||
num-traits = "0.2.12"
|
||||
parking_lot = "0.11.0"
|
||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
|
||||
num-traits = "0.2.14"
|
||||
parking_lot = "0.11.1"
|
||||
pin-utils = "0.1.0"
|
||||
pretty-hex = "0.2.0"
|
||||
ptree = {version = "0.3.0", optional = true}
|
||||
ptree = { version = "0.3.1", optional = true }
|
||||
query_interface = "0.3.5"
|
||||
quick-xml = "0.18.1"
|
||||
rand = "0.7.3"
|
||||
rayon = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
roxmltree = "0.13.0"
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = {version = "6.3.0", optional = true}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
quick-xml = "0.21.0"
|
||||
rand = "0.8.3"
|
||||
rayon = "1.5.0"
|
||||
regex = "1.4.3"
|
||||
roxmltree = "0.14.0"
|
||||
rust-embed = "5.9.0"
|
||||
rustyline = { version = "8.1.0", optional = true }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.57"
|
||||
serde_json = "1.0.61"
|
||||
serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.13"
|
||||
sha2 = "0.9.1"
|
||||
shellexpand = "2.0.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.9.3"
|
||||
shellexpand = "2.1.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
tempfile = "3.1.0"
|
||||
term = {version = "0.6.1", optional = true}
|
||||
tempfile = "3.2.0"
|
||||
term = { version = "0.7.0", optional = true }
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
titlecase = "1.0"
|
||||
toml = "0.5.6"
|
||||
trash = {version = "1.2.0", optional = true}
|
||||
unicode-segmentation = "1.6.0"
|
||||
uom = {version = "0.30.0", features = ["f64", "try-from"]}
|
||||
termcolor = "1.1.2"
|
||||
titlecase = "1.1.0"
|
||||
toml = "0.5.8"
|
||||
trash = { version = "1.3.0", optional = true }
|
||||
unicode-segmentation = "1.7.1"
|
||||
url = "2.1.1"
|
||||
uuid_crate = {package = "uuid", version = "0.8.1", features = ["v4"], optional = true}
|
||||
which = {version = "4.0.2", optional = true}
|
||||
zip = {version = "0.5.7", optional = true}
|
||||
uuid_crate = { package = "uuid", version = "0.8.2", features = ["v4"], optional = true }
|
||||
which = { version = "4.0.2", optional = true }
|
||||
zip = { version = "0.5.9", optional = true }
|
||||
shadow-rs = { version = "0.5", default-features = false, optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
users = "0.10.0"
|
||||
users = "0.11.0"
|
||||
|
||||
# TODO this will be possible with new dependency resolver
|
||||
# (currently on nightly behind -Zfeatures=itarget):
|
||||
@ -116,18 +115,20 @@ users = "0.10.0"
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
optional = true
|
||||
version = "0.24.2"
|
||||
version = "0.25.3"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
|
||||
[features]
|
||||
default = ["shadow-rs"]
|
||||
clipboard-cli = ["arboard"]
|
||||
rich-benchmark = ["heim"]
|
||||
rustyline-support = ["rustyline"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
trash-support = ["trash"]
|
||||
dirs = ["dirs-next"]
|
||||
directories = ["directories-next"]
|
||||
|
@ -1,31 +1,99 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::default_context::create_default_context;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::prelude::*;
|
||||
use crate::script::{print_err, run_script_standalone};
|
||||
use crate::line_editor::configure_ctrl_c;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use crate::script::{process_script, LineResult};
|
||||
pub(crate) use nu_engine::script::{process_script, LineResult};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use crate::line_editor::{
|
||||
configure_rustyline_editor, convert_rustyline_result_to_string,
|
||||
default_rustyline_editor_configuration, nu_line_editor_helper,
|
||||
};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
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;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{self, error::ReadlineError};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
At, Cmd, Editor, KeyPress, Movement, Word,
|
||||
};
|
||||
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;
|
||||
|
||||
@ -39,16 +107,14 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
}
|
||||
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(plugin_dirs) = config.get("plugin_dirs") {
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} = plugin_dirs
|
||||
{
|
||||
for pipeline in pipelines {
|
||||
if let Ok(plugin_dir) = pipeline.as_string() {
|
||||
search_paths.push(PathBuf::from(plugin_dir));
|
||||
}
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
}) = config.get("plugin_dirs")
|
||||
{
|
||||
for pipeline in pipelines {
|
||||
if let Ok(plugin_dir) = pipeline.as_string() {
|
||||
search_paths.push(PathBuf::from(plugin_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,87 +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();
|
||||
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);
|
||||
}
|
||||
|
||||
context.configure(&config, |_, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
let _ = register_plugins(&context);
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
let script = options
|
||||
.scripts
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
||||
|
||||
let _ = register_plugins(ctx);
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
|
||||
run_script_standalone(file_contents, redirect_stdin, &context, true).await?;
|
||||
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
// 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();
|
||||
|
||||
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(""));
|
||||
}
|
||||
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
|
||||
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||
let history_path = if let Some(cfg) = &context.configs.lock().global_config {
|
||||
let _ = configure_rustyline_editor(&mut rl, cfg);
|
||||
let helper = Some(nu_line_editor_helper(&context, cfg));
|
||||
rl.set_helper(helper);
|
||||
});
|
||||
nu_data::config::path::history_path_or_default(cfg)
|
||||
} else {
|
||||
nu_data::config::path::default_history_path()
|
||||
};
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
// Don't load history if it's not necessary
|
||||
if options.save_history {
|
||||
let _ = rl.load_history(&history_path);
|
||||
}
|
||||
|
||||
//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)
|
||||
};
|
||||
|
||||
//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 = crate::commands::history::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)",
|
||||
@ -147,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;
|
||||
@ -161,35 +225,38 @@ 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 (prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
||||
|
||||
if err.is_some() {
|
||||
use crate::git::current_branch;
|
||||
context.scope.exit_scope();
|
||||
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix().to_string(),
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
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(),
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
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();
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
maybe_print_errors(&context, Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
@ -199,14 +266,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()
|
||||
@ -214,14 +281,14 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
use crate::git::current_branch;
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix().to_string(),
|
||||
cwd,
|
||||
match current_branch() {
|
||||
Some(s) => format!("({})", s),
|
||||
None => "".to_string(),
|
||||
}
|
||||
nu_ansi_term::ansi::RESET,
|
||||
Color::Cyan.bold().prefix().to_string(),
|
||||
current_branch(),
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
}
|
||||
};
|
||||
@ -234,7 +301,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
};
|
||||
|
||||
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
|
||||
if let Some(helper) = rl.helper_mut() {
|
||||
helper.colored_prompt = colored_prompt;
|
||||
}
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
@ -248,65 +317,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(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
LineResult::Success(_) => process_script(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
),
|
||||
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) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
context.maybe_print_errors(Text::from(session_text.clone()));
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
rl.clear_history();
|
||||
let _ = rl.save_history(&history_path);
|
||||
if options.save_history {
|
||||
rl.clear_history();
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
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()));
|
||||
|
||||
context.maybe_print_errors(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 {
|
||||
@ -314,7 +387,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)"));
|
||||
@ -338,13 +413,50 @@ 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> {
|
||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||
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
|
||||
.into_iter()
|
||||
@ -356,232 +468,7 @@ pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellErro
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_script_standalone(pipeline_string, false, context, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"expected a table of pipeline strings as startup commands",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn nu_line_editor_helper(
|
||||
context: &mut EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub 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
|
||||
@ -598,24 +485,44 @@ 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,
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
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)]
|
||||
fn current_branch() -> String {
|
||||
#[cfg(feature = "shadow-rs")]
|
||||
{
|
||||
Some(shadow_rs::branch())
|
||||
.map(|x| x.trim().to_string())
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(|x| format!("({})", x))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
#[cfg(not(feature = "shadow-rs"))]
|
||||
{
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
let (tokens, err) = nu_parser::lex(&data, 0);
|
||||
let (lite_block, err2) = nu_parser::group(tokens);
|
||||
let (lite_block, err2) = nu_parser::parse_block(tokens);
|
||||
if err.is_none() && err2.is_none() {
|
||||
let context = crate::evaluation_context::EvaluationContext::basic().unwrap();
|
||||
let context = EvaluationContext::basic();
|
||||
let _ = nu_parser::classify_block(&lite_block, &context.scope);
|
||||
}
|
||||
true
|
||||
|
@ -1,240 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
escape: Option<Tagged<String>>,
|
||||
osc: Option<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi")
|
||||
.optional(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
.named(
|
||||
"escape", // \x1b
|
||||
SyntaxShape::Any,
|
||||
"escape sequence without the escape character(s)",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"osc",
|
||||
SyntaxShape::Any,
|
||||
"operating system command (ocs) escape sequence without the escape character(s)",
|
||||
Some('o'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
r#"Output ANSI codes to change color
|
||||
|
||||
For escape sequences:
|
||||
Escape: '\x1b[' is not required for --escape parameter
|
||||
Format: #(;#)m
|
||||
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
|
||||
There can be multiple text formatting sequence numbers
|
||||
separated by a ; and ending with an m where the # is of the
|
||||
following values:
|
||||
attributes
|
||||
0 reset / normal display
|
||||
1 bold or increased intensity
|
||||
2 faint or decreased intensity
|
||||
3 italic on (non-mono font)
|
||||
4 underline on
|
||||
5 slow blink on
|
||||
6 fast blink on
|
||||
7 reverse video on
|
||||
8 nondisplayed (invisible) on
|
||||
9 strike-through on
|
||||
|
||||
foreground/bright colors background/bright colors
|
||||
30/90 black 40/100 black
|
||||
31/91 red 41/101 red
|
||||
32/92 green 42/102 green
|
||||
33/93 yellow 43/103 yellow
|
||||
34/94 blue 44/104 blue
|
||||
35/95 magenta 45/105 magenta
|
||||
36/96 cyan 46/106 cyan
|
||||
37/97 white 47/107 white
|
||||
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
OSC: '\x1b]' is not required for --osc parameter
|
||||
Example: echo [$(ansi -o '0') 'some title' $(char bel)] | str collect
|
||||
Format: #
|
||||
0 Set window title and icon name
|
||||
1 Set icon name
|
||||
2 Set window title
|
||||
4 Set/read color palette
|
||||
9 iTerm2 Grown notifications
|
||||
10 Set foreground color (x11 color spec)
|
||||
11 Set background color (x11 color spec)
|
||||
... others"#
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi rb) Hello " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)",
|
||||
example: r#"echo [$(ansi -e '3;93;41m') Hello $(ansi reset) " " $(ansi gb) Nu " " $(ansi pb) World] | str collect"#,
|
||||
result: Some(vec![Value::from(
|
||||
"\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld",
|
||||
)]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color, escape, osc }, _) = args.process().await?;
|
||||
|
||||
if let Some(e) = escape {
|
||||
let esc_vec: Vec<char> = e.item.chars().collect();
|
||||
if esc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
e.tag(),
|
||||
));
|
||||
}
|
||||
let output = format!("\x1b[{}", e.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(e.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(o) = osc {
|
||||
let osc_vec: Vec<char> = o.item.chars().collect();
|
||||
if osc_vec[0] == '\\' {
|
||||
return Err(ShellError::labeled_error(
|
||||
"no need for escape characters",
|
||||
"no need for escape characters",
|
||||
o.tag(),
|
||||
));
|
||||
}
|
||||
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc
|
||||
// OCS's need to end with a bell '\x07' char
|
||||
let output = format!("\x1b]{};", o.item);
|
||||
return Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(o.tag()),
|
||||
)));
|
||||
}
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(color.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown color",
|
||||
"unknown color",
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(Color::Green.prefix().to_string()),
|
||||
"gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()),
|
||||
"gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()),
|
||||
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
|
||||
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
|
||||
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
|
||||
"r" | "red" => Some(Color::Red.prefix().to_string()),
|
||||
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
|
||||
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
|
||||
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
|
||||
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
|
||||
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
|
||||
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
|
||||
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
|
||||
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
|
||||
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
|
||||
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
|
||||
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
|
||||
"b" | "black" => Some(Color::Black.prefix().to_string()),
|
||||
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
|
||||
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
|
||||
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
|
||||
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
|
||||
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
|
||||
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
|
||||
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
|
||||
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
|
||||
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
|
||||
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
|
||||
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
|
||||
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
|
||||
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
|
||||
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
|
||||
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
|
||||
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
|
||||
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
|
||||
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
|
||||
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
|
||||
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
|
||||
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
|
||||
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
|
||||
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
|
||||
"w" | "white" => Some(Color::White.prefix().to_string()),
|
||||
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
|
||||
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
|
||||
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
|
||||
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
|
||||
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Ansi {})?)
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
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::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: &PathBuf, 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 {
|
||||
// "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."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("autoenv")
|
||||
}
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Example .nu-env file",
|
||||
example: r#"cat .nu-env
|
||||
[env]
|
||||
mykey = "myvalue"
|
||||
|
||||
[scriptvars]
|
||||
myscript = "echo myval"
|
||||
|
||||
[scripts]
|
||||
entryscripts = ["touch hello.txt", "touch hello2.txt"]
|
||||
exitscripts = ["touch bye.txt"]"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Char;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CharArgs {
|
||||
name: Tagged<String>,
|
||||
unicode: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Char {
|
||||
fn name(&self) -> &str {
|
||||
"char"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi")
|
||||
.required(
|
||||
"character",
|
||||
SyntaxShape::Any,
|
||||
"the name of the character to output",
|
||||
)
|
||||
.switch("unicode", "unicode string i.e. 1f378", Some('u'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output special characters (eg. 'newline')"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Output newline",
|
||||
example: r#"char newline"#,
|
||||
result: Some(vec![Value::from("\n")]),
|
||||
},
|
||||
Example {
|
||||
description: "Output prompt character, newline and a hamburger character",
|
||||
example: r#"echo $(char prompt) $(char newline) $(char hamburger)"#,
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("\u{25b6}").into(),
|
||||
UntaggedValue::string("\n").into(),
|
||||
UntaggedValue::string("\u{2261}").into(),
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Output unicode character",
|
||||
example: r#"char -u 1f378"#,
|
||||
result: Some(vec![Value::from("\u{1f378}")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CharArgs { name, unicode }, _) = args.process().await?;
|
||||
|
||||
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()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error decoding unicode character",
|
||||
"error decoding unicode character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let special_character = str_to_character(&name.item);
|
||||
if let Some(output) = special_character {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(name.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"error finding named character",
|
||||
"error finding named character",
|
||||
name.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str) -> Option<char> {
|
||||
u32::from_str_radix(s, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32)
|
||||
}
|
||||
|
||||
fn str_to_character(s: &str) -> Option<String> {
|
||||
match s {
|
||||
"newline" | "enter" | "nl" => Some("\n".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
"sp" | "space" => Some(" ".into()),
|
||||
// Unicode names came from https://www.compart.com/en/unicode
|
||||
// Private Use Area (U+E000-U+F8FF)
|
||||
// Unicode can't be mixed with Ansi or it will break width calculation
|
||||
"branch" => Some('\u{e0a0}'.to_string()), //
|
||||
"segment" => Some('\u{e0b0}'.to_string()), //
|
||||
|
||||
"identical_to" | "hamburger" => Some('\u{2261}'.to_string()), // ≡
|
||||
"not_identical_to" | "branch_untracked" => Some('\u{2262}'.to_string()), // ≢
|
||||
"strictly_equivalent_to" | "branch_identical" => Some('\u{2263}'.to_string()), // ≣
|
||||
|
||||
"upwards_arrow" | "branch_ahead" => Some('\u{2191}'.to_string()), // ↑
|
||||
"downwards_arrow" | "branch_behind" => Some('\u{2193}'.to_string()), // ↓
|
||||
"up_down_arrow" | "branch_ahead_behind" => Some('\u{2195}'.to_string()), // ↕
|
||||
|
||||
"black_right_pointing_triangle" | "prompt" => Some('\u{25b6}'.to_string()), // ▶
|
||||
"vector_or_cross_product" | "failed" => Some('\u{2a2f}'.to_string()), // ⨯
|
||||
"high_voltage_sign" | "elevated" => Some('\u{26a1}'.to_string()), // ⚡
|
||||
"tilde" | "twiddle" | "squiggly" | "home" => Some("~".into()), // ~
|
||||
"hash" | "hashtag" | "pound_sign" | "sharp" | "root" => Some("#".into()), // #
|
||||
|
||||
// Weather symbols
|
||||
"sun" | "sunny" | "sunrise" => Some("☀️".to_string()),
|
||||
"moon" => Some("🌛".to_string()),
|
||||
"cloudy" | "cloud" | "clouds" => Some("☁️".to_string()),
|
||||
"rainy" | "rain" => Some("🌦️".to_string()),
|
||||
"foggy" | "fog" => Some("🌫️".to_string()),
|
||||
"mist" | "haze" => Some("\u{2591}".to_string()),
|
||||
"snowy" | "snow" => Some("❄️".to_string()),
|
||||
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
|
||||
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
|
||||
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title
|
||||
"bel" => Some('\x07'.to_string()), // Terminal Bell
|
||||
"backspace" => Some('\x08'.to_string()), // Backspace
|
||||
|
||||
// Ansi Erase Sequences
|
||||
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
|
||||
"clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen
|
||||
"clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen
|
||||
"cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen
|
||||
"erase_line" => Some("\x1b[K".to_string()), // clears the current line
|
||||
"erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line
|
||||
"erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line
|
||||
"erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Char;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Char {})?)
|
||||
}
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
use crate::commands::classified::expr::run_expression_block;
|
||||
use crate::commands::classified::internal::run_internal_command;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::prelude::*;
|
||||
use async_recursion::async_recursion;
|
||||
use futures::stream::TryStreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{
|
||||
Block, Call, ClassifiedCommand, Expression, Pipeline, SpannedExpression, Synthetic,
|
||||
};
|
||||
use nu_protocol::{ReturnSuccess, UntaggedValue, Value};
|
||||
use nu_stream::InputStream;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[async_recursion]
|
||||
pub async fn run_block(
|
||||
block: &Block,
|
||||
ctx: &EvaluationContext,
|
||||
mut input: InputStream,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut output: Result<InputStream, ShellError> = Ok(InputStream::empty());
|
||||
for (_, definition) in block.definitions.iter() {
|
||||
ctx.scope.add_definition(definition.clone());
|
||||
}
|
||||
|
||||
for group in &block.block {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
// Run autoview on the values we've seen so far
|
||||
// We may want to make this configurable for other kinds of hosting
|
||||
if let Some(autoview) = ctx.get_command("autoview") {
|
||||
let mut output_stream = match ctx
|
||||
.run_command(
|
||||
autoview,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".into())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
inp,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
return Ok(InputStream::empty());
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = Ok(InputStream::empty());
|
||||
for pipeline in &group.pipelines {
|
||||
match output {
|
||||
Ok(inp) if inp.is_empty() => {}
|
||||
Ok(inp) => {
|
||||
let mut output_stream = inp.to_output_stream();
|
||||
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(Some(_item)) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
// This early return doesn't return the result
|
||||
// we have so far, but breaking out of this loop
|
||||
// causes lifetime issues. A future contribution
|
||||
// could attempt to return the current output.
|
||||
// https://github.com/nushell/nushell/pull/2830#discussion_r550319687
|
||||
return Ok(InputStream::empty());
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(err) = ctx.get_errors().get(0) {
|
||||
ctx.clear_errors();
|
||||
return Err(err.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
output = run_pipeline(pipeline, ctx, input).await;
|
||||
|
||||
input = InputStream::empty();
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn run_pipeline(
|
||||
commands: &Pipeline,
|
||||
ctx: &EvaluationContext,
|
||||
mut input: InputStream,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
for item in commands.list.clone() {
|
||||
input = match item {
|
||||
ClassifiedCommand::Dynamic(call) => {
|
||||
let mut args = vec![];
|
||||
if let Some(positional) = call.positional {
|
||||
for pos in &positional {
|
||||
let result = run_expression_block(pos, ctx).await?.into_vec().await;
|
||||
args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
match &call.head.expr {
|
||||
Expression::Block(block) => {
|
||||
ctx.scope.enter_scope();
|
||||
for (param, value) in block.params.positional.iter().zip(args.iter()) {
|
||||
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||
}
|
||||
let result = run_block(&block, ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let result = result?;
|
||||
return Ok(result);
|
||||
}
|
||||
Expression::Variable(v, span) => {
|
||||
if let Some(value) = ctx.scope.get_var(v) {
|
||||
match &value.value {
|
||||
UntaggedValue::Block(captured_block) => {
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_vars(&captured_block.captured.entries);
|
||||
for (param, value) in captured_block
|
||||
.block
|
||||
.params
|
||||
.positional
|
||||
.iter()
|
||||
.zip(args.iter())
|
||||
{
|
||||
ctx.scope.add_var(param.0.name(), value[0].clone());
|
||||
}
|
||||
let result = run_block(&captured_block.block, ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let result = result?;
|
||||
return Ok(result);
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Variable not found",
|
||||
"variable not found",
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error("Dynamic commands must start with a block (or variable pointing to a block)", "needs to be a block", call.head.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassifiedCommand::Expr(expr) => run_expression_block(&*expr, ctx).await?,
|
||||
|
||||
ClassifiedCommand::Error(err) => return Err(err.into()),
|
||||
|
||||
ClassifiedCommand::Internal(left) => run_internal_command(left, ctx, input).await?,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
|
||||
use log::{log_enabled, trace};
|
||||
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::SpannedExpression;
|
||||
|
||||
pub(crate) async fn run_expression_block(
|
||||
expr: &SpannedExpression,
|
||||
ctx: &EvaluationContext,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::expr", "->");
|
||||
trace!(target: "nu::run::expr", "{:?}", expr);
|
||||
}
|
||||
|
||||
let output = evaluate_baseline_expr(expr, ctx).await?;
|
||||
|
||||
Ok(once(async { Ok(output) }).to_input_stream())
|
||||
}
|
@ -1,250 +0,0 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::prelude::*;
|
||||
use log::{log_enabled, trace};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ExternalRedirection, InternalCommand};
|
||||
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
pub(crate) async fn run_internal_command(
|
||||
command: InternalCommand,
|
||||
context: &EvaluationContext,
|
||||
input: InputStream,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
trace!(target: "nu::run::internal", "->");
|
||||
trace!(target: "nu::run::internal", "{}", command.name);
|
||||
}
|
||||
|
||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||
|
||||
let internal_command = context.scope.expect_command(&command.name);
|
||||
|
||||
if command.name == "autoenv untrust" {
|
||||
context
|
||||
.user_recently_used_autoenv_untrust
|
||||
.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let result = {
|
||||
context
|
||||
.run_command(
|
||||
internal_command?,
|
||||
Tag::unknown_anchor(command.name_span),
|
||||
command.args.clone(),
|
||||
objects,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let head = Arc::new(command.args.head.clone());
|
||||
let context = context.clone();
|
||||
let command = Arc::new(command);
|
||||
|
||||
Ok(InputStream::from_stream(
|
||||
result
|
||||
.then(move |item| {
|
||||
let head = head.clone();
|
||||
let command = command.clone();
|
||||
let context = context.clone();
|
||||
async move {
|
||||
match item {
|
||||
Ok(ReturnSuccess::Action(action)) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.shell_manager.set_path(path);
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
|
||||
CommandAction::Error(err) => {
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::AutoConvert(tagged_contents, extension) => {
|
||||
let contents_tag = tagged_contents.tag.clone();
|
||||
let command_name = format!("from {}", extension);
|
||||
let command = command.clone();
|
||||
if let Some(converter) = context.scope.get_command(&command_name) {
|
||||
let new_args = RawCommandArgs {
|
||||
host: context.host.clone(),
|
||||
ctrl_c: context.ctrl_c.clone(),
|
||||
current_errors: context.current_errors.clone(),
|
||||
shell_manager: context.shell_manager.clone(),
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head: (&*head).clone(),
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: Tag::unknown_anchor(command.name_span),
|
||||
},
|
||||
scope: context.scope.clone(),
|
||||
};
|
||||
let result = converter
|
||||
.run(new_args.with_input(vec![tagged_contents]))
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(mut result) => {
|
||||
let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
|
||||
result.drain_vec().await;
|
||||
|
||||
let mut output = vec![];
|
||||
for res in result_vec {
|
||||
match res {
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
})) => {
|
||||
for l in list {
|
||||
output.push(Ok(l));
|
||||
}
|
||||
}
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value,
|
||||
..
|
||||
})) => {
|
||||
output
|
||||
.push(Ok(value
|
||||
.into_value(contents_tag.clone())));
|
||||
}
|
||||
Err(e) => output.push(Err(e)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
futures::stream::iter(output).to_input_stream()
|
||||
}
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
InputStream::one(tagged_contents)
|
||||
}
|
||||
}
|
||||
CommandAction::EnterHelpShell(value) => match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(cmd)),
|
||||
tag,
|
||||
} => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::for_command(
|
||||
UntaggedValue::string(cmd).into_value(tag),
|
||||
&context.scope,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
_ => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match HelpShell::index(&context.scope) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
},
|
||||
CommandAction::EnterValueShell(value) => {
|
||||
context
|
||||
.shell_manager
|
||||
.insert_at_current(Box::new(ValueShell::new(value)));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::EnterShell(location) => {
|
||||
context.shell_manager.insert_at_current(Box::new(
|
||||
match FilesystemShell::with_location(location) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
context.error(err.into());
|
||||
return InputStream::empty();
|
||||
}
|
||||
},
|
||||
));
|
||||
InputStream::from_stream(futures::stream::iter(vec![]))
|
||||
}
|
||||
CommandAction::AddPlugins(path) => {
|
||||
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
|
||||
Ok(plugins) => {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| {
|
||||
!context.is_command_registered(p.name())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
InputStream::empty()
|
||||
}
|
||||
Err(reason) => {
|
||||
context.error(reason);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
CommandAction::PreviousShell => {
|
||||
context.shell_manager.prev();
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::NextShell => {
|
||||
context.shell_manager.next();
|
||||
InputStream::empty()
|
||||
}
|
||||
CommandAction::LeaveShell => {
|
||||
context.shell_manager.remove_at_current();
|
||||
if context.shell_manager.is_empty() {
|
||||
std::process::exit(0); // TODO: save history.txt
|
||||
}
|
||||
InputStream::empty()
|
||||
}
|
||||
},
|
||||
|
||||
Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(err),
|
||||
..
|
||||
})) => {
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
|
||||
Ok(ReturnSuccess::Value(v)) => InputStream::one(v),
|
||||
|
||||
Ok(ReturnSuccess::DebugValue(v)) => {
|
||||
let doc = PrettyDebug::pretty_doc(&v);
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
|
||||
let _ = doc.render_raw(
|
||||
context.with_host(|host| host.width() - 5),
|
||||
&mut nu_source::TermColored::new(&mut buffer),
|
||||
);
|
||||
|
||||
let value = String::from_utf8_lossy(buffer.as_slice());
|
||||
|
||||
InputStream::one(UntaggedValue::string(value).into_untagged_value())
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
context.error(err);
|
||||
InputStream::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.take_while(|x| futures::future::ready(!x.is_error())),
|
||||
))
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
pub(crate) mod block;
|
||||
mod dynamic;
|
||||
pub(crate) mod expr;
|
||||
pub(crate) mod external;
|
||||
pub(crate) mod internal;
|
||||
pub(crate) mod maybe_text_codec;
|
||||
pub(crate) mod plugin;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use dynamic::Command as DynamicCommand;
|
@ -1,395 +0,0 @@
|
||||
use crate::deserializer::ConfigDeserializer;
|
||||
use crate::evaluate::evaluate_args::evaluate_args;
|
||||
use crate::prelude::*;
|
||||
use crate::{commands::help::get_help, run_block};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{self, Block};
|
||||
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UnevaluatedCallInfo {
|
||||
pub args: hir::Call,
|
||||
pub name_tag: Tag,
|
||||
}
|
||||
|
||||
impl UnevaluatedCallInfo {
|
||||
pub async fn evaluate(self, ctx: &EvaluationContext) -> Result<CallInfo, ShellError> {
|
||||
let args = evaluate_args(&self.args, ctx).await?;
|
||||
|
||||
Ok(CallInfo {
|
||||
args,
|
||||
name_tag: self.name_tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn switch_present(&self, switch: &str) -> bool {
|
||||
self.args.switch_preset(switch)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct CommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
pub scope: Scope,
|
||||
pub input: InputStream,
|
||||
}
|
||||
|
||||
#[derive(Getters, Clone)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct RawCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub scope: Scope,
|
||||
pub call_info: UnevaluatedCallInfo,
|
||||
}
|
||||
|
||||
impl RawCommandArgs {
|
||||
pub fn with_input(self, input: impl Into<InputStream>) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host,
|
||||
ctrl_c: self.ctrl_c,
|
||||
current_errors: self.current_errors,
|
||||
shell_manager: self.shell_manager,
|
||||
call_info: self.call_info,
|
||||
scope: self.scope,
|
||||
input: input.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CommandArgs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.call_info.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub async fn evaluate_once(self) -> Result<EvaluatedWholeStreamCommandArgs, ShellError> {
|
||||
let ctx = EvaluationContext::from_args(&self);
|
||||
let host = self.host.clone();
|
||||
let ctrl_c = self.ctrl_c.clone();
|
||||
let shell_manager = self.shell_manager.clone();
|
||||
let input = self.input;
|
||||
let call_info = self.call_info.evaluate(&ctx).await?;
|
||||
let scope = self.scope.clone();
|
||||
|
||||
Ok(EvaluatedWholeStreamCommandArgs::new(
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
input,
|
||||
scope,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn process<'de, T: Deserialize<'de>>(self) -> Result<(T, InputStream), ShellError> {
|
||||
let args = self.evaluate_once().await?;
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let mut deserializer = ConfigDeserializer::from_call_info(call_info);
|
||||
|
||||
Ok((T::deserialize(&mut deserializer)?, args.input))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunnableContext {
|
||||
pub input: InputStream,
|
||||
pub shell_manager: ShellManager,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub scope: Scope,
|
||||
pub name: Tag,
|
||||
}
|
||||
|
||||
impl RunnableContext {
|
||||
pub fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.scope.get_command(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EvaluatedWholeStreamCommandArgs {
|
||||
pub args: EvaluatedCommandArgs,
|
||||
pub input: InputStream,
|
||||
}
|
||||
|
||||
impl Deref for EvaluatedWholeStreamCommandArgs {
|
||||
type Target = EvaluatedCommandArgs;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatedWholeStreamCommandArgs {
|
||||
pub fn new(
|
||||
host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
shell_manager: ShellManager,
|
||||
call_info: CallInfo,
|
||||
input: impl Into<InputStream>,
|
||||
scope: Scope,
|
||||
) -> EvaluatedWholeStreamCommandArgs {
|
||||
EvaluatedWholeStreamCommandArgs {
|
||||
args: EvaluatedCommandArgs {
|
||||
host,
|
||||
ctrl_c,
|
||||
shell_manager,
|
||||
call_info,
|
||||
scope,
|
||||
},
|
||||
input: input.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_tag(&self) -> Tag {
|
||||
self.args.call_info.name_tag.clone()
|
||||
}
|
||||
|
||||
pub fn parts(self) -> (InputStream, EvaluatedArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args.call_info.args)
|
||||
}
|
||||
|
||||
pub fn split(self) -> (InputStream, EvaluatedCommandArgs) {
|
||||
let EvaluatedWholeStreamCommandArgs { args, input } = self;
|
||||
|
||||
(input, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Getters, new)]
|
||||
#[get = "pub(crate)"]
|
||||
pub struct EvaluatedCommandArgs {
|
||||
pub host: Arc<parking_lot::Mutex<dyn Host>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub shell_manager: ShellManager,
|
||||
pub call_info: CallInfo,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl EvaluatedCommandArgs {
|
||||
pub fn nth(&self, pos: usize) -> Option<&Value> {
|
||||
self.call_info.args.nth(pos)
|
||||
}
|
||||
|
||||
/// Get the nth positional argument, error if not possible
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
|
||||
self.call_info
|
||||
.args
|
||||
.nth(pos)
|
||||
.ok_or_else(|| ShellError::unimplemented("Better error: expect_nth"))
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Value> {
|
||||
self.call_info.args.get(name)
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.call_info.args.has(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Example {
|
||||
pub example: &'static str,
|
||||
pub description: &'static str,
|
||||
pub result: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WholeStreamCommand: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name()).desc(self.usage()).filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Commands that are not meant to be run by users
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
// Custom commands are blocks, so we can use the information in the block to also
|
||||
// implement a WholeStreamCommand
|
||||
#[allow(clippy::suspicious_else_formatting)]
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Block {
|
||||
fn name(&self) -> &str {
|
||||
&self.params.name
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
self.params.clone()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
let mut block = self.clone();
|
||||
block.set_redirect(call_info.args.external_redirection);
|
||||
|
||||
let ctx = EvaluationContext::from_args(&args);
|
||||
let evaluated = call_info.evaluate(&ctx).await?;
|
||||
|
||||
let input = args.input;
|
||||
ctx.scope.enter_scope();
|
||||
if let Some(args) = evaluated.args.positional {
|
||||
// FIXME: do not do this
|
||||
for arg in args.into_iter().zip(self.params.positional.iter()) {
|
||||
let name = arg.1 .0.name();
|
||||
|
||||
if name.starts_with('$') {
|
||||
ctx.scope.add_var(name, arg.0);
|
||||
} else {
|
||||
ctx.scope.add_var(format!("${}", name), arg.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(args) = evaluated.args.named {
|
||||
for named in &block.params.named {
|
||||
let name = named.0;
|
||||
if let Some(value) = args.get(name) {
|
||||
if name.starts_with('$') {
|
||||
ctx.scope.add_var(name, value.clone());
|
||||
} else {
|
||||
ctx.scope.add_var(format!("${}", name), value.clone());
|
||||
}
|
||||
} else if name.starts_with('$') {
|
||||
ctx.scope
|
||||
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||
} else {
|
||||
ctx.scope.add_var(
|
||||
format!("${}", name),
|
||||
UntaggedValue::nothing().into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for named in &block.params.named {
|
||||
let name = named.0;
|
||||
if name.starts_with('$') {
|
||||
ctx.scope
|
||||
.add_var(name, UntaggedValue::nothing().into_untagged_value());
|
||||
} else {
|
||||
ctx.scope.add_var(
|
||||
format!("${}", name),
|
||||
UntaggedValue::nothing().into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = run_block(&block, &ctx, input).await;
|
||||
ctx.scope.exit_scope();
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_internal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command(Arc<dyn WholeStreamCommand>);
|
||||
|
||||
impl PrettyDebugWithSource for Command {
|
||||
fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
|
||||
b::typed(
|
||||
"whole stream command",
|
||||
b::description(self.name())
|
||||
+ b::space()
|
||||
+ b::equals()
|
||||
+ b::space()
|
||||
+ self.signature().pretty_debug(source),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Command {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Command({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn name(&self) -> &str {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> Signature {
|
||||
self.0.signature()
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> &str {
|
||||
self.0.usage()
|
||||
}
|
||||
|
||||
pub fn examples(&self) -> Vec<Example> {
|
||||
self.0.examples()
|
||||
}
|
||||
|
||||
pub async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.call_info.switch_present("help") {
|
||||
let cl = self.0.clone();
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(get_help(&*cl, &args.scope)).into_value(Tag::unknown()),
|
||||
))))
|
||||
} else {
|
||||
self.0.run(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_binary(&self) -> bool {
|
||||
self.0.is_binary()
|
||||
}
|
||||
|
||||
pub fn is_internal(&self) -> bool {
|
||||
self.0.is_internal()
|
||||
}
|
||||
|
||||
pub fn stream_command(&self) -> &dyn WholeStreamCommand {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn whole_stream_command(command: impl WholeStreamCommand + 'static) -> Command {
|
||||
Command(Arc::new(command))
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Compact;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CompactArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Compact {
|
||||
fn name(&self) -> &str {
|
||||
"compact"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("compact").rest(SyntaxShape::Any, "the columns to compact from the table")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a table with non-empty rows"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
compact(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Filter out all directory entries having no 'target'",
|
||||
example: "ls -la | compact target",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compact(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (CompactArgs { rest: columns }, input) = args.process().await?;
|
||||
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 {
|
||||
value: UntaggedValue::Row(ref r),
|
||||
..
|
||||
} => {
|
||||
if columns
|
||||
.iter()
|
||||
.all(|field| r.get_data(field).borrow().is_some())
|
||||
{
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Compact;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Compact {})?)
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config clear"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config clear")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"clear the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
clear(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Clear the config (be careful!)",
|
||||
example: "config clear",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
|
||||
// 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),
|
||||
)))
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::{CommandArgs, OutputStream};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"config"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Configuration management."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
)])
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetArgs {
|
||||
path: ColumnPath,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config get"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config get").required(
|
||||
"get",
|
||||
SyntaxShape::ColumnPath,
|
||||
"value to get from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Gets a value from the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
get(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the current startup commands",
|
||||
example: "config get startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (GetArgs { path }, _) = args.process().await?;
|
||||
|
||||
// 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)?;
|
||||
|
||||
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)),
|
||||
})
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
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::Path,
|
||||
"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,41 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config path"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the path to the config file"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
path(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the path to the current config file",
|
||||
example: "config path",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let path = config::default_path()?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Primitive(Primitive::Path(path)).into_value(args.call_info.name_tag),
|
||||
)))
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveArgs {
|
||||
remove: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config remove"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config remove").required(
|
||||
"remove",
|
||||
SyntaxShape::Any,
|
||||
"remove a value from the config",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Removes a value from the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
remove(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Remove the startup commands",
|
||||
example: "config remove startup",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
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())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Key does not exist in config",
|
||||
"key",
|
||||
remove.tag(),
|
||||
))
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetArgs {
|
||||
path: ColumnPath,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set")
|
||||
.required("key", SyntaxShape::ColumnPath, "variable name to set")
|
||||
.required("value", SyntaxShape::Any, "value to use")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Set auto pivoting",
|
||||
example: "config set pivot_mode always",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set line editor options",
|
||||
example: "config set line_editor [[edit_mode, completion_type]; [emacs circular]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set coloring options",
|
||||
example: "config set color_config [[header_align header_bold]; [left $true]]",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set nested options",
|
||||
example: "config set color_config.header_color white",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
// 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);
|
||||
|
||||
if let UntaggedValue::Table(rows) = &value.value {
|
||||
if rows.len() == 1 && rows[0].is_row() {
|
||||
value = rows[0].clone();
|
||||
}
|
||||
}
|
||||
|
||||
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Row(changes),
|
||||
..
|
||||
}) => {
|
||||
config::write(&changes.entries, &None)?;
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(changes).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
Ok(_) => Ok(OutputStream::empty()),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetIntoArgs {
|
||||
set_into: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"config set_into"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("config set_into").required(
|
||||
"set_into",
|
||||
SyntaxShape::String,
|
||||
"sets a variable from values in the pipeline",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a value in the config"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
set_into(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Store the contents of the pipeline as a path",
|
||||
example: "echo ['/usr/bin' '/bin'] | config set_into path",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
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 key = v.to_string();
|
||||
|
||||
Ok(if rows.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No values given for set_into",
|
||||
"needs value(s) from pipeline",
|
||||
v.tag(),
|
||||
));
|
||||
} else if rows.len() == 1 {
|
||||
// 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),
|
||||
))
|
||||
} else {
|
||||
// Take in the pipeline as a table
|
||||
let value = UntaggedValue::Table(rows).into_value(name.clone());
|
||||
|
||||
result.insert(key, value);
|
||||
|
||||
config::write(&result, &configuration)?;
|
||||
|
||||
OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
))
|
||||
})
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::stream::StreamExt;
|
||||
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;
|
||||
|
||||
Ok(test_examples(Count {})?)
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date utc"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date utc")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
utc(args).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once().await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Utc> = Utc::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
use crate::commands::each::process_row;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::CapturedBlock, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct EachGroup;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EachGroupArgs {
|
||||
group_size: Tagged<usize>,
|
||||
block: CapturedBlock,
|
||||
//numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for EachGroup {
|
||||
fn name(&self) -> &str {
|
||||
"each group"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("each group")
|
||||
.required("group_size", SyntaxShape::Int, "the size of each group")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run on each group",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block on groups of `group_size` rows of a table at a time."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Echo the sum of each pair",
|
||||
example: "echo [1 2 3 4] | each group 2 { echo $it | math sum }",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
async fn run(&self, raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||
let (each_args, input): (EachGroupArgs, _) = raw_args.process().await?;
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_block_on_vec(
|
||||
input: Vec<Value>,
|
||||
block: Arc<Box<CapturedBlock>>,
|
||||
context: Arc<EvaluationContext>,
|
||||
) -> impl Future<Output = OutputStream> {
|
||||
let value = Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag: Tag::unknown(),
|
||||
};
|
||||
|
||||
async {
|
||||
match process_row(block, context, value).await {
|
||||
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;
|
||||
|
||||
// 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.",
|
||||
));
|
||||
}
|
||||
|
||||
// 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EachGroup;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(EachGroup {})?)
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{
|
||||
Primitive, Range, RangeInclusion, ReturnSuccess, 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"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Echo the arguments back to the user."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
echo(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Put a hello message in the pipeline",
|
||||
example: "echo 'hello'",
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Print the value of the special '$nu' variable",
|
||||
example: "echo $nu",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn echo(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (args, _): (EchoArgs, _) = args.process().await?;
|
||||
|
||||
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()),
|
||||
))),
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream(),
|
||||
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))),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
struct RangeIterator {
|
||||
curr: Primitive,
|
||||
end: Primitive,
|
||||
tag: Tag,
|
||||
is_end_inclusive: bool,
|
||||
}
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||
let start = match range.from.0.item {
|
||||
Primitive::Nothing => Primitive::Int(0.into()),
|
||||
x => x,
|
||||
};
|
||||
|
||||
RangeIterator {
|
||||
curr: start,
|
||||
end: range.to.0.item,
|
||||
tag,
|
||||
is_end_inclusive: matches!(range.to.1, RangeInclusion::Inclusive),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Result<ReturnSuccess, ShellError>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let ordering = if self.end == Primitive::Nothing {
|
||||
Ordering::Less
|
||||
} else {
|
||||
let result =
|
||||
nu_data::base::coerce_compare_primitive(&self.curr, &self.end).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot create range",
|
||||
"unsupported range",
|
||||
self.tag.span,
|
||||
)
|
||||
});
|
||||
|
||||
if let Err(result) = result {
|
||||
return Some(Err(result));
|
||||
}
|
||||
|
||||
let result = result
|
||||
.expect("Internal error: the error case was already protected, but that failed");
|
||||
|
||||
result.compare()
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
if (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,
|
||||
&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",
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err((left_type, right_type)) => {
|
||||
return Some(Err(ShellError::coerce_error(
|
||||
left_type.spanned(self.tag.span),
|
||||
right_type.spanned(self.tag.span),
|
||||
)));
|
||||
}
|
||||
};
|
||||
Some(ReturnSuccess::value(output))
|
||||
} else {
|
||||
// TODO: add inclusive/exclusive ranges
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Echo;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Echo {})?)
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive::Filesize, ReturnSuccess, Signature, SyntaxShape, UntaggedValue,
|
||||
UntaggedValue::Primitive, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use nu_value_ext::get_data_by_column_path;
|
||||
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
|
||||
pub struct FileSize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
field: ColumnPath,
|
||||
format: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for FileSize {
|
||||
fn name(&self) -> &str {
|
||||
"format filesize"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format filesize")
|
||||
.required(
|
||||
"field",
|
||||
SyntaxShape::ColumnPath,
|
||||
"the name of the column to update",
|
||||
)
|
||||
.required(
|
||||
"format value",
|
||||
SyntaxShape::String,
|
||||
"the format into which convert the filesizes",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts a column of filesizes to some specified format"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
filesize(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the size row to KB",
|
||||
example: "ls | format filesize size KB",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert the apparent row to B",
|
||||
example: "du | format filesize apparent B",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
input: Value,
|
||||
format: Tagged<String>,
|
||||
field: Arc<ColumnPath>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok({
|
||||
let replace_for = get_data_by_column_path(&input, &field, move |_, _, error| error);
|
||||
match replace_for {
|
||||
Ok(s) => match convert_bytes_to_string_using_format(s, format) {
|
||||
Ok(b) => OutputStream::one(ReturnSuccess::value(
|
||||
input.replace_data_at_column_path(&field, b).expect("Given that the existence check was already done, this shouldn't trigger never"),
|
||||
)),
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
},
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn filesize(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { field, format }, input) = raw_args.process().await?;
|
||||
let field = Arc::new(field);
|
||||
|
||||
Ok(input
|
||||
.then(move |input| {
|
||||
let format = format.clone();
|
||||
let field = field.clone();
|
||||
|
||||
async {
|
||||
match process_row(input, format, field).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => OutputStream::one(Err(e)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn convert_bytes_to_string_using_format(
|
||||
bytes: Value,
|
||||
format: Tagged<String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match bytes.value {
|
||||
Primitive(Filesize(b)) => {
|
||||
let byte = byte_unit::Byte::from_bytes(b as u128);
|
||||
let value = match format.item().to_lowercase().as_str() {
|
||||
"b" => Ok(UntaggedValue::string(b.to_formatted_string(&Locale::en))),
|
||||
"kb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KB).to_string(),
|
||||
)),
|
||||
"kib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::KiB).to_string(),
|
||||
)),
|
||||
"mb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MB).to_string(),
|
||||
)),
|
||||
"mib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::MiB).to_string(),
|
||||
)),
|
||||
"gb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GB).to_string(),
|
||||
)),
|
||||
"gib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::GiB).to_string(),
|
||||
)),
|
||||
"tb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TB).to_string(),
|
||||
)),
|
||||
"tib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::TiB).to_string(),
|
||||
)),
|
||||
"pb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PB).to_string(),
|
||||
)),
|
||||
"pib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::PiB).to_string(),
|
||||
)),
|
||||
"eb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EB).to_string(),
|
||||
)),
|
||||
"eib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::EiB).to_string(),
|
||||
)),
|
||||
"zb" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZB).to_string(),
|
||||
)),
|
||||
"zib" => Ok(UntaggedValue::string(
|
||||
byte.get_adjusted_unit(byte_unit::ByteUnit::ZiB).to_string(),
|
||||
)),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
format!("Invalid format code: {:}", format.item()),
|
||||
"invalid format",
|
||||
format.tag(),
|
||||
)),
|
||||
};
|
||||
match value {
|
||||
Ok(b) => Ok(Value { value: b, ..bytes }),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"the data in this row is not of the type filesize",
|
||||
"invalid row type",
|
||||
bytes.tag(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FileSize;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(FileSize {})?)
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
pub struct IntoInt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IntoIntArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for IntoInt {
|
||||
fn name(&self) -> &str {
|
||||
"into-int"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into-int").rest(SyntaxShape::Any, "the values to into-int")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert value to integer"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
into_int(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert filesize to integer",
|
||||
example: "into-int 1kb | each { = $it / 1024 }",
|
||||
result: Some(vec![UntaggedValue::int(1).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn into_int(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (args, _): (IntoIntArgs, _) = args.process().await?;
|
||||
|
||||
let stream = args.rest.into_iter().map(|i| match i {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
} => match primitive_val {
|
||||
Primitive::Filesize(size) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::int(size.to_bigint().expect("Conversion should never fail.")),
|
||||
tag,
|
||||
}))),
|
||||
Primitive::Int(_) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(primitive_val),
|
||||
tag,
|
||||
}))),
|
||||
_ => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"Could not convert int value",
|
||||
"original value",
|
||||
tag,
|
||||
))),
|
||||
},
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(i))),
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IntoInt;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(IntoInt {})?)
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! command {
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($number:tt)* }
|
||||
Rest {}
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$(
|
||||
($named_param:tt : $named_type:ty : $named_kind:tt)
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$( ( $param_name:tt : $param_type:tt ) )*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct $export;
|
||||
|
||||
impl Command for $export {
|
||||
fn run(&self, $args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
fn command($args: EvaluatedCommandArgs, ( $($param_name),*, ): ( $($param_type),*, )) -> Result<OutputStream, ShellError> {
|
||||
let output = $body;
|
||||
|
||||
Ok(output.boxed().to_output_stream())
|
||||
}
|
||||
|
||||
let $args = $args.evaluate_once(registry)?;
|
||||
let tuple = ( $($extract ,)* );
|
||||
command( $args, tuple )
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
stringify!($config_name)
|
||||
}
|
||||
|
||||
fn config(&self) -> $nu_parser::registry::Signature {
|
||||
$nu_parser::registry::Signature {
|
||||
name: self.name().to_string(),
|
||||
positional: vec![$($mandatory_positional)*],
|
||||
rest_positional: false,
|
||||
is_filter: false,
|
||||
is_sink: false,
|
||||
|
||||
named: {
|
||||
use $nu_parser::registry::NamedType;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut named: indexmap::IndexMap<String, NamedType> = indexmap::IndexMap::new();
|
||||
|
||||
$(
|
||||
named.insert(stringify!($named_param).to_string(), $nu_parser::registry::NamedType::$named_kind);
|
||||
)*
|
||||
|
||||
named
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// switch
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident : Switch , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Switch : Switch)
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : Switch)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// mandatory named arguments
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Mandatory(NamedValue::Single))
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// optional named arguments
|
||||
(
|
||||
Named { $export:tt $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { -- $param_name:ident ? : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
($param_name : Optional(NamedValue::Single))
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract)* {
|
||||
use std::convert::TryInto;
|
||||
|
||||
$args.get(stringify!($param_name)).try_into()?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// mandatory positional block
|
||||
(
|
||||
Named { $export:ident $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { $param_name:ident : Block , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* $nu_parser::registry::PositionalType::mandatory_block(
|
||||
stringify!($param_name)
|
||||
), ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : Block)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
Block::extract(value)?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// mandatory positional argument
|
||||
(
|
||||
Named { $export:ident $args:ident $body:block }
|
||||
Positional { $($positional_count:tt)* }
|
||||
Rest { $param_name:ident : $param_kind:ty , $($rest:tt)* }
|
||||
Signature {
|
||||
name: $config_name:tt,
|
||||
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
|
||||
optional_positional: vec![ $($optional_positional:tt)* ],
|
||||
rest_positional: $rest_positional:tt,
|
||||
named: {
|
||||
$($config_named:tt)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function:tt)*
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)*
|
||||
}
|
||||
|
||||
) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { $($positional_count)* + 1 }
|
||||
Rest { $($rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![ $($mandatory_positional)* $nu_parser::registry::PositionalType::mandatory(
|
||||
stringify!($param_name), <$param_kind>::syntax_type()
|
||||
), ],
|
||||
optional_positional: vec![ $($optional_positional)* ],
|
||||
rest_positional: $rest_positional,
|
||||
named: {
|
||||
$($config_named)*
|
||||
}
|
||||
}
|
||||
|
||||
Function {
|
||||
$($function)* ($param_name : $param_kind)
|
||||
}
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
<$param_kind>::extract(&value)?
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
($export:ident as $config_name:tt ( $args:ident , $($command_rest:tt)* ) $body:block) => {
|
||||
command!(
|
||||
Named { $export $args $body }
|
||||
Positional { 0 }
|
||||
Rest { $($command_rest)* }
|
||||
Signature {
|
||||
name: $config_name,
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
rest_positional: false,
|
||||
named: {}
|
||||
}
|
||||
|
||||
Function {
|
||||
}
|
||||
|
||||
Extract {
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NthArgs {
|
||||
row_number: Tagged<u64>,
|
||||
rest: Vec<Tagged<u64>>,
|
||||
}
|
||||
|
||||
pub struct Nth;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Nth {
|
||||
fn name(&self) -> &str {
|
||||
"nth"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nth")
|
||||
.required(
|
||||
"row number",
|
||||
SyntaxShape::Int,
|
||||
"the number of the row to return",
|
||||
)
|
||||
.rest(SyntaxShape::Any, "Optionally return more rows")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return only the selected rows"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
nth(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the second row",
|
||||
example: "echo [first second third] | nth 1",
|
||||
result: Some(vec![Value::from("second")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get the first and third rows",
|
||||
example: "echo [first second third] | nth 0 2",
|
||||
result: Some(vec![Value::from("first"), Value::from("third")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn nth(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (
|
||||
NthArgs {
|
||||
row_number,
|
||||
rest: and_rows,
|
||||
},
|
||||
input,
|
||||
) = args.process().await?;
|
||||
|
||||
let row_numbers = vec![vec![row_number], and_rows]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|x| x.item)
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
let max_row_number = row_numbers
|
||||
.iter()
|
||||
.max()
|
||||
.expect("Internal error: should be > 0 row numbers");
|
||||
|
||||
Ok(input
|
||||
.take(*max_row_number as usize + 1)
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, item)| {
|
||||
futures::future::ready(
|
||||
if row_numbers.iter().any(|requested| *requested == idx as u64) {
|
||||
Some(ReturnSuccess::value(item))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Nth;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Nth {})?)
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Path;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Path {
|
||||
fn name(&self) -> &str {
|
||||
"path"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Explore and manipulate paths"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Path, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Path;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Path {})?)
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExtension;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathExtensionArguments {
|
||||
replace: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExtension {
|
||||
fn name(&self) -> &str {
|
||||
"path extension"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path extension")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with extension replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Gets the extension of a path"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (PathExtensionArguments { replace, rest }, input) = args.process().await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get extension of a path",
|
||||
example: "echo 'test.txt' | path extension",
|
||||
result: Some(vec![Value::from("txt")]),
|
||||
},
|
||||
Example {
|
||||
description: "You get an empty string if there is no extension",
|
||||
example: "echo 'test' | path extension",
|
||||
result: Some(vec![Value::from("")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace an extension with a custom string",
|
||||
example: "echo 'test.txt' | path extension -r md",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("test.md"))]),
|
||||
},
|
||||
Example {
|
||||
description: "To replace more complex extensions:",
|
||||
example: "echo 'test.tar.gz' | path extension -r '' | path extension -r txt",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("test.txt"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
match args.replace {
|
||||
Some(ref extension) => UntaggedValue::path(path.with_extension(extension)),
|
||||
None => UntaggedValue::string(match path.extension() {
|
||||
Some(extension) => extension.to_string_lossy(),
|
||||
None => "".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathExtension;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(PathExtension {})?)
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathFilestem;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathFilestemArguments {
|
||||
prefix: Option<Tagged<String>>,
|
||||
suffix: Option<Tagged<String>>,
|
||||
replace: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathFilestem {
|
||||
fn name(&self) -> &str {
|
||||
"path filestem"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path filestem")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with filestem replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.named(
|
||||
"prefix",
|
||||
SyntaxShape::String,
|
||||
"Strip this string from from the beginning of a file name",
|
||||
Some('p'),
|
||||
)
|
||||
.named(
|
||||
"suffix",
|
||||
SyntaxShape::String,
|
||||
"Strip this string from from the end of a file name",
|
||||
Some('s'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Gets the file stem of a path"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (
|
||||
PathFilestemArguments {
|
||||
replace,
|
||||
prefix,
|
||||
suffix,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process().await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: prefix.map(|v| v.item),
|
||||
suffix: suffix.map(|v| v.item),
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get filestem of a path",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg' | path filestem",
|
||||
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||
result: Some(vec![Value::from("lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the filestem that would be returned",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe\\bacon_spam.egg.gz"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get filestem of a path",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg' | path filestem",
|
||||
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||
result: Some(vec![Value::from("lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the filestem that would be returned",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/bacon_spam.egg.gz"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
let basename = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let suffix = match args.suffix {
|
||||
Some(ref suf) => match basename.rmatch_indices(suf).next() {
|
||||
Some((i, _)) => basename.split_at(i).1.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => match path.extension() {
|
||||
// Prepend '.' since the extension returned comes without it
|
||||
Some(ext) => ".".to_string() + &ext.to_string_lossy().to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let prefix = match args.prefix {
|
||||
Some(ref pre) => match basename.matches(pre).next() {
|
||||
Some(m) => basename.split_at(m.len()).0.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let basename_without_prefix = match basename.matches(&prefix).next() {
|
||||
Some(m) => basename.split_at(m.len()).1.to_string(),
|
||||
None => basename,
|
||||
};
|
||||
|
||||
let stem = match basename_without_prefix.rmatch_indices(&suffix).next() {
|
||||
Some((i, _)) => basename_without_prefix.split_at(i).0.to_string(),
|
||||
None => basename_without_prefix,
|
||||
};
|
||||
|
||||
match args.replace {
|
||||
Some(ref replace) => {
|
||||
let new_name = prefix + replace + &suffix;
|
||||
UntaggedValue::path(path.with_file_name(&new_name))
|
||||
}
|
||||
None => UntaggedValue::string(stem),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathFilestem;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(PathFilestem {})?)
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
mod basename;
|
||||
mod command;
|
||||
mod dirname;
|
||||
mod exists;
|
||||
mod expand;
|
||||
mod extension;
|
||||
mod filestem;
|
||||
mod r#type;
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Span;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use basename::PathBasename;
|
||||
pub use command::Path as PathCommand;
|
||||
pub use dirname::PathDirname;
|
||||
pub use exists::PathExists;
|
||||
pub use expand::PathExpand;
|
||||
pub use extension::PathExtension;
|
||||
pub use filestem::PathFilestem;
|
||||
pub use r#type::PathType;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DefaultArguments {
|
||||
// used by basename, dirname, extension and filestem
|
||||
replace: Option<String>,
|
||||
// used by filestem
|
||||
prefix: Option<String>,
|
||||
suffix: Option<String>,
|
||||
// used by dirname
|
||||
num_levels: Option<u32>,
|
||||
// used by all
|
||||
paths: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
fn handle_value<F>(
|
||||
action: &F,
|
||||
v: &Value,
|
||||
span: Span,
|
||||
args: Arc<DefaultArguments>,
|
||||
) -> Result<Value, ShellError>
|
||||
where
|
||||
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
|
||||
{
|
||||
let v = match &v.value {
|
||||
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf, args).into_value(v.tag()),
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||
action(s.as_ref(), args).into_value(v.tag())
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"value is not string or path",
|
||||
got,
|
||||
span,
|
||||
"originates from here".to_string(),
|
||||
v.tag().span,
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
async fn operate<F>(
|
||||
input: crate::InputStream,
|
||||
action: &'static F,
|
||||
span: Span,
|
||||
args: Arc<DefaultArguments>,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
|
||||
{
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if args.paths.is_empty() {
|
||||
ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &args.paths {
|
||||
let cloned_args = Arc::clone(&args);
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| handle_value(&action, &old, span, cloned_args)),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"random"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("random")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate random values"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, &args.scope))
|
||||
.into_value(Tag::unknown()),
|
||||
))))
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::deserializer::NumericRange;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{RangeInclusion, ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RangeArgs {
|
||||
area: Tagged<NumericRange>,
|
||||
}
|
||||
|
||||
pub struct Range;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Range {
|
||||
fn name(&self) -> &str {
|
||||
"range"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("range").required(
|
||||
"rows ",
|
||||
SyntaxShape::Range,
|
||||
"range of rows to return: Eg) 4..7 (=> from 4 to 7)",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Return only the selected rows"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
range(args).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn range(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (RangeArgs { area }, input) = args.process().await?;
|
||||
let range = area.item;
|
||||
let (from, left_inclusive) = range.from;
|
||||
let (to, right_inclusive) = range.to;
|
||||
let from = from.map(|from| *from as usize).unwrap_or(0).saturating_add(
|
||||
if left_inclusive == RangeInclusion::Inclusive {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
},
|
||||
);
|
||||
let to = to
|
||||
.map(|to| *to as usize)
|
||||
.unwrap_or(usize::MAX)
|
||||
.saturating_sub(if right_inclusive == RangeInclusion::Inclusive {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
});
|
||||
|
||||
Ok(input
|
||||
.skip(from)
|
||||
.take(to - from + 1)
|
||||
.map(ReturnSuccess::value)
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Range;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Range {})?)
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::each;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::{CommandArgs, Example, OutputStream};
|
||||
use futures::stream::once;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::{hir::CapturedBlock, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Reduce;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ReduceArgs {
|
||||
block: CapturedBlock,
|
||||
fold: Option<Value>,
|
||||
numbered: Tagged<bool>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Reduce {
|
||||
fn name(&self) -> &str {
|
||||
"reduce"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("reduce")
|
||||
.named(
|
||||
"fold",
|
||||
SyntaxShape::Any,
|
||||
"reduce with initial value",
|
||||
Some('f'),
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "reducing function")
|
||||
.switch(
|
||||
"numbered",
|
||||
"returned a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Aggregate a list table to a single value using an accumulator block. Block must be
|
||||
(A, A) -> A unless --fold is selected, in which case it may be A, B -> A."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
reduce(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Simple summation (equivalent to math sum)",
|
||||
example: "echo 1 2 3 4 | reduce { = $acc + $it }",
|
||||
result: Some(vec![UntaggedValue::int(10).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Summation from starting value using fold",
|
||||
example: "echo 1 2 3 4 | reduce -f $(= -1) { = $acc + $it }",
|
||||
result: Some(vec![UntaggedValue::int(9).into()]),
|
||||
},
|
||||
Example {
|
||||
description: "Folding with rows",
|
||||
example: "<table> | reduce -f 1.6 { = $acc * $(echo $it.a | str to-int) + $(echo $it.b | str to-int) }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Numbered reduce to find index of longest word",
|
||||
example: "echo one longest three bar | reduce -n { if $(echo $it.item | str length) > $(echo $acc.item | str length) {echo $it} {echo $acc}} | get index",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_row(
|
||||
block: Arc<CapturedBlock>,
|
||||
context: &EvaluationContext,
|
||||
row: Value,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let row_clone = row.clone();
|
||||
let input_stream = once(async { Ok(row_clone) }).to_input_stream();
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_vars(&block.captured.entries);
|
||||
context.scope.add_var("$it", row);
|
||||
let result = run_block(&block.block, context, input_stream).await;
|
||||
context.scope.exit_scope();
|
||||
|
||||
Ok(result?)
|
||||
}
|
||||
|
||||
async fn reduce(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let span = raw_args.call_info.name_tag.span;
|
||||
let context = Arc::new(EvaluationContext::from_raw(&raw_args));
|
||||
let (reduce_args, mut input): (ReduceArgs, _) = raw_args.process().await?;
|
||||
let block = Arc::new(reduce_args.block);
|
||||
let (ioffset, start) = if !input.is_empty() {
|
||||
match reduce_args.fold {
|
||||
None => {
|
||||
let first = input.next().await.expect("non-empty stream");
|
||||
|
||||
(1, first)
|
||||
}
|
||||
Some(acc) => (0, acc),
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected input",
|
||||
"needs input",
|
||||
span,
|
||||
));
|
||||
};
|
||||
|
||||
if reduce_args.numbered.item {
|
||||
// process_row returns Result<InputStream, ShellError>, so we must fold with one
|
||||
let initial = Ok(InputStream::one(each::make_indexed_item(
|
||||
ioffset - 1,
|
||||
start,
|
||||
)));
|
||||
|
||||
Ok(input
|
||||
.enumerate()
|
||||
.fold(initial, move |acc, input| {
|
||||
let context = context.clone();
|
||||
let block = Arc::clone(&block);
|
||||
let row = each::make_indexed_item(input.0 + ioffset, input.1);
|
||||
|
||||
async move {
|
||||
let values = acc?.drain_vec().await;
|
||||
|
||||
let f = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_var("$acc", f);
|
||||
let result = process_row(block, &*context, row).await;
|
||||
context.scope.exit_scope();
|
||||
|
||||
result
|
||||
}
|
||||
})
|
||||
.await?
|
||||
.to_output_stream())
|
||||
} else {
|
||||
let initial = Ok(InputStream::one(start));
|
||||
Ok(input
|
||||
.fold(initial, move |acc, row| {
|
||||
let block = Arc::clone(&block);
|
||||
let context = context.clone();
|
||||
|
||||
async move {
|
||||
let values = acc?.drain_vec().await;
|
||||
|
||||
let f = if values.len() == 1 {
|
||||
let value = values
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("No value to update with"))?;
|
||||
value.clone()
|
||||
} else if values.is_empty() {
|
||||
UntaggedValue::nothing().into_untagged_value()
|
||||
} else {
|
||||
UntaggedValue::table(&values).into_untagged_value()
|
||||
};
|
||||
|
||||
context.scope.enter_scope();
|
||||
context.scope.add_var("$acc", f);
|
||||
let result = process_row(block, &*context, row).await;
|
||||
context.scope.exit_scope();
|
||||
result
|
||||
}
|
||||
})
|
||||
.await?
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Reduce;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Reduce {})?)
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use nu_source::Tagged;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::atomic::Ordering,
|
||||
task::{Poll, Waker},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
pub struct Sleep;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SleepArgs {
|
||||
pub duration: Tagged<u64>,
|
||||
pub rest: Vec<Tagged<u64>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Sleep {
|
||||
fn name(&self) -> &str {
|
||||
"sleep"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sleep")
|
||||
.required("duration", SyntaxShape::Unit, "time to sleep")
|
||||
.rest(SyntaxShape::Unit, "additional time")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Delay for a specified amount of time"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let ctrl_c = args.ctrl_c().clone();
|
||||
|
||||
let (SleepArgs { duration, rest }, input) = args.process().await?;
|
||||
|
||||
let total_dur = Duration::from_nanos(duration.item)
|
||||
+ rest
|
||||
.iter()
|
||||
.map(|val| Duration::from_nanos(val.item))
|
||||
.sum::<Duration>();
|
||||
|
||||
SleepFuture::new(total_dur, ctrl_c).await;
|
||||
// this is necessary because the following 2 commands gave different results:
|
||||
// `echo | sleep 1sec` - nothing
|
||||
// `sleep 1sec` - table with 0 elements
|
||||
if input.is_empty() {
|
||||
Ok(OutputStream::empty())
|
||||
} else {
|
||||
Ok(input.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Sleep for 1sec",
|
||||
example: "sleep 1sec",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Sleep for 3sec",
|
||||
example: "sleep 1sec 1sec 1sec",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Delay the output of another command by 1sec",
|
||||
example: "echo [55 120] | sleep 1sec",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(55).into(),
|
||||
UntaggedValue::int(120).into(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
struct SleepFuture {
|
||||
shared_state: Arc<Mutex<SharedState>>,
|
||||
}
|
||||
|
||||
impl SleepFuture {
|
||||
/// Create a new `SleepFuture` which will complete after the provided
|
||||
/// timeout and check for Ctrl+C periodically.
|
||||
pub fn new(duration: Duration, ctrl_c: Arc<AtomicBool>) -> Self {
|
||||
let shared_state = Arc::new(Mutex::new(SharedState {
|
||||
done: false,
|
||||
waker: None,
|
||||
}));
|
||||
|
||||
// Spawn the main sleep thread
|
||||
let thread_shared_state = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
thread::sleep(duration);
|
||||
let mut shared_state = thread_shared_state.lock();
|
||||
// Signal that the timer has completed and wake up the last
|
||||
// task on which the future was polled, if one exists.
|
||||
if !shared_state.done {
|
||||
shared_state.done = true;
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Spawn the Ctrl+C-watching polling thread
|
||||
let thread_shared_state = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
{
|
||||
let mut shared_state = thread_shared_state.lock();
|
||||
// exit if the main thread is done
|
||||
if shared_state.done {
|
||||
return;
|
||||
}
|
||||
// finish the future prematurely if Ctrl+C has been pressed
|
||||
if ctrl_c.load(Ordering::SeqCst) {
|
||||
shared_state.done = true;
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake()
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// sleep for a short time
|
||||
thread::sleep(CTRL_C_CHECK_INTERVAL);
|
||||
}
|
||||
});
|
||||
|
||||
SleepFuture { shared_state }
|
||||
}
|
||||
}
|
||||
|
||||
struct SharedState {
|
||||
done: bool,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl Future for SleepFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
// Look at the shared state to see if the timer has already completed.
|
||||
let mut shared_state = self.shared_state.lock();
|
||||
if shared_state.done {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
// Set the waker if necessary
|
||||
if shared_state
|
||||
.waker
|
||||
.as_ref()
|
||||
.map(|waker| !waker.will_wake(&cx.waker()))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
shared_state.waker = Some(cx.waker().clone());
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Sleep;
|
||||
use nu_errors::ShellError;
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
let start = Instant::now();
|
||||
let results = test_examples(Sleep {});
|
||||
let elapsed = start.elapsed();
|
||||
println!("{:?}", elapsed);
|
||||
// only examples with actual output are run
|
||||
assert!(elapsed >= std::time::Duration::from_secs(1));
|
||||
assert!(elapsed < std::time::Duration::from_secs(2));
|
||||
|
||||
Ok(results?)
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str length"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str length")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"outputs the lengths of the strings in the pipeline"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |x| match x.as_string() {
|
||||
Ok(s) => ReturnSuccess::value(UntaggedValue::int(s.len()).into_untagged_value()),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "echo 'hello' | str length",
|
||||
result: Some(vec![UntaggedValue::int(5).into_untagged_value()]),
|
||||
},
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "echo 'hi' 'there' | str length",
|
||||
result: Some(vec![
|
||||
UntaggedValue::int(2).into_untagged_value(),
|
||||
UntaggedValue::int(5).into_untagged_value(),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str reverse"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str reverse")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"outputs the reversals of the strings in the pipeline"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(args
|
||||
.input
|
||||
.map(move |x| match x.as_string() {
|
||||
Ok(s) => ReturnSuccess::value(
|
||||
UntaggedValue::string(s.chars().rev().collect::<String>())
|
||||
.into_untagged_value(),
|
||||
),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Return the reversals of multiple strings",
|
||||
example: "echo 'Nushell' | str reverse",
|
||||
result: Some(vec![UntaggedValue::string("llehsuN").into_untagged_value()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
replace: Tagged<String>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str set"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str set")
|
||||
.required("set", SyntaxShape::String, "the new string to set")
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally set text by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"sets text"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
operate(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Set contents with preferred string",
|
||||
example: "echo 'good day' | str set 'good bye'",
|
||||
result: Some(vec![Value::from("good bye")]),
|
||||
},
|
||||
Example {
|
||||
description: "Set the contents on preferred column paths",
|
||||
example: "open Cargo.toml | str set '255' package.version",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Replace(String);
|
||||
|
||||
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { replace, rest }, input) = args.process().await?;
|
||||
let options = Replace(replace.item);
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &options, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &options, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(_input: &Value, options: &Replace, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let replacement = &options.0;
|
||||
Ok(UntaggedValue::string(replacement.as_str()).into_value(tag))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, Replace, SubCommand};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets() {
|
||||
let word = string("andres");
|
||||
let expected = string("robalino");
|
||||
|
||||
let set_options = Replace(String::from("robalino"));
|
||||
|
||||
let actual = action(&word, &set_options, Tag::unknown()).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, ShellTypeName, Signature, SyntaxShape, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, LocalResult, Offset, TimeZone};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
format: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str to-datetime"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str to-datetime")
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
)
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert text into datetime by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"converts text into datetime"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
operate(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert to datetime",
|
||||
example: "echo '16.11.1984 8:00 am +0000' | str to-datetime",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert to datetime",
|
||||
example: "echo '2020-08-04T16:39:18+00:00' | str to-datetime",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert to datetime using a custom format",
|
||||
example: "echo '20200904_163918+0000' | str to-datetime -f '%Y%m%d_%H%M%S%z'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DatetimeFormat(String);
|
||||
|
||||
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let (Arguments { format, rest }, input) = args.process().await?;
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
let options = if let Some(Tagged { item: fmt, .. }) = format {
|
||||
Some(DatetimeFormat(fmt))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &options, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
let options = options.clone();
|
||||
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &options, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
options: &Option<DatetimeFormat>,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let out = match options {
|
||||
Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
|
||||
Ok(d) => UntaggedValue::date(d),
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
format!("could not parse as datetime using format '{}'", dt.0),
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => match dtparse::parse(s) {
|
||||
Ok((native_dt, fixed_offset)) => {
|
||||
let offset = match fixed_offset {
|
||||
Some(fo) => fo,
|
||||
None => FixedOffset::east(0).fix(),
|
||||
};
|
||||
match offset.from_local_datetime(&native_dt) {
|
||||
LocalResult::Single(d) => UntaggedValue::date(d),
|
||||
LocalResult::Ambiguous(d, _) => UntaggedValue::date(d),
|
||||
LocalResult::None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not convert to a timezone-aware datetime",
|
||||
"local time representation is invalid",
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"could not parse as datetime",
|
||||
reason.to_string(),
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(out.into_value(tag))
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::{action, DatetimeFormat, SubCommand};
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(SubCommand {})?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_a_date_format() {
|
||||
let date_str = string("16.11.1984 8:00 am +0000");
|
||||
|
||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||
|
||||
let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap();
|
||||
|
||||
match actual.value {
|
||||
UntaggedValue::Primitive(Primitive::Date(_)) => {}
|
||||
_ => panic!("Didn't convert to date"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_iso8601_date_format() {
|
||||
let date_str = string("2020-08-04T16:39:18+00:00");
|
||||
let actual = action(&date_str, &None, Tag::unknown()).unwrap();
|
||||
match actual.value {
|
||||
UntaggedValue::Primitive(Primitive::Date(_)) => {}
|
||||
_ => panic!("Didn't convert to date"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
||||
let date_str = string("16.11.1984 8:00 am Oops0000");
|
||||
|
||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||
|
||||
let actual = action(&date_str, &fmt_options, Tag::unknown());
|
||||
|
||||
assert!(actual.is_err());
|
||||
}
|
||||
}
|
@ -1,261 +0,0 @@
|
||||
use crate::commands::table::options::{ConfigExtensions, NuConfig as TableConfiguration};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::primitive::get_color_config;
|
||||
use nu_data::value::{format_leaf, style_leaf};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_table::{draw_table, Alignment, StyledString, TextStyle};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
const STREAM_PAGE_SIZE: usize = 1000;
|
||||
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"table"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("table").named(
|
||||
"start_number",
|
||||
SyntaxShape::Number,
|
||||
"row number to start viewing from",
|
||||
Some('n'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View the contents of the pipeline as a table."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
table(TableConfiguration::new(), (args)).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_list(
|
||||
values: &[Value],
|
||||
configuration: &TableConfiguration,
|
||||
starting_idx: usize,
|
||||
color_hm: &HashMap<String, ansi_term::Style>,
|
||||
) -> nu_table::Table {
|
||||
let header_style = configuration.header_style();
|
||||
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
|
||||
.into_iter()
|
||||
.map(|x| StyledString::new(x, header_style))
|
||||
.collect();
|
||||
let entries = values_to_entries(values, &mut headers, configuration, starting_idx, &color_hm);
|
||||
nu_table::Table {
|
||||
headers,
|
||||
data: entries,
|
||||
theme: configuration.table_mode(),
|
||||
}
|
||||
}
|
||||
|
||||
fn values_to_entries(
|
||||
values: &[Value],
|
||||
headers: &mut Vec<StyledString>,
|
||||
configuration: &TableConfiguration,
|
||||
starting_idx: usize,
|
||||
color_hm: &HashMap<String, ansi_term::Style>,
|
||||
) -> Vec<Vec<StyledString>> {
|
||||
let disable_indexes = configuration.disabled_indexes();
|
||||
let mut entries = vec![];
|
||||
|
||||
if headers.is_empty() {
|
||||
headers.push(StyledString::new("".to_string(), TextStyle::basic_left()));
|
||||
}
|
||||
|
||||
for (idx, value) in values.iter().enumerate() {
|
||||
let mut row: Vec<StyledString> = headers
|
||||
.iter()
|
||||
.map(|d: &StyledString| {
|
||||
if d.contents.is_empty() {
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Row(..),
|
||||
..
|
||||
} => StyledString::new(
|
||||
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
|
||||
style_leaf(&UntaggedValue::nothing(), &color_hm),
|
||||
),
|
||||
_ => StyledString::new(
|
||||
format_leaf(value).plain_string(100_000),
|
||||
style_leaf(value, &color_hm),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Row(..),
|
||||
..
|
||||
} => {
|
||||
let data = value.get_data(&d.contents);
|
||||
|
||||
StyledString::new(
|
||||
format_leaf(data.borrow()).plain_string(100_000),
|
||||
style_leaf(data.borrow(), &color_hm),
|
||||
)
|
||||
}
|
||||
_ => StyledString::new(
|
||||
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
|
||||
style_leaf(&UntaggedValue::nothing(), &color_hm),
|
||||
),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Indices are green, bold, right-aligned:
|
||||
// unless we change them :)
|
||||
if !disable_indexes {
|
||||
row.insert(
|
||||
0,
|
||||
StyledString::new(
|
||||
(starting_idx + idx).to_string(),
|
||||
TextStyle::new().alignment(Alignment::Right).style(
|
||||
color_hm
|
||||
.get("index_color")
|
||||
.unwrap_or(
|
||||
&ansi_term::Style::default()
|
||||
.bold()
|
||||
.fg(ansi_term::Color::Green),
|
||||
)
|
||||
.to_owned(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
entries.push(row);
|
||||
}
|
||||
|
||||
if !disable_indexes {
|
||||
headers.insert(
|
||||
0,
|
||||
StyledString::new(
|
||||
"#".to_owned(),
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(ansi_term::Color::Green)
|
||||
.bold(Some(true)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
async fn table(
|
||||
configuration: TableConfiguration,
|
||||
args: CommandArgs,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut args = args.evaluate_once().await?;
|
||||
let mut finished = false;
|
||||
// Ideally, get_color_config would get all the colors configured in the config.toml
|
||||
// and create a style based on those settings. However, there are few places where
|
||||
// this just won't work right now, like header styling, because a style needs to know
|
||||
// more than just color, it needs fg & bg color, bold, dimmed, italic, underline,
|
||||
// blink, reverse, hidden, strikethrough and most of those aren't available in the
|
||||
// config.toml.... yet.
|
||||
let color_hm = get_color_config();
|
||||
|
||||
let mut start_number = match args.get("start_number") {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(i)),
|
||||
..
|
||||
}) => {
|
||||
if let Some(num) = i.to_usize() {
|
||||
num
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a row number",
|
||||
"expected a row number",
|
||||
&args.args.call_info.name_tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let mut delay_slot = None;
|
||||
|
||||
let term_width = args.host.lock().width();
|
||||
|
||||
while !finished {
|
||||
let mut new_input: VecDeque<Value> = VecDeque::new();
|
||||
|
||||
let start_time = Instant::now();
|
||||
for idx in 0..STREAM_PAGE_SIZE {
|
||||
if let Some(val) = delay_slot {
|
||||
new_input.push_back(val);
|
||||
delay_slot = None;
|
||||
} else {
|
||||
match args.input.next().await {
|
||||
Some(a) => {
|
||||
if !new_input.is_empty() {
|
||||
if let Some(descs) = new_input.get(0) {
|
||||
let descs = descs.data_descriptors();
|
||||
let compare = a.data_descriptors();
|
||||
if descs != compare {
|
||||
delay_slot = Some(a);
|
||||
break;
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
} else {
|
||||
new_input.push_back(a);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we've gone over our buffering threshold
|
||||
if (idx + 1) % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
|
||||
let end_time = Instant::now();
|
||||
|
||||
// If we've been buffering over a second, go ahead and send out what we have so far
|
||||
if (end_time - start_time).as_secs() >= 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input: Vec<Value> = new_input.into();
|
||||
|
||||
if !input.is_empty() {
|
||||
let t = from_list(&input, &configuration, start_number, &color_hm);
|
||||
|
||||
draw_table(&t, term_width, &color_hm);
|
||||
}
|
||||
|
||||
start_number += input.len();
|
||||
}
|
||||
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct ToMarkdown;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ToMarkdownArgs {
|
||||
pretty: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for ToMarkdown {
|
||||
fn name(&self) -> &str {
|
||||
"to md"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("to md").switch(
|
||||
"pretty",
|
||||
"Formats the Markdown table to vertically align items",
|
||||
Some('p'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into simple Markdown"
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
to_md(args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an unformatted md string representing the contents of ls",
|
||||
example: "ls | to md",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Outputs a formatted md string representing the contents of ls",
|
||||
example: "ls | to md -p",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_md(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (ToMarkdownArgs { pretty }, input) = args.process().await?;
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
let headers = nu_protocol::merge_descriptors(&input);
|
||||
|
||||
let mut escaped_headers: Vec<String> = Vec::new();
|
||||
let mut column_widths: Vec<usize> = Vec::new();
|
||||
|
||||
if !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty()) {
|
||||
for header in &headers {
|
||||
let escaped_header_string = htmlescape::encode_minimal(&header);
|
||||
column_widths.push(escaped_header_string.len());
|
||||
escaped_headers.push(escaped_header_string);
|
||||
}
|
||||
} else {
|
||||
column_widths = vec![0; headers.len()]
|
||||
}
|
||||
|
||||
let mut escaped_rows: Vec<Vec<String>> = Vec::new();
|
||||
|
||||
for row in &input {
|
||||
let mut escaped_row: Vec<String> = Vec::new();
|
||||
|
||||
match row.value.clone() {
|
||||
UntaggedValue::Row(row) => {
|
||||
for i in 0..headers.len() {
|
||||
let data = row.get_data(&headers[i]);
|
||||
let value_string = format_leaf(data.borrow()).plain_string(100_000);
|
||||
let new_column_width = value_string.len();
|
||||
|
||||
escaped_row.push(value_string);
|
||||
|
||||
if column_widths[i] < new_column_width {
|
||||
column_widths[i] = new_column_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
p => {
|
||||
let value_string =
|
||||
htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000));
|
||||
escaped_row.push(value_string);
|
||||
}
|
||||
}
|
||||
|
||||
escaped_rows.push(escaped_row);
|
||||
}
|
||||
|
||||
let output_string = if (column_widths.is_empty() || column_widths.iter().all(|x| *x == 0))
|
||||
&& escaped_rows.is_empty()
|
||||
{
|
||||
String::from("")
|
||||
} else {
|
||||
get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty)
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output_string).into_value(name_tag),
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_output_string(
|
||||
headers: &[String],
|
||||
rows: &[Vec<String>],
|
||||
column_widths: &[usize],
|
||||
pretty: bool,
|
||||
) -> String {
|
||||
let mut output_string = String::new();
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push('|');
|
||||
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(
|
||||
headers[i].clone(),
|
||||
column_widths[i],
|
||||
' ',
|
||||
));
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push_str(headers[i].as_str());
|
||||
}
|
||||
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
output_string.push_str("\n|");
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(
|
||||
String::from("-"),
|
||||
column_widths[i],
|
||||
'-',
|
||||
));
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push('-');
|
||||
}
|
||||
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
output_string.push('\n');
|
||||
}
|
||||
|
||||
for row in rows {
|
||||
if !headers.is_empty() {
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
for i in 0..row.len() {
|
||||
if pretty {
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' '));
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push_str(row[i].as_str());
|
||||
}
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push('|');
|
||||
}
|
||||
}
|
||||
|
||||
output_string.push('\n');
|
||||
}
|
||||
|
||||
output_string
|
||||
}
|
||||
|
||||
fn get_padded_string(text: String, desired_length: usize, padding_character: char) -> String {
|
||||
let repeat_length = if text.len() > desired_length {
|
||||
0
|
||||
} else {
|
||||
desired_length - text.len()
|
||||
};
|
||||
|
||||
format!(
|
||||
"{}{}",
|
||||
text,
|
||||
padding_character.to_string().repeat(repeat_length)
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::ToMarkdown;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(ToMarkdown {})?)
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Which;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Which {
|
||||
fn name(&self) -> &str {
|
||||
"which"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("which")
|
||||
.required("application", SyntaxShape::String, "application")
|
||||
.switch("all", "list all executables", Some('a'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds a program file, alias or custom command."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
which(args).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcuts for creating an entry to the output table
|
||||
fn entry(arg: impl Into<String>, path: Value, builtin: bool, tag: Tag) -> Value {
|
||||
let mut map = IndexMap::new();
|
||||
map.insert(
|
||||
"arg".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(arg.into())).into_value(tag.clone()),
|
||||
);
|
||||
map.insert("path".to_string(), path);
|
||||
map.insert(
|
||||
"builtin".to_string(),
|
||||
UntaggedValue::boolean(builtin).into_value(tag.clone()),
|
||||
);
|
||||
|
||||
UntaggedValue::row(map).into_value(tag)
|
||||
}
|
||||
|
||||
macro_rules! create_entry {
|
||||
($arg:expr, $path:expr, $tag:expr, $is_builtin:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::String($path.to_string())).into_value($tag.clone()),
|
||||
$is_builtin,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! entry_path {
|
||||
($arg:expr, $path:expr, $tag:expr) => {
|
||||
entry(
|
||||
$arg.clone(),
|
||||
UntaggedValue::Primitive(Primitive::Path($path)).into_value($tag.clone()),
|
||||
false,
|
||||
$tag,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WhichArgs {
|
||||
application: Tagged<String>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
async fn which(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let mut output = vec![];
|
||||
let scope = args.scope.clone();
|
||||
|
||||
let (WhichArgs { application, all }, _) = args.process().await?;
|
||||
let external = application.starts_with('^');
|
||||
let item = if external {
|
||||
application.item[1..].to_string()
|
||||
} else {
|
||||
application.item.clone()
|
||||
};
|
||||
if !external {
|
||||
if let Some(entry) = entry_for(&scope, &item, application.tag.clone()) {
|
||||
output.push(ReturnSuccess::value(entry));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ichwh")]
|
||||
{
|
||||
if let Ok(paths) = ichwh::which_all(&item).await {
|
||||
for path in paths {
|
||||
output.push(ReturnSuccess::value(entry_path!(
|
||||
item,
|
||||
path.into(),
|
||||
application.tag.clone()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if all {
|
||||
Ok(futures::stream::iter(output.into_iter()).to_output_stream())
|
||||
} else {
|
||||
Ok(futures::stream::iter(output.into_iter().take(1)).to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_for(scope: &Scope, name: &str, tag: Tag) -> Option<Value> {
|
||||
if scope.has_custom_command(name) {
|
||||
Some(create_entry!(name, "Nushell custom command", tag, false))
|
||||
} else if scope.has_command(name) {
|
||||
Some(create_entry!(name, "Nushell built-in command", tag, true))
|
||||
} else if scope.has_alias(name) {
|
||||
Some(create_entry!(name, "Nushell alias", tag, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::Which;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Which {})?)
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ use indexmap::set::IndexSet;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||
|
||||
pub struct CommandCompleter;
|
||||
|
||||
@ -121,7 +122,7 @@ fn is_executable(path: &Path) -> bool {
|
||||
|
||||
// TODO cache these, but watch for changes to PATH
|
||||
fn find_path_executables() -> Option<IndexSet<String>> {
|
||||
let path_var = std::env::var_os("PATH")?;
|
||||
let path_var = std::env::var_os(NATIVE_PATH_ENV_VAR)?;
|
||||
let paths: Vec<_> = std::env::split_paths(&path_var).collect();
|
||||
|
||||
let mut executables: IndexSet<String> = IndexSet::new();
|
||||
|
@ -37,7 +37,7 @@ impl<'s> Flatten<'s> {
|
||||
)
|
||||
.collect(),
|
||||
Expression::Command => vec![LocationType::Command.spanned(e.span)],
|
||||
Expression::Path(path) => self.expression(&path.head),
|
||||
Expression::FullColumnPath(path) => self.expression(&path.head),
|
||||
Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)],
|
||||
|
||||
Expression::Boolean(_)
|
||||
@ -254,9 +254,11 @@ 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, group, lex, ParserScope};
|
||||
use nu_parser::{classify_block, lex, parse_block, ParserScope};
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -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![]
|
||||
}
|
||||
|
||||
@ -307,7 +309,7 @@ mod tests {
|
||||
pos: usize,
|
||||
) -> Vec<LocationType> {
|
||||
let (tokens, _) = lex(line, 0);
|
||||
let (lite_block, _) = group(tokens);
|
||||
let (lite_block, _) = parse_block(tokens);
|
||||
|
||||
scope.enter_scope();
|
||||
let (block, _) = classify_block(&lite_block, scope);
|
||||
@ -382,7 +384,7 @@ mod tests {
|
||||
#[test]
|
||||
fn completes_incomplete_nested_structure() {
|
||||
let registry: VecRegistry = vec![Signature::build("sys")].into();
|
||||
let line = "echo $(sy";
|
||||
let line = "echo (sy";
|
||||
|
||||
assert_eq!(
|
||||
completion_location(line, ®istry, 8),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
pub struct FlagCompleter {
|
||||
pub(crate) cmd: String,
|
||||
|
@ -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]
|
||||
|
@ -4,8 +4,8 @@ pub(crate) mod flag;
|
||||
pub(crate) mod matchers;
|
||||
pub(crate) mod path;
|
||||
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use matchers::Matcher;
|
||||
use nu_engine::EvaluationContext;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Suggestion {
|
||||
|
@ -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()),
|
||||
@ -29,7 +30,7 @@ impl PathCompleter {
|
||||
{
|
||||
let home_prefix = format!("~{}", SEP);
|
||||
if base_dir_name.starts_with(&home_prefix) {
|
||||
let mut home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"));
|
||||
let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~"));
|
||||
home_dir.push(&base_dir_name[2..]);
|
||||
home_dir
|
||||
} else {
|
||||
|
@ -1,6 +0,0 @@
|
||||
pub(crate) mod directory_specific_environment;
|
||||
pub(crate) mod environment;
|
||||
pub(crate) mod environment_syncer;
|
||||
pub(crate) mod host;
|
||||
|
||||
pub(crate) use self::host::Host;
|
@ -1,260 +0,0 @@
|
||||
use crate::commands;
|
||||
use commands::autoenv;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
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: &PathBuf) -> 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: &PathBuf,
|
||||
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(dir.clone())
|
||||
.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())
|
||||
}
|
303
crates/nu-cli/src/env/environment.rs
vendored
303
crates/nu-cli/src/env/environment.rs
vendored
@ -1,303 +0,0 @@
|
||||
use crate::env::directory_specific_environment::*;
|
||||
use indexmap::{indexmap, IndexSet};
|
||||
use nu_data::config::Conf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::env::*;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Env: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str);
|
||||
fn add_path(&mut self, new_path: OsString);
|
||||
}
|
||||
|
||||
impl Env for Box<dyn Env> {
|
||||
fn env(&self) -> Option<Value> {
|
||||
(**self).env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn add_env(&mut self, key: &str, value: &str) {
|
||||
(**self).add_env(key, value);
|
||||
}
|
||||
|
||||
fn add_path(&mut self, new_path: OsString) {
|
||||
(**self).add_path(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Environment {
|
||||
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 = 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()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
608
crates/nu-cli/src/env/environment_syncer.rs
vendored
608
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,608 +0,0 @@
|
||||
use crate::env::environment::{Env, Environment};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
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 mut environment = self.env.lock();
|
||||
|
||||
if environment.env().is_some() {
|
||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||
if name != "path" && name != "PATH" {
|
||||
// account for new env vars present in the current session
|
||||
// that aren't loaded from config.
|
||||
environment.add_env(&name, &value);
|
||||
|
||||
// clear the env var from the session
|
||||
// we are about to replace them
|
||||
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(name)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(variables) = environment.env() {
|
||||
for var in variables.row_entries() {
|
||||
if let Ok(string) = var.1.as_string() {
|
||||
ctx.with_host(|host| {
|
||||
host.env_set(
|
||||
std::ffi::OsString::from(var.0),
|
||||
std::ffi::OsString::from(string),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
if environment.path().is_some() {
|
||||
let native_paths = ctx.with_host(|host| host.env_get(std::ffi::OsString::from("PATH")));
|
||||
|
||||
if let Some(native_paths) = native_paths {
|
||||
environment.add_path(native_paths);
|
||||
|
||||
ctx.with_host(|host| {
|
||||
host.env_rm(std::ffi::OsString::from("PATH"));
|
||||
});
|
||||
}
|
||||
|
||||
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.with_host(|host| {
|
||||
host.env_set(std::ffi::OsString::from("PATH"), paths_ready);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 crate::env::environment::Env;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::config::tests::FakeConfig;
|
||||
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 = EvaluationContext::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::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.clone()));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
// Nu loads the environment variables from the configuration file
|
||||
actual.load_environment();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!actual.did_config_change());
|
||||
|
||||
// Replacing the newer configuration file to the existing one.
|
||||
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
|
||||
std::fs::write(&file, &new_config_contents).expect("Failed");
|
||||
|
||||
// A change has happened
|
||||
assert!(actual.did_config_change());
|
||||
|
||||
// Syncer should reload and add new envs
|
||||
actual.reload();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = EvaluationContext::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::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);
|
||||
|
||||
// Confirms session environment variables are replaced from Nu configuration file
|
||||
// including the newer one accounted for.
|
||||
ctx.with_host(|test_host| {
|
||||
let var_user = test_host
|
||||
.env_get(std::ffi::OsString::from("USER"))
|
||||
.expect("Couldn't get USER var from host.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
let var_shell = test_host
|
||||
.env_get(std::ffi::OsString::from("SHELL"))
|
||||
.expect("Couldn't get SHELL var from host.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
let mut found = IndexMap::new();
|
||||
found.insert("SHELL".to_string(), var_shell);
|
||||
found.insert("USER".to_string(), var_user);
|
||||
|
||||
for k in found.keys() {
|
||||
assert!(expected.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
// Now confirm in-memory environment variables synced appropriately
|
||||
// including the newer one accounted for.
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
|
||||
let mut ctx = EvaluationContext::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::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_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);
|
||||
|
||||
ctx.with_host(|test_host| {
|
||||
let var_shell = test_host
|
||||
.env_get(std::ffi::OsString::from("SHELL"))
|
||||
.expect("Couldn't get SHELL var from host.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
let mut found = IndexMap::new();
|
||||
found.insert("SHELL".to_string(), var_shell);
|
||||
|
||||
for k in found.keys() {
|
||||
assert!(expected.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
|
||||
) -> Result<(), ShellError> {
|
||||
let mut ctx = EvaluationContext::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::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);
|
||||
|
||||
ctx.with_host(|test_host| {
|
||||
let actual = test_host
|
||||
.env_get(std::ffi::OsString::from("PATH"))
|
||||
.expect("Couldn't get PATH var from host.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
});
|
||||
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let paths = std::env::join_paths(
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment.")
|
||||
.table_entries()
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
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 = EvaluationContext::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::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);
|
||||
|
||||
ctx.with_host(|test_host| {
|
||||
let actual = test_host
|
||||
.env_get(std::ffi::OsString::from("PATH"))
|
||||
.expect("Couldn't get PATH var from host.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
});
|
||||
|
||||
let environment = actual.env.lock();
|
||||
|
||||
let paths = std::env::join_paths(
|
||||
&environment
|
||||
.path()
|
||||
.expect("No path variable in the environment.")
|
||||
.table_entries()
|
||||
.map(|value| value.as_string().expect("Couldn't convert to string"))
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.expect("Couldn't join paths.")
|
||||
.into_string()
|
||||
.expect("Couldn't convert to string.");
|
||||
|
||||
assert_eq!(paths, expected);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
212
crates/nu-cli/src/env/host.rs
vendored
212
crates/nu-cli/src/env/host.rs
vendored
@ -1,212 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
#[cfg(test)]
|
||||
use indexmap::IndexMap;
|
||||
// use nu_errors::ShellError;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Host: Debug + Send {
|
||||
fn out_termcolor(&self) -> termcolor::StandardStream;
|
||||
fn err_termcolor(&self) -> termcolor::StandardStream;
|
||||
|
||||
fn stdout(&mut self, out: &str);
|
||||
fn stderr(&mut self, out: &str);
|
||||
|
||||
fn vars(&mut self) -> Vec<(String, String)>;
|
||||
fn env_get(&mut self, key: OsString) -> Option<OsString>;
|
||||
fn env_set(&mut self, k: OsString, v: OsString);
|
||||
fn env_rm(&mut self, k: OsString);
|
||||
|
||||
fn width(&self) -> usize;
|
||||
}
|
||||
|
||||
impl Host for Box<dyn Host> {
|
||||
fn stdout(&mut self, out: &str) {
|
||||
(**self).stdout(out)
|
||||
}
|
||||
|
||||
fn stderr(&mut self, out: &str) {
|
||||
(**self).stderr(out)
|
||||
}
|
||||
|
||||
fn vars(&mut self) -> Vec<(String, String)> {
|
||||
(**self).vars()
|
||||
}
|
||||
|
||||
fn env_get(&mut self, key: OsString) -> Option<OsString> {
|
||||
(**self).env_get(key)
|
||||
}
|
||||
|
||||
fn env_set(&mut self, key: OsString, value: OsString) {
|
||||
(**self).env_set(key, value);
|
||||
}
|
||||
|
||||
fn env_rm(&mut self, key: OsString) {
|
||||
(**self).env_rm(key)
|
||||
}
|
||||
|
||||
fn out_termcolor(&self) -> termcolor::StandardStream {
|
||||
(**self).out_termcolor()
|
||||
}
|
||||
|
||||
fn err_termcolor(&self) -> termcolor::StandardStream {
|
||||
(**self).err_termcolor()
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
(**self).width()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BasicHost;
|
||||
|
||||
impl Host for BasicHost {
|
||||
fn stdout(&mut self, out: &str) {
|
||||
match out {
|
||||
"\n" => outln!(""),
|
||||
other => outln!("{}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn stderr(&mut self, out: &str) {
|
||||
match out {
|
||||
"\n" => errln!(""),
|
||||
other => errln!("{}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn vars(&mut self) -> Vec<(String, String)> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
std::env::vars().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn env_get(&mut self, key: OsString) -> Option<OsString> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
std::env::var_os(key)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn env_set(&mut self, key: OsString, value: OsString) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn env_rm(&mut self, key: OsString) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
std::env::remove_var(key);
|
||||
}
|
||||
}
|
||||
|
||||
fn out_termcolor(&self) -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto)
|
||||
}
|
||||
|
||||
fn err_termcolor(&self) -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto)
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
let (mut term_width, _) = term_size::dimensions().unwrap_or((80, 20));
|
||||
term_width -= 1;
|
||||
term_width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Debug)]
|
||||
pub struct FakeHost {
|
||||
line_written: String,
|
||||
env_vars: IndexMap<String, String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl FakeHost {
|
||||
pub fn new() -> FakeHost {
|
||||
FakeHost {
|
||||
line_written: String::from(""),
|
||||
env_vars: IndexMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Host for FakeHost {
|
||||
fn stdout(&mut self, out: &str) {
|
||||
self.line_written = out.to_string();
|
||||
}
|
||||
|
||||
fn stderr(&mut self, out: &str) {
|
||||
self.line_written = out.to_string();
|
||||
}
|
||||
|
||||
fn vars(&mut self) -> Vec<(String, String)> {
|
||||
self.env_vars
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn env_get(&mut self, key: OsString) -> Option<OsString> {
|
||||
let key = key.into_string().expect("Couldn't convert to string.");
|
||||
|
||||
match self.env_vars.get(&key) {
|
||||
Some(env) => Some(OsString::from(env)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn env_set(&mut self, key: OsString, value: OsString) {
|
||||
self.env_vars.insert(
|
||||
key.into_string().expect("Couldn't convert to string."),
|
||||
value.into_string().expect("Couldn't convert to string."),
|
||||
);
|
||||
}
|
||||
|
||||
fn env_rm(&mut self, key: OsString) {
|
||||
self.env_vars
|
||||
.shift_remove(&key.into_string().expect("Couldn't convert to string."));
|
||||
}
|
||||
|
||||
fn out_termcolor(&self) -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto)
|
||||
}
|
||||
|
||||
fn err_termcolor(&self) -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto)
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
// pub(crate) fn handle_unexpected<T>(
|
||||
// host: &mut dyn Host,
|
||||
// func: impl FnOnce(&mut dyn Host) -> Result<T, ShellError>,
|
||||
// ) {
|
||||
// let result = func(host);
|
||||
|
||||
// if let Err(err) = result {
|
||||
// host.stderr(&format!("Something unexpected happened:\n{:?}", err));
|
||||
// }
|
||||
// }
|
@ -1,61 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
for v in env.iter() {
|
||||
if v.0 != "PATH" && v.0 != "Path" {
|
||||
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
|
||||
}
|
||||
}
|
||||
nu_dict.insert_value("env", dict.into_value());
|
||||
|
||||
let config = nu_data::config::read(&tag, &None)?;
|
||||
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
||||
|
||||
let mut table = vec![];
|
||||
let path = std::env::var_os("PATH");
|
||||
if let Some(paths) = path {
|
||||
for path in std::env::split_paths(&paths) {
|
||||
table.push(UntaggedValue::path(path).into_value(&tag));
|
||||
}
|
||||
}
|
||||
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
|
||||
|
||||
let path = std::env::current_dir()?;
|
||||
nu_dict.insert_value("cwd", UntaggedValue::path(path).into_value(&tag));
|
||||
|
||||
if let Some(home) = crate::shell::filesystem_shell::homedir_if_possible() {
|
||||
nu_dict.insert_value("home-dir", UntaggedValue::path(home).into_value(&tag));
|
||||
}
|
||||
|
||||
let temp = std::env::temp_dir();
|
||||
nu_dict.insert_value("temp-dir", UntaggedValue::path(temp).into_value(&tag));
|
||||
|
||||
let config = nu_data::config::default_path()?;
|
||||
nu_dict.insert_value("config-path", UntaggedValue::path(config).into_value(&tag));
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
{
|
||||
let keybinding_path = crate::keybinding::keybinding_path()?;
|
||||
nu_dict.insert_value(
|
||||
"keybinding-path",
|
||||
UntaggedValue::path(keybinding_path).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
let config: Box<dyn nu_data::config::Conf> = Box::new(nu_data::config::NuConfig::new());
|
||||
let history = crate::commands::history::history_path(&config);
|
||||
nu_dict.insert_value(
|
||||
"history-path",
|
||||
UntaggedValue::path(history).into_value(&tag),
|
||||
);
|
||||
|
||||
Ok(nu_dict.into_value())
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
|
||||
use crate::env::host::Host;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell_manager::ShellManager;
|
||||
use nu_protocol::hir;
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_stream::{InputStream, OutputStream};
|
||||
use parking_lot::Mutex;
|
||||
use std::error::Error;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EvaluationContext {
|
||||
pub scope: Scope,
|
||||
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
|
||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||
pub ctrl_c: Arc<AtomicBool>,
|
||||
pub user_recently_used_autoenv_untrust: Arc<AtomicBool>,
|
||||
pub(crate) shell_manager: ShellManager,
|
||||
|
||||
/// Windows-specific: keep track of previous cwd on each drive
|
||||
pub windows_drives_previous_cwd: Arc<Mutex<std::collections::HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl EvaluationContext {
|
||||
pub(crate) fn from_raw(raw_args: &CommandArgs) -> EvaluationContext {
|
||||
EvaluationContext {
|
||||
scope: raw_args.scope.clone(),
|
||||
host: raw_args.host.clone(),
|
||||
current_errors: raw_args.current_errors.clone(),
|
||||
ctrl_c: raw_args.ctrl_c.clone(),
|
||||
shell_manager: raw_args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_args(args: &CommandArgs) -> EvaluationContext {
|
||||
EvaluationContext {
|
||||
scope: args.scope.clone(),
|
||||
host: args.host.clone(),
|
||||
current_errors: args.current_errors.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic() -> Result<EvaluationContext, Box<dyn Error>> {
|
||||
Ok(EvaluationContext {
|
||||
scope: Scope::new(),
|
||||
host: Arc::new(parking_lot::Mutex::new(Box::new(
|
||||
crate::env::host::BasicHost,
|
||||
))),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
|
||||
shell_manager: ShellManager::basic()?,
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn error(&self, error: ShellError) {
|
||||
self.with_errors(|errors| errors.push(error))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_errors(&self) {
|
||||
self.current_errors.lock().clear()
|
||||
}
|
||||
|
||||
pub(crate) fn get_errors(&self) -> Vec<ShellError> {
|
||||
self.current_errors.lock().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_print_errors(&self, source: Text) -> bool {
|
||||
let errors = self.current_errors.clone();
|
||||
let mut errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
let error = errors[0].clone();
|
||||
*errors = vec![];
|
||||
|
||||
crate::script::print_err(error, &source);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure<T>(
|
||||
&mut self,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
block: impl FnOnce(&dyn nu_data::config::Conf, &mut Self) -> T,
|
||||
) {
|
||||
block(config, &mut *self);
|
||||
}
|
||||
|
||||
pub(crate) fn with_host<T>(&self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
|
||||
let mut host = self.host.lock();
|
||||
|
||||
block(&mut *host)
|
||||
}
|
||||
|
||||
pub(crate) fn with_errors<T>(&self, block: impl FnOnce(&mut Vec<ShellError>) -> T) -> T {
|
||||
let mut errors = self.current_errors.lock();
|
||||
|
||||
block(&mut *errors)
|
||||
}
|
||||
|
||||
pub fn add_commands(&self, commands: Vec<Command>) {
|
||||
for command in commands {
|
||||
self.scope.add_command(command.name().to_string(), command);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
|
||||
self.scope.get_command(name)
|
||||
}
|
||||
|
||||
pub(crate) fn is_command_registered(&self, name: &str) -> bool {
|
||||
self.scope.has_command(name)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_command(
|
||||
&self,
|
||||
command: Command,
|
||||
name_tag: Tag,
|
||||
args: hir::Call,
|
||||
input: InputStream,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let command_args = self.command_args(args, input, name_tag);
|
||||
command.run(command_args).await
|
||||
}
|
||||
|
||||
fn call_info(&self, args: hir::Call, name_tag: Tag) -> UnevaluatedCallInfo {
|
||||
UnevaluatedCallInfo { args, name_tag }
|
||||
}
|
||||
|
||||
fn command_args(&self, args: hir::Call, input: InputStream, name_tag: Tag) -> CommandArgs {
|
||||
CommandArgs {
|
||||
host: self.host.clone(),
|
||||
ctrl_c: self.ctrl_c.clone(),
|
||||
current_errors: self.current_errors.clone(),
|
||||
shell_manager: self.shell_manager.clone(),
|
||||
call_info: self.call_info(args, name_tag),
|
||||
scope: self.scope.clone(),
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_env(&self) -> IndexMap<String, String> {
|
||||
let mut output = IndexMap::new();
|
||||
for (var, value) in self.host.lock().vars() {
|
||||
output.insert(var, value);
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
@ -1,493 +0,0 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::hir::ClassifiedBlock;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{AnchorLocation, TaggedItem};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::command::CommandArgs;
|
||||
use crate::commands::{
|
||||
whole_stream_command, BuildString, Command, Each, Echo, First, Get, Keep, Last, Let, Nth,
|
||||
StrCollect, WholeStreamCommand, Wrap,
|
||||
};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use nu_stream::{InputStream, OutputStream};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::executor::block_on;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
|
||||
let examples = cmd.examples();
|
||||
|
||||
let base_context = EvaluationContext::basic()?;
|
||||
|
||||
base_context.add_commands(vec![
|
||||
// Mocks
|
||||
whole_stream_command(MockLs {}),
|
||||
// Minimal restricted commands to aid in testing
|
||||
whole_stream_command(Echo {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(First {}),
|
||||
whole_stream_command(Get {}),
|
||||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(Last {}),
|
||||
whole_stream_command(Nth {}),
|
||||
whole_stream_command(Let {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
]);
|
||||
|
||||
for sample_pipeline in examples {
|
||||
let mut ctx = base_context.clone();
|
||||
|
||||
let block = parse_line(sample_pipeline.example, &ctx)?;
|
||||
|
||||
println!("{:#?}", block);
|
||||
|
||||
if let Some(expected) = &sample_pipeline.result {
|
||||
let result = block_on(evaluate_block(block, &mut ctx))?;
|
||||
|
||||
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
||||
.map_or(Ok(()), Err)?;
|
||||
|
||||
if expected.len() != result.len() {
|
||||
let rows_returned =
|
||||
format!("expected: {}\nactual: {}", expected.len(), result.len());
|
||||
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
||||
|
||||
panic!(
|
||||
"example command produced unexpected number of results.\n {} {}",
|
||||
failed_call, rows_returned
|
||||
);
|
||||
}
|
||||
|
||||
for (e, a) in expected.iter().zip(result.iter()) {
|
||||
if !values_equal(e, a) {
|
||||
let row_errored = format!("expected: {:#?}\nactual: {:#?}", e, a);
|
||||
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
||||
|
||||
panic!(
|
||||
"example command produced unexpected result.\n {} {}",
|
||||
failed_call, row_errored
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
|
||||
let examples = cmd.examples();
|
||||
|
||||
let base_context = EvaluationContext::basic()?;
|
||||
|
||||
base_context.add_commands(vec![
|
||||
whole_stream_command(Echo {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(Get {}),
|
||||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(Let {}),
|
||||
whole_stream_command(cmd),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
]);
|
||||
|
||||
for sample_pipeline in examples {
|
||||
let mut ctx = base_context.clone();
|
||||
|
||||
let block = parse_line(sample_pipeline.example, &ctx)?;
|
||||
|
||||
if let Some(expected) = &sample_pipeline.result {
|
||||
let result = block_on(evaluate_block(block, &mut ctx))?;
|
||||
|
||||
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
||||
.map_or(Ok(()), Err)?;
|
||||
|
||||
if expected.len() != result.len() {
|
||||
let rows_returned =
|
||||
format!("expected: {}\nactual: {}", expected.len(), result.len());
|
||||
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
||||
|
||||
panic!(
|
||||
"example command produced unexpected number of results.\n {} {}",
|
||||
failed_call, rows_returned
|
||||
);
|
||||
}
|
||||
|
||||
for (e, a) in expected.iter().zip(result.iter()) {
|
||||
if !values_equal(e, a) {
|
||||
let row_errored = format!("expected: {:#?}\nactual: {:#?}", e, a);
|
||||
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
||||
|
||||
panic!(
|
||||
"example command produced unexpected result.\n {} {}",
|
||||
failed_call, row_errored
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
||||
let examples = cmd.examples();
|
||||
|
||||
let base_context = EvaluationContext::basic()?;
|
||||
|
||||
base_context.add_commands(vec![
|
||||
// Minimal restricted commands to aid in testing
|
||||
whole_stream_command(MockCommand {}),
|
||||
whole_stream_command(MockEcho {}),
|
||||
whole_stream_command(MockLs {}),
|
||||
whole_stream_command(BuildString {}),
|
||||
whole_stream_command(First {}),
|
||||
whole_stream_command(Get {}),
|
||||
whole_stream_command(Keep {}),
|
||||
whole_stream_command(Each {}),
|
||||
whole_stream_command(Last {}),
|
||||
whole_stream_command(Nth {}),
|
||||
whole_stream_command(Let {}),
|
||||
whole_stream_command(StrCollect),
|
||||
whole_stream_command(Wrap),
|
||||
cmd,
|
||||
]);
|
||||
|
||||
for sample_pipeline in examples {
|
||||
let pipeline_with_anchor = format!("mock --open --path | {}", sample_pipeline.example);
|
||||
|
||||
let mut ctx = base_context.clone();
|
||||
|
||||
let block = parse_line(&pipeline_with_anchor, &ctx)?;
|
||||
let result = block_on(evaluate_block(block, &mut ctx))?;
|
||||
|
||||
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
||||
.map_or(Ok(()), Err)?;
|
||||
|
||||
for actual in result.iter() {
|
||||
if !is_anchor_carried(actual, mock_path()) {
|
||||
let failed_call = format!("command: {}\n", pipeline_with_anchor);
|
||||
|
||||
panic!(
|
||||
"example command didn't carry anchor tag correctly.\n {} {:#?} {:#?}",
|
||||
failed_call,
|
||||
actual,
|
||||
mock_path()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse and run a nushell pipeline
|
||||
fn parse_line(line: &str, ctx: &EvaluationContext) -> Result<ClassifiedBlock, ShellError> {
|
||||
//FIXME: do we still need this?
|
||||
let line = if let Some(line) = line.strip_suffix('\n') {
|
||||
line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
let (lite_result, err) = nu_parser::lex(&line, 0);
|
||||
if let Some(err) = err {
|
||||
return Err(err.into());
|
||||
}
|
||||
let (lite_result, err) = nu_parser::group(lite_result);
|
||||
if let Some(err) = err {
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
let (block, err) = nu_parser::classify_block(&lite_result, &ctx.scope);
|
||||
Ok(ClassifiedBlock { block, failed: err })
|
||||
}
|
||||
|
||||
async fn evaluate_block(
|
||||
block: ClassifiedBlock,
|
||||
ctx: &mut EvaluationContext,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let input_stream = InputStream::empty();
|
||||
let env = ctx.get_env();
|
||||
|
||||
ctx.scope.enter_scope();
|
||||
ctx.scope.add_env(env);
|
||||
|
||||
let result = run_block(&block.block, ctx, input_stream).await;
|
||||
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
let result = result?.drain_vec().await;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// TODO probably something already available to do this
|
||||
// TODO perhaps better panic messages when things don't compare
|
||||
|
||||
// Deep value comparisons that ignore tags
|
||||
fn values_equal(expected: &Value, actual: &Value) -> bool {
|
||||
use nu_protocol::UntaggedValue::*;
|
||||
|
||||
match (&expected.value, &actual.value) {
|
||||
(Primitive(e), Primitive(a)) => e == a,
|
||||
(Row(e), Row(a)) => {
|
||||
if e.entries.len() != a.entries.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
e.entries
|
||||
.iter()
|
||||
.zip(a.entries.iter())
|
||||
.all(|((ek, ev), (ak, av))| ek == ak && values_equal(ev, av))
|
||||
}
|
||||
(Table(e), Table(a)) => e.iter().zip(a.iter()).all(|(e, a)| values_equal(e, a)),
|
||||
(e, a) => unimplemented!("{} {}", e.type_name(), a.type_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_anchor_carried(actual: &Value, anchor: AnchorLocation) -> bool {
|
||||
actual.tag.anchor() == Some(anchor)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Arguments {
|
||||
path: Option<bool>,
|
||||
open: bool,
|
||||
}
|
||||
|
||||
struct MockCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for MockCommand {
|
||||
fn name(&self) -> &str {
|
||||
"mock"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("mock")
|
||||
.switch("open", "fake opening sources", Some('o'))
|
||||
.switch("path", "file open", Some('p'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generates tables and metadata that mimics behavior of real commands in controlled ways."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
Arguments {
|
||||
path: mocked_path,
|
||||
open: open_mock,
|
||||
},
|
||||
_input,
|
||||
) = args.process().await?;
|
||||
|
||||
let out = UntaggedValue::string("Yehuda Katz in Ecuador");
|
||||
|
||||
if open_mock {
|
||||
if let Some(true) = mocked_path {
|
||||
return Ok(OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: out,
|
||||
tag: Tag {
|
||||
anchor: Some(mock_path()),
|
||||
span: name_tag.span,
|
||||
},
|
||||
}))));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
|
||||
out.into_value(name_tag),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
struct MockEcho;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MockEchoArgs {
|
||||
pub rest: Vec<Value>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for MockEcho {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("echo").rest(SyntaxShape::Any, "the values to echo")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Mock echo."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let (MockEchoArgs { rest }, input) = args.process().await?;
|
||||
|
||||
let mut base_value = UntaggedValue::string("Yehuda Katz in Ecuador").into_value(name_tag);
|
||||
let input: Vec<Value> = input.collect().await;
|
||||
|
||||
if let Some(first) = input.get(0) {
|
||||
base_value = first.clone()
|
||||
}
|
||||
|
||||
let stream = rest.into_iter().map(move |i| {
|
||||
let base_value = base_value.clone();
|
||||
match i.as_string() {
|
||||
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
tag: base_value.tag,
|
||||
}))),
|
||||
_ => match i {
|
||||
Value {
|
||||
value: UntaggedValue::Table(table),
|
||||
..
|
||||
} => {
|
||||
if table.len() == 1 && table[0].is_table() {
|
||||
let mut values: Vec<Value> =
|
||||
table[0].table_entries().map(Clone::clone).collect();
|
||||
|
||||
for v in values.iter_mut() {
|
||||
v.tag = base_value.tag();
|
||||
}
|
||||
|
||||
let subtable =
|
||||
vec![UntaggedValue::Table(values).into_value(base_value.tag())];
|
||||
|
||||
futures::stream::iter(subtable.into_iter().map(ReturnSuccess::value))
|
||||
.to_output_stream()
|
||||
} else {
|
||||
futures::stream::iter(
|
||||
table
|
||||
.into_iter()
|
||||
.map(move |mut v| {
|
||||
v.tag = base_value.tag();
|
||||
v
|
||||
})
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream()
|
||||
}
|
||||
}
|
||||
_ => OutputStream::one(Ok(ReturnSuccess::Value(Value {
|
||||
value: i.value.clone(),
|
||||
tag: base_value.tag,
|
||||
}))),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
struct MockLs;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for MockLs {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ls")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Mock ls."
|
||||
}
|
||||
|
||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
|
||||
let mut base_value =
|
||||
UntaggedValue::string("Andrés N. Robalino in Portland").into_value(name_tag);
|
||||
let input: Vec<Value> = args.input.collect().await;
|
||||
|
||||
if let Some(first) = input.get(0) {
|
||||
base_value = first.clone()
|
||||
}
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
file_listing()
|
||||
.iter()
|
||||
.map(|row| Value {
|
||||
value: row.value.clone(),
|
||||
tag: base_value.tag.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(ReturnSuccess::value),
|
||||
)
|
||||
.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
fn int(s: impl Into<BigInt>) -> Value {
|
||||
UntaggedValue::int(s).into_untagged_value()
|
||||
}
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn date(input: impl Into<String>) -> Value {
|
||||
let key = input.into().tagged_unknown();
|
||||
crate::value::Date::naive_from_str(key.borrow_tagged())
|
||||
.expect("date from string failed")
|
||||
.into_untagged_value()
|
||||
}
|
||||
|
||||
fn file_listing() -> Vec<Value> {
|
||||
vec![
|
||||
row! {
|
||||
"name".to_string() => string("Andrés.txt"),
|
||||
"type".to_string() => string("File"),
|
||||
"chickens".to_string() => int(10),
|
||||
"modified".to_string() => date("2019-07-23")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Jonathan"),
|
||||
"type".to_string() => string("Dir"),
|
||||
"chickens".to_string() => int(5),
|
||||
"modified".to_string() => date("2019-07-23")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Andrés.txt"),
|
||||
"type".to_string() => string("File"),
|
||||
"chickens".to_string() => int(20),
|
||||
"modified".to_string() => date("2019-09-24")
|
||||
},
|
||||
row! {
|
||||
"name".to_string() => string("Yehuda"),
|
||||
"type".to_string() => string("Dir"),
|
||||
"chickens".to_string() => int(4),
|
||||
"modified".to_string() => date("2019-09-24")
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn mock_path() -> AnchorLocation {
|
||||
let path = String::from("path/to/las_best_arepas_in_the_world.txt");
|
||||
|
||||
AnchorLocation::File(path)
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
use futures::stream::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::task::{self, Poll, Waker};
|
||||
use std::thread;
|
||||
|
||||
#[allow(clippy::option_option)]
|
||||
struct SharedState<T: Send + 'static> {
|
||||
result: Option<Option<T>>,
|
||||
kill: bool,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
pub struct ThreadedReceiver<T: Send + 'static> {
|
||||
shared_state: Arc<Mutex<SharedState<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> ThreadedReceiver<T> {
|
||||
pub fn new(recv: mpsc::Receiver<T>) -> ThreadedReceiver<T> {
|
||||
let shared_state = Arc::new(Mutex::new(SharedState {
|
||||
result: None,
|
||||
kill: false,
|
||||
waker: None,
|
||||
}));
|
||||
|
||||
// Clone everything to avoid lifetimes
|
||||
let thread_shared_state = shared_state.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
let result = recv.recv();
|
||||
|
||||
{
|
||||
let mut shared_state = thread_shared_state
|
||||
.lock()
|
||||
.expect("ThreadedFuture shared state shouldn't be poisoned");
|
||||
|
||||
if let Ok(result) = result {
|
||||
shared_state.result = Some(Some(result));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't attempt to recv anything else until consumed
|
||||
loop {
|
||||
let mut shared_state = thread_shared_state
|
||||
.lock()
|
||||
.expect("ThreadedFuture shared state shouldn't be poisoned");
|
||||
|
||||
if shared_state.kill {
|
||||
return;
|
||||
}
|
||||
|
||||
if shared_state.result.is_some() {
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let the Stream implementation know that we're done
|
||||
let mut shared_state = thread_shared_state
|
||||
.lock()
|
||||
.expect("ThreadedFuture shared state shouldn't be poisoned");
|
||||
|
||||
shared_state.result = Some(None);
|
||||
if let Some(waker) = shared_state.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
});
|
||||
|
||||
ThreadedReceiver { shared_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Stream for ThreadedReceiver<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut shared_state = self
|
||||
.shared_state
|
||||
.lock()
|
||||
.expect("ThreadedFuture shared state shouldn't be poisoned");
|
||||
|
||||
if let Some(result) = shared_state.result.take() {
|
||||
Poll::Ready(result)
|
||||
} else {
|
||||
shared_state.waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Drop for ThreadedReceiver<T> {
|
||||
fn drop(&mut self) {
|
||||
// Setting the kill flag to true will cause the thread spawned in `new` to exit, which
|
||||
// will cause the `Receiver` argument to get dropped. This can allow senders to
|
||||
// potentially clean up.
|
||||
match self.shared_state.lock() {
|
||||
Ok(mut state) => state.kill = true,
|
||||
Err(mut poisoned_err) => poisoned_err.get_mut().kill = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod threaded_receiver {
|
||||
use super::super::ThreadedReceiver;
|
||||
use futures::executor::block_on_stream;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[test]
|
||||
fn returns_expected_result() {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
std::thread::spawn(move || {
|
||||
let _ = tx.send(1);
|
||||
let _ = tx.send(2);
|
||||
let _ = tx.send(3);
|
||||
});
|
||||
|
||||
let stream = ThreadedReceiver::new(rx);
|
||||
let mut result = block_on_stream(stream);
|
||||
assert_eq!(Some(1), result.next());
|
||||
assert_eq!(Some(2), result.next());
|
||||
assert_eq!(Some(3), result.next());
|
||||
assert_eq!(None, result.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drops_receiver_when_stream_dropped() {
|
||||
let (tx, rx) = mpsc::sync_channel(0);
|
||||
let th = std::thread::spawn(move || {
|
||||
tx.send(1).and_then(|_| tx.send(2)).and_then(|_| tx.send(3))
|
||||
});
|
||||
|
||||
{
|
||||
let stream = ThreadedReceiver::new(rx);
|
||||
let mut result = block_on_stream(stream);
|
||||
assert_eq!(Some(1), result.next());
|
||||
}
|
||||
let result = th.join();
|
||||
assert_eq!(true, result.unwrap().is_err());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
pub fn current_branch() -> Option<String> {
|
||||
#[cfg(feature = "git2")]
|
||||
{
|
||||
use git2::{Repository, RepositoryOpenFlags};
|
||||
use std::ffi::OsString;
|
||||
|
||||
let v: Vec<OsString> = vec![];
|
||||
match Repository::open_ext(".", RepositoryOpenFlags::empty(), v) {
|
||||
Ok(repo) => {
|
||||
let r = repo.head();
|
||||
match r {
|
||||
Ok(r) => match r.shorthand() {
|
||||
Some(s) => Some(s.to_string()),
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "git2"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
@ -1,38 +1,43 @@
|
||||
use rustyline::{KeyCode as RustyKeyCode, 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: KeyCode, modifiers: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
match key_event {
|
||||
KeyCode::UnknownEscSeq => convert_to_rl_keyevent(RustyKeyCode::UnknownEscSeq, modifiers),
|
||||
KeyCode::Backspace => convert_to_rl_keyevent(RustyKeyCode::Backspace, modifiers),
|
||||
KeyCode::BackTab => convert_to_rl_keyevent(RustyKeyCode::BackTab, modifiers),
|
||||
KeyCode::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteStart, modifiers)
|
||||
}
|
||||
KeyCode::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteEnd, modifiers)
|
||||
}
|
||||
KeyCode::Char(c) => convert_to_rl_keyevent(RustyKeyCode::Char(c), modifiers),
|
||||
KeyCode::Delete => convert_to_rl_keyevent(RustyKeyCode::Delete, modifiers),
|
||||
KeyCode::Down => convert_to_rl_keyevent(RustyKeyCode::Down, modifiers),
|
||||
KeyCode::End => convert_to_rl_keyevent(RustyKeyCode::End, modifiers),
|
||||
KeyCode::Enter => convert_to_rl_keyevent(RustyKeyCode::Enter, modifiers),
|
||||
KeyCode::Esc => convert_to_rl_keyevent(RustyKeyCode::Esc, modifiers),
|
||||
KeyCode::F(u) => convert_to_rl_keyevent(RustyKeyCode::F(u), modifiers),
|
||||
KeyCode::Home => convert_to_rl_keyevent(RustyKeyCode::Home, modifiers),
|
||||
KeyCode::Insert => convert_to_rl_keyevent(RustyKeyCode::Insert, modifiers),
|
||||
KeyCode::Left => convert_to_rl_keyevent(RustyKeyCode::Left, modifiers),
|
||||
KeyCode::Null => convert_to_rl_keyevent(RustyKeyCode::Null, modifiers),
|
||||
KeyCode::PageDown => convert_to_rl_keyevent(RustyKeyCode::PageDown, modifiers),
|
||||
KeyCode::PageUp => convert_to_rl_keyevent(RustyKeyCode::PageUp, modifiers),
|
||||
KeyCode::Right => convert_to_rl_keyevent(RustyKeyCode::Right, modifiers),
|
||||
KeyCode::Tab => convert_to_rl_keyevent(RustyKeyCode::Tab, modifiers),
|
||||
KeyCode::Up => convert_to_rl_keyevent(RustyKeyCode::Up, modifiers),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_rl_keyevent(
|
||||
key_code: RustyKeyCode,
|
||||
modifier: Option<Modifiers>,
|
||||
) -> rustyline::KeyEvent {
|
||||
rustyline::KeyEvent {
|
||||
0: key_code,
|
||||
1: modifier.unwrap_or(Modifiers::NONE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,19 +102,23 @@ 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,
|
||||
Cmd::Complete => rustyline::Cmd::Complete,
|
||||
Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward,
|
||||
Cmd::CompleteHint => rustyline::Cmd::CompleteHint,
|
||||
Cmd::Dedent(movement) => rustyline::Cmd::Dedent(convert_movement(movement)),
|
||||
Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord,
|
||||
Cmd::EndOfFile => rustyline::Cmd::EndOfFile,
|
||||
Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory,
|
||||
Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory,
|
||||
Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward,
|
||||
Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward,
|
||||
Cmd::Indent(movement) => rustyline::Cmd::Indent(convert_movement(movement)),
|
||||
Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string),
|
||||
Cmd::Interrupt => rustyline::Cmd::Interrupt,
|
||||
Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)),
|
||||
@ -117,8 +126,11 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u),
|
||||
Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)),
|
||||
Cmd::NextHistory => rustyline::Cmd::NextHistory,
|
||||
Cmd::Newline => rustyline::Cmd::Newline,
|
||||
Cmd::Noop => rustyline::Cmd::Noop,
|
||||
Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c),
|
||||
#[cfg(windows)]
|
||||
Cmd::PasteFromClipboard => rustyline::Cmd::PasteFromClipboard,
|
||||
Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory,
|
||||
Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert,
|
||||
Cmd::Replace {
|
||||
@ -140,18 +152,32 @@ 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) {
|
||||
let rusty_modifiers = match keybinding.modifiers {
|
||||
Some(mods) => match mods {
|
||||
NuModifiers::CTRL => Some(Modifiers::CTRL),
|
||||
NuModifiers::ALT => Some(Modifiers::ALT),
|
||||
NuModifiers::SHIFT => Some(Modifiers::SHIFT),
|
||||
NuModifiers::NONE => Some(Modifiers::NONE),
|
||||
NuModifiers::CTRL_SHIFT => Some(Modifiers::CTRL_SHIFT),
|
||||
NuModifiers::ALT_SHIFT => Some(Modifiers::ALT_SHIFT),
|
||||
NuModifiers::CTRL_ALT => Some(Modifiers::CTRL_ALT),
|
||||
NuModifiers::CTRL_ALT_SHIFT => Some(Modifiers::CTRL_ALT_SHIFT),
|
||||
// _ => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
(
|
||||
convert_keypress(keybinding.key),
|
||||
convert_keyevent(keybinding.key, rusty_modifiers),
|
||||
convert_cmd(keybinding.binding),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum KeyPress {
|
||||
pub enum KeyCode {
|
||||
/// Unsupported escape sequence (on unix platform)
|
||||
UnknownEscSeq,
|
||||
/// ⌫ or `KeyPress::Ctrl('H')`
|
||||
/// ⌫ or `KeyEvent::Ctrl('H')`
|
||||
Backspace,
|
||||
/// ⇤ (usually Shift-Tab)
|
||||
BackTab,
|
||||
@ -161,25 +187,15 @@ pub enum KeyPress {
|
||||
BracketedPasteEnd,
|
||||
/// Single char
|
||||
Char(char),
|
||||
/// Ctrl-↓
|
||||
ControlDown,
|
||||
/// Ctrl-←
|
||||
ControlLeft,
|
||||
/// Ctrl-→
|
||||
ControlRight,
|
||||
/// Ctrl-↑
|
||||
ControlUp,
|
||||
/// Ctrl-char
|
||||
Ctrl(char),
|
||||
/// ⌦
|
||||
Delete,
|
||||
/// ↓ arrow key
|
||||
Down,
|
||||
/// ⇲
|
||||
End,
|
||||
/// ↵ or `KeyPress::Ctrl('M')`
|
||||
/// ↵ or `KeyEvent::Ctrl('M')`
|
||||
Enter,
|
||||
/// Escape or `KeyPress::Ctrl('[')`
|
||||
/// Escape or `KeyEvent::Ctrl('[')`
|
||||
Esc,
|
||||
/// Function key
|
||||
F(u8),
|
||||
@ -189,9 +205,7 @@ pub enum KeyPress {
|
||||
Insert,
|
||||
/// ← arrow key
|
||||
Left,
|
||||
/// Escape-char or Alt-char
|
||||
Meta(char),
|
||||
/// `KeyPress::Char('\0')`
|
||||
// /// `KeyEvent::Char('\0')`
|
||||
Null,
|
||||
/// ⇟
|
||||
PageDown,
|
||||
@ -199,15 +213,7 @@ pub enum KeyPress {
|
||||
PageUp,
|
||||
/// → arrow key
|
||||
Right,
|
||||
/// Shift-↓
|
||||
ShiftDown,
|
||||
/// Shift-←
|
||||
ShiftLeft,
|
||||
/// Shift-→
|
||||
ShiftRight,
|
||||
/// Shift-↑
|
||||
ShiftUp,
|
||||
/// ⇥ or `KeyPress::Ctrl('I')`
|
||||
/// ⇥ or `KeyEvent::Ctrl('I')`
|
||||
Tab,
|
||||
/// ↑ arrow key
|
||||
Up,
|
||||
@ -231,6 +237,8 @@ pub enum Cmd {
|
||||
CompleteBackward,
|
||||
/// complete-hint
|
||||
CompleteHint,
|
||||
/// Dedent current line
|
||||
Dedent(Movement),
|
||||
/// downcase-word
|
||||
DowncaseWord,
|
||||
/// vi-eof-maybe
|
||||
@ -243,6 +251,8 @@ pub enum Cmd {
|
||||
HistorySearchBackward,
|
||||
/// history-search-forward
|
||||
HistorySearchForward,
|
||||
/// Indent current line
|
||||
Indent(Movement),
|
||||
/// Insert text
|
||||
Insert { repeat: RepeatCount, string: String },
|
||||
/// Interrupt signal (Ctrl-C)
|
||||
@ -255,12 +265,17 @@ pub enum Cmd {
|
||||
/// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
|
||||
/// vi-prev-word
|
||||
Move(Movement),
|
||||
/// Inserts a newline
|
||||
Newline,
|
||||
/// next-history
|
||||
NextHistory,
|
||||
/// No action
|
||||
Noop,
|
||||
/// vi-replace
|
||||
Overwrite(char),
|
||||
/// Paste from the clipboard
|
||||
#[cfg(windows)]
|
||||
PasteFromClipboard,
|
||||
/// previous-history
|
||||
PreviousHistory,
|
||||
/// quoted-insert
|
||||
@ -394,31 +409,51 @@ pub enum CharSearch {
|
||||
BackwardAfter(char),
|
||||
}
|
||||
|
||||
/// The set of modifier keys that were triggered along with a key press.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::clippy::upper_case_acronyms)]
|
||||
pub enum NuModifiers {
|
||||
/// Control modifier
|
||||
CTRL = 8,
|
||||
/// Escape or Alt modifier
|
||||
ALT = 4,
|
||||
/// Shift modifier
|
||||
SHIFT = 2,
|
||||
/// No modifier
|
||||
NONE = 0,
|
||||
/// Ctrl + Shift
|
||||
CTRL_SHIFT = 10,
|
||||
/// Alt + Shift
|
||||
ALT_SHIFT = 6,
|
||||
/// Ctrl + Alt
|
||||
CTRL_ALT = 12,
|
||||
/// Ctrl + Alt + Shift
|
||||
CTRL_ALT_SHIFT = 14,
|
||||
}
|
||||
|
||||
/// The number of times one command should be repeated.
|
||||
pub type RepeatCount = usize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Keybinding {
|
||||
key: KeyPress,
|
||||
key: KeyCode,
|
||||
modifiers: Option<NuModifiers>,
|
||||
binding: Cmd,
|
||||
}
|
||||
|
||||
type Keybindings = Vec<Keybinding>;
|
||||
|
||||
pub(crate) fn keybinding_path() -> Result<std::path::PathBuf, nu_errors::ShellError> {
|
||||
nu_data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml")))
|
||||
}
|
||||
|
||||
pub(crate) fn load_keybindings(
|
||||
rl: &mut rustyline::Editor<crate::shell::Helper>,
|
||||
) -> Result<(), nu_errors::ShellError> {
|
||||
let filename = keybinding_path()?;
|
||||
let filename = nu_data::keybinding::keybinding_path()?;
|
||||
let contents = std::fs::read_to_string(filename);
|
||||
|
||||
// Silently fail if there is no file there
|
||||
if let Ok(contents) = contents {
|
||||
let keybindings: Keybindings = serde_yaml::from_str(&contents)?;
|
||||
|
||||
// eprint!("{}{}{}", keybindings.key, keybindings.mo);
|
||||
for keybinding in keybindings.into_iter() {
|
||||
let (k, b) = convert_keybinding(keybinding);
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
#![recursion_limit = "2048"]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate indexmap;
|
||||
|
||||
#[macro_use]
|
||||
mod prelude;
|
||||
|
||||
@ -14,50 +10,27 @@ extern crate quickcheck;
|
||||
extern crate quickcheck_macros;
|
||||
|
||||
mod cli;
|
||||
mod commands;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod completion;
|
||||
mod deserializer;
|
||||
mod documentation;
|
||||
mod env;
|
||||
mod evaluate;
|
||||
mod evaluation_context;
|
||||
mod format;
|
||||
mod futures;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod git;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
mod path;
|
||||
mod plugin;
|
||||
pub mod script;
|
||||
mod line_editor;
|
||||
mod shell;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
mod examples;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub use crate::cli::cli;
|
||||
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
pub use crate::commands::classified::block::run_block;
|
||||
pub use crate::commands::command::{
|
||||
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,
|
||||
};
|
||||
pub use crate::cli::{NuScript, Options};
|
||||
|
||||
pub use crate::commands::default_context::create_default_context;
|
||||
pub use crate::commands::help::get_help;
|
||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||
pub use crate::env::host::BasicHost;
|
||||
pub use crate::evaluation_context::EvaluationContext;
|
||||
pub use crate::prelude::ToOutputStream;
|
||||
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;
|
||||
|
||||
|
273
crates/nu-cli/src/line_editor.rs
Normal file
273
crates/nu-cli/src/line_editor.rs
Normal file
@ -0,0 +1,273 @@
|
||||
use nu_engine::EvaluationContext;
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::prelude::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_engine::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::keybinding::{convert_keyevent, KeyCode};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
line_buffer::LineBuffer,
|
||||
At, Cmd, ConditionalEventHandler, Editor, EventHandler, Modifiers, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
outln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
struct PartialCompleteHintHandler;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
impl ConditionalEventHandler for PartialCompleteHintHandler {
|
||||
fn handle(
|
||||
&self,
|
||||
_evt: &rustyline::Event,
|
||||
_n: rustyline::RepeatCount,
|
||||
_positive: bool,
|
||||
ctx: &rustyline::EventContext,
|
||||
) -> Option<Cmd> {
|
||||
Some(match ctx.hint_text() {
|
||||
Some(hint_text) if ctx.pos() == ctx.line().len() => {
|
||||
let mut line_buffer = LineBuffer::with_capacity(hint_text.len());
|
||||
line_buffer.update(hint_text, 0);
|
||||
line_buffer.move_to_next_word(At::AfterEnd, Word::Vi, 1);
|
||||
|
||||
let text = hint_text[0..line_buffer.pos()].to_string();
|
||||
|
||||
Cmd::Insert(1, text)
|
||||
}
|
||||
_ => Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
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
|
||||
//M modifier, E KeyEvent, K KeyCode
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Left, Some(Modifiers::CTRL)),
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Right, Some(Modifiers::CTRL)),
|
||||
EventHandler::Conditional(Box::new(PartialCompleteHintHandler)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::BracketedPasteStart, None),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn nu_line_editor_helper(
|
||||
context: &EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn rustyline_hinter(
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -8,47 +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_stream {
|
||||
(target: $target:tt, $desc:tt = $expr:expr) => {{
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let objects = $expr.inspect(move |o| {
|
||||
trace!(
|
||||
target: $target,
|
||||
"{} = {}",
|
||||
$desc,
|
||||
nu_source::PrettyDebug::plain_string(o, 70)
|
||||
);
|
||||
});
|
||||
|
||||
nu_stream::InputStream::from_stream(objects.boxed())
|
||||
} else {
|
||||
$expr
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[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,
|
||||
@ -68,105 +31,29 @@ macro_rules! trace_out_stream {
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use nu_protocol::{errln, out, outln, row};
|
||||
use nu_source::HasFallibleSpan;
|
||||
|
||||
pub(crate) use crate::commands::command::{CommandArgs, RawCommandArgs, RunnableContext};
|
||||
pub(crate) use crate::commands::Example;
|
||||
pub(crate) use crate::evaluation_context::EvaluationContext;
|
||||
pub(crate) use nu_data::config;
|
||||
pub(crate) use nu_data::value;
|
||||
// pub(crate) use crate::env::host::handle_unexpected;
|
||||
pub(crate) use crate::env::Host;
|
||||
pub(crate) use crate::evaluate::scope::Scope;
|
||||
|
||||
pub(crate) use crate::shell::filesystem_shell::FilesystemShell;
|
||||
pub(crate) use crate::shell::help_shell::HelpShell;
|
||||
pub(crate) use crate::shell::shell_manager::ShellManager;
|
||||
pub(crate) use crate::shell::value_shell::ValueShell;
|
||||
pub(crate) use bigdecimal::BigDecimal;
|
||||
pub(crate) use futures::stream::BoxStream;
|
||||
pub(crate) use futures::{Stream, StreamExt};
|
||||
pub(crate) use nu_engine::Host;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_errors::ShellError;
|
||||
pub(crate) use nu_parser::ParserScope;
|
||||
pub(crate) use nu_source::{
|
||||
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
|
||||
TaggedItem, Text,
|
||||
};
|
||||
pub(crate) use nu_stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_protocol::outln;
|
||||
pub(crate) use nu_stream::ActionStream;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_value_ext::ValueExt;
|
||||
pub(crate) use num_bigint::BigInt;
|
||||
pub(crate) use num_traits::cast::ToPrimitive;
|
||||
pub(crate) use serde::Deserialize;
|
||||
pub(crate) use std::collections::VecDeque;
|
||||
pub(crate) use std::future::Future;
|
||||
pub(crate) use std::sync::atomic::{AtomicBool, Ordering};
|
||||
pub(crate) use std::sync::Arc;
|
||||
|
||||
pub(crate) use async_trait::async_trait;
|
||||
pub(crate) use indexmap::{indexmap, IndexMap};
|
||||
pub(crate) use itertools::Itertools;
|
||||
#[allow(unused_imports)]
|
||||
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(),
|
||||
fn from_input_stream(self) -> ActionStream {
|
||||
ActionStream {
|
||||
values: Box::new(self.map(nu_protocol::ReturnSuccess::value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToInputStream {
|
||||
fn to_input_stream(self) -> InputStream;
|
||||
}
|
||||
|
||||
impl<T, U> ToInputStream for T
|
||||
where
|
||||
T: Stream<Item = U> + Send + 'static,
|
||||
U: Into<Result<nu_protocol::Value, nu_errors::ShellError>>,
|
||||
{
|
||||
fn to_input_stream(self) -> InputStream {
|
||||
InputStream::from_stream(self.map(|item| match item.into() {
|
||||
Ok(result) => result,
|
||||
Err(err) => match HasFallibleSpan::maybe_span(&err) {
|
||||
Some(span) => nu_protocol::UntaggedValue::Error(err).into_value(span),
|
||||
None => nu_protocol::UntaggedValue::Error(err).into_untagged_value(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToOutputStream {
|
||||
fn to_output_stream(self) -> OutputStream;
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Interruptible<V> {
|
||||
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V>;
|
||||
}
|
||||
|
||||
impl<S, V> Interruptible<V> for S
|
||||
where
|
||||
S: Stream<Item = V> + Send + 'static,
|
||||
{
|
||||
fn interruptible(self, ctrl_c: Arc<AtomicBool>) -> InterruptibleStream<V> {
|
||||
InterruptibleStream::new(self, ctrl_c)
|
||||
}
|
||||
}
|
||||
|
@ -1,274 +0,0 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
use log::{debug, trace};
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LineResult {
|
||||
Success(String),
|
||||
Error(String, ShellError),
|
||||
Break,
|
||||
CtrlC,
|
||||
CtrlD,
|
||||
ClearHistory,
|
||||
}
|
||||
|
||||
fn chomp_newline(s: &str) -> &str {
|
||||
if let Some(s) = s.strip_suffix('\n') {
|
||||
s
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
if let Some(diag) = err.into_diagnostic() {
|
||||
let source = source.to_string();
|
||||
let mut files = codespan_reporting::files::SimpleFiles::new();
|
||||
files.add("shell", source);
|
||||
|
||||
let writer = codespan_reporting::term::termcolor::StandardStream::stderr(
|
||||
codespan_reporting::term::termcolor::ColorChoice::Always,
|
||||
);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let _ = std::panic::catch_unwind(move || {
|
||||
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
|
||||
pub async fn process_script(
|
||||
script_text: &str,
|
||||
ctx: &EvaluationContext,
|
||||
redirect_stdin: bool,
|
||||
span_offset: usize,
|
||||
cli_mode: bool,
|
||||
) -> LineResult {
|
||||
if script_text.trim() == "" {
|
||||
LineResult::Success(script_text.to_string())
|
||||
} else {
|
||||
let line = chomp_newline(script_text);
|
||||
|
||||
let (block, err) = nu_parser::parse(&line, span_offset, &ctx.scope);
|
||||
|
||||
debug!("{:#?}", block);
|
||||
//println!("{:#?}", pipeline);
|
||||
|
||||
if let Some(failure) = err {
|
||||
return LineResult::Error(line.to_string(), failure.into());
|
||||
}
|
||||
|
||||
// There's a special case to check before we process the pipeline:
|
||||
// If we're giving a path by itself
|
||||
// ...and it's not a command in the path
|
||||
// ...and it doesn't have any arguments
|
||||
// ...and we're in the CLI
|
||||
// ...then change to this directory
|
||||
if cli_mode
|
||||
&& block.block.len() == 1
|
||||
&& block.block[0].pipelines.len() == 1
|
||||
&& block.block[0].pipelines[0].list.len() == 1
|
||||
{
|
||||
if let ClassifiedCommand::Internal(InternalCommand {
|
||||
ref name, ref args, ..
|
||||
}) = block.block[0].pipelines[0].list[0]
|
||||
{
|
||||
let internal_name = name;
|
||||
let name = args
|
||||
.positional
|
||||
.as_ref()
|
||||
.and_then(|positionals| {
|
||||
positionals.get(0).map(|e| {
|
||||
if let Expression::Literal(Literal::String(ref s)) = e.expr {
|
||||
&s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or("");
|
||||
|
||||
if internal_name == "run_external"
|
||||
&& args
|
||||
.positional
|
||||
.as_ref()
|
||||
.map(|ref v| v.len() == 1)
|
||||
.unwrap_or(true)
|
||||
&& args
|
||||
.named
|
||||
.as_ref()
|
||||
.map(NamedArguments::is_empty)
|
||||
.unwrap_or(true)
|
||||
&& canonicalize(ctx.shell_manager.path(), name).is_ok()
|
||||
&& Path::new(&name).is_dir()
|
||||
&& !crate::commands::classified::external::did_find_command(&name)
|
||||
{
|
||||
// Here we work differently if we're in Windows because of the expected Windows behavior
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if name.ends_with(':') {
|
||||
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
|
||||
// But first, we need to save where we are now
|
||||
let current_path = ctx.shell_manager.path();
|
||||
|
||||
let split_path: Vec<_> = current_path.split(':').collect();
|
||||
if split_path.len() > 1 {
|
||||
ctx.windows_drives_previous_cwd
|
||||
.lock()
|
||||
.insert(split_path[0].to_string(), current_path);
|
||||
}
|
||||
|
||||
let name = name.to_uppercase();
|
||||
let new_drive: Vec<_> = name.split(':').collect();
|
||||
|
||||
if let Some(val) =
|
||||
ctx.windows_drives_previous_cwd.lock().get(new_drive[0])
|
||||
{
|
||||
ctx.shell_manager.set_path(val.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
} else {
|
||||
ctx.shell_manager
|
||||
.set_path(format!("{}\\", name.to_string()));
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
} else {
|
||||
ctx.shell_manager.set_path(name.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
ctx.shell_manager.set_path(name.to_string());
|
||||
return LineResult::Success(line.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input_stream = if redirect_stdin {
|
||||
let file = futures::io::AllowStdIo::new(std::io::stdin());
|
||||
let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| {
|
||||
if let Ok(line) = line {
|
||||
let primitive = match line {
|
||||
StringOrBinary::String(s) => Primitive::String(s),
|
||||
StringOrBinary::Binary(b) => Primitive::Binary(b.into_iter().collect()),
|
||||
};
|
||||
|
||||
Ok(Value {
|
||||
value: UntaggedValue::Primitive(primitive),
|
||||
tag: Tag::unknown(),
|
||||
})
|
||||
} else {
|
||||
panic!("Internal error: could not read lines of text from stdin")
|
||||
}
|
||||
});
|
||||
stream.to_input_stream()
|
||||
} else {
|
||||
InputStream::empty()
|
||||
};
|
||||
|
||||
trace!("{:#?}", block);
|
||||
let env = ctx.get_env();
|
||||
|
||||
ctx.scope.add_env(env);
|
||||
let result = run_block(&block, ctx, input_stream).await;
|
||||
|
||||
match result {
|
||||
Ok(input) => {
|
||||
// Running a pipeline gives us back a stream that we can then
|
||||
// work through. At the top level, we just want to pull on the
|
||||
// values to compute them.
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let context = RunnableContext {
|
||||
input,
|
||||
shell_manager: ctx.shell_manager.clone(),
|
||||
host: ctx.host.clone(),
|
||||
ctrl_c: ctx.ctrl_c.clone(),
|
||||
current_errors: ctx.current_errors.clone(),
|
||||
scope: ctx.scope.clone(),
|
||||
name: Tag::unknown(),
|
||||
};
|
||||
|
||||
if let Ok(mut output_stream) =
|
||||
crate::commands::autoview::command::autoview(context).await
|
||||
{
|
||||
loop {
|
||||
match output_stream.try_next().await {
|
||||
Ok(Some(ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Error(e),
|
||||
..
|
||||
}))) => return LineResult::Error(line.to_string(), e),
|
||||
Ok(Some(_item)) => {
|
||||
if ctx.ctrl_c.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(e) => return LineResult::Error(line.to_string(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Success(line.to_string())
|
||||
}
|
||||
Err(err) => LineResult::Error(line.to_string(), err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_script_standalone(
|
||||
script_text: String,
|
||||
redirect_stdin: bool,
|
||||
context: &EvaluationContext,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_script(&script_text, context, redirect_stdin, 0, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -2,15 +2,8 @@
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod completer;
|
||||
pub(crate) mod filesystem_shell;
|
||||
pub(crate) mod help_shell;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod helper;
|
||||
pub(crate) mod painter;
|
||||
pub(crate) mod palette;
|
||||
pub(crate) mod shell;
|
||||
pub(crate) mod shell_manager;
|
||||
pub(crate) mod value_shell;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) use helper::Helper;
|
||||
|
@ -4,7 +4,7 @@ use crate::completion::matchers;
|
||||
use crate::completion::matchers::Matcher;
|
||||
use crate::completion::path::{PathCompleter, PathSuggestion};
|
||||
use crate::completion::{self, Completer, Suggestion};
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_source::Tag;
|
||||
|
||||
@ -44,6 +44,10 @@ impl NuCompleter {
|
||||
let matcher = matcher.as_str();
|
||||
let matcher: &dyn Matcher = match matcher {
|
||||
"case-insensitive" => &matchers::case_insensitive::Matcher,
|
||||
"case-sensitive" => &matchers::case_sensitive::Matcher,
|
||||
#[cfg(target_os = "windows")]
|
||||
_ => &matchers::case_insensitive::Matcher,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
_ => &matchers::case_sensitive::Matcher,
|
||||
};
|
||||
|
||||
@ -134,7 +138,7 @@ fn requote(orig_value: String) -> String {
|
||||
let mut quotes = vec!['"', '\'', '`'];
|
||||
let mut should_quote = false;
|
||||
for c in value.chars() {
|
||||
if c.is_whitespace() {
|
||||
if c.is_whitespace() || c == '#' {
|
||||
should_quote = true;
|
||||
} else if let Some(index) = quotes.iter().position(|q| *q == c) {
|
||||
should_quote = true;
|
||||
@ -145,7 +149,7 @@ fn requote(orig_value: String) -> String {
|
||||
if should_quote {
|
||||
if quotes.is_empty() {
|
||||
// TODO we don't really have an escape character, so there isn't a great option right
|
||||
// now. One possibility is `{{$(char backtick)}}`
|
||||
// now. One possibility is `{{(char backtick)}}`
|
||||
value.to_string()
|
||||
} else {
|
||||
let quote = quotes[0];
|
||||
|
@ -1,231 +0,0 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::help::command_dict;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::move_::mv::Arguments as MvArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::commands::classified::maybe_text_codec::StringOrBinary;
|
||||
use encoding_rs::Encoding;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HelpShell {
|
||||
pub(crate) path: String,
|
||||
pub(crate) value: Value,
|
||||
}
|
||||
|
||||
impl HelpShell {
|
||||
pub fn index(scope: &Scope) -> Result<HelpShell, ShellError> {
|
||||
let mut cmds = TaggedDictBuilder::new(Tag::unknown());
|
||||
let mut specs = Vec::new();
|
||||
|
||||
for cmd in scope.get_command_names() {
|
||||
if let Some(cmd_value) = scope.get_command(&cmd) {
|
||||
let mut spec = TaggedDictBuilder::new(Tag::unknown());
|
||||
let value = command_dict(cmd_value, Tag::unknown());
|
||||
|
||||
spec.insert_untagged("name", cmd);
|
||||
spec.insert_untagged(
|
||||
"description",
|
||||
value
|
||||
.get_data_by_key("usage".spanned_unknown())
|
||||
.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(
|
||||
"Internal error: expected to find usage",
|
||||
)
|
||||
})?
|
||||
.as_string()?,
|
||||
);
|
||||
spec.insert_value("details", value);
|
||||
|
||||
specs.push(spec.into_value());
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
cmds.insert_untagged("help", UntaggedValue::Table(specs));
|
||||
|
||||
Ok(HelpShell {
|
||||
path: "/help".to_string(),
|
||||
value: cmds.into_value(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_command(cmd: Value, scope: &Scope) -> Result<HelpShell, ShellError> {
|
||||
let mut sh = HelpShell::index(scope)?;
|
||||
|
||||
if let Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(name)),
|
||||
..
|
||||
} = cmd
|
||||
{
|
||||
sh.set_path(format!("/help/{:}/details", name));
|
||||
}
|
||||
|
||||
Ok(sh)
|
||||
}
|
||||
|
||||
fn commands(&self) -> VecDeque<Value> {
|
||||
let mut cmds = VecDeque::new();
|
||||
let full_path = PathBuf::from(&self.path);
|
||||
|
||||
let mut viewed = self.value.clone();
|
||||
let sep_string = std::path::MAIN_SEPARATOR.to_string();
|
||||
let sep = OsStr::new(&sep_string);
|
||||
|
||||
for p in full_path.iter() {
|
||||
match p {
|
||||
x if x == sep => {}
|
||||
step => {
|
||||
let step: &str = &step.to_string_lossy().to_string();
|
||||
let value = viewed.get_data_by_key(step.spanned_unknown());
|
||||
if let Some(v) = value {
|
||||
viewed = v.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match viewed {
|
||||
Value {
|
||||
value: UntaggedValue::Table(l),
|
||||
..
|
||||
} => {
|
||||
for item in l {
|
||||
cmds.push_back(item.clone());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
cmds.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
cmds
|
||||
}
|
||||
}
|
||||
|
||||
impl Shell for HelpShell {
|
||||
fn name(&self) -> String {
|
||||
let anchor_name = self.value.anchor_name();
|
||||
|
||||
match anchor_name {
|
||||
Some(x) => format!("{{{}}}", x),
|
||||
None => format!("<{}>", self.value.type_name()),
|
||||
}
|
||||
}
|
||||
|
||||
fn homedir(&self) -> Option<PathBuf> {
|
||||
#[cfg(feature = "dirs")]
|
||||
{
|
||||
dirs::home_dir()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "dirs"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn pwd(&self, _: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn set_path(&mut self, path: String) {
|
||||
let _ = std::env::set_current_dir(&path);
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
fn ls(
|
||||
&self,
|
||||
_args: LsArgs,
|
||||
_name: Tag,
|
||||
_ctrl_c: Arc<AtomicBool>,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let output = self
|
||||
.commands()
|
||||
.into_iter()
|
||||
.map(ReturnSuccess::value)
|
||||
.collect::<VecDeque<_>>();
|
||||
Ok(output.into())
|
||||
}
|
||||
|
||||
fn cd(&self, args: CdArgs, _name: Tag) -> Result<OutputStream, ShellError> {
|
||||
let path = match args.path {
|
||||
None => "/".to_string(),
|
||||
Some(v) => {
|
||||
let Tagged { item: target, .. } = v;
|
||||
let mut cwd = PathBuf::from(&self.path);
|
||||
|
||||
if target == PathBuf::from("..") {
|
||||
cwd.pop();
|
||||
} else {
|
||||
match target.to_str() {
|
||||
Some(target) => match target.chars().next() {
|
||||
Some(x) if x == '/' => cwd = PathBuf::from(target),
|
||||
_ => cwd.push(target),
|
||||
},
|
||||
None => cwd.push(target),
|
||||
}
|
||||
}
|
||||
cwd.to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
stream.push_back(ReturnSuccess::change_cwd(path));
|
||||
Ok(stream.into())
|
||||
}
|
||||
|
||||
fn cp(&self, _args: CopyArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn mv(&self, _args: MvArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn mkdir(&self, _args: MkdirArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn rm(&self, _args: RemoveArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::empty())
|
||||
}
|
||||
|
||||
fn open(
|
||||
&self,
|
||||
_path: &PathBuf,
|
||||
_name: Span,
|
||||
_with_encoding: Option<&'static Encoding>,
|
||||
) -> Result<BoxStream<'static, Result<StringOrBinary, ShellError>>, ShellError> {
|
||||
Err(ShellError::unimplemented(
|
||||
"open on help shell is not supported",
|
||||
))
|
||||
}
|
||||
|
||||
fn save(
|
||||
&mut self,
|
||||
_path: &PathBuf,
|
||||
_contents: &[u8],
|
||||
_name: Span,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Err(ShellError::unimplemented(
|
||||
"save on help shell is not supported",
|
||||
))
|
||||
}
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
use nu_source::{Tag, Tagged};
|
||||
|
||||
use crate::completion;
|
||||
use crate::evaluation_context::EvaluationContext;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use crate::shell::painter::Painter;
|
||||
use crate::shell::palette::DefaultPalette;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub struct Helper {
|
||||
completer: NuCompleter,
|
||||
@ -61,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))
|
||||
}
|
||||
@ -82,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> {
|
||||
@ -123,7 +121,7 @@ impl rustyline::validate::Validator for NuValidator {
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::group(tokens);
|
||||
let (_, err) = nu_parser::parse_block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
@ -148,3 +146,51 @@ fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::EvaluationContext;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), &replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::classified::maybe_text_codec::StringOrBinary;
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::move_::mv::Arguments as MvArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::prelude::*;
|
||||
use nu_stream::OutputStream;
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
use nu_errors::ShellError;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait Shell: std::fmt::Debug {
|
||||
fn name(&self) -> String;
|
||||
fn homedir(&self) -> Option<PathBuf>;
|
||||
|
||||
fn ls(
|
||||
&self,
|
||||
args: LsArgs,
|
||||
name: Tag,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
fn cd(&self, args: CdArgs, name: Tag) -> Result<OutputStream, ShellError>;
|
||||
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn mkdir(&self, args: MkdirArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn mv(&self, args: MvArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn rm(&self, args: RemoveArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||
fn path(&self) -> String;
|
||||
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn set_path(&mut self, path: String);
|
||||
fn open(
|
||||
&self,
|
||||
path: &PathBuf,
|
||||
name: Span,
|
||||
with_encoding: Option<&'static Encoding>,
|
||||
) -> Result<BoxStream<'static, Result<StringOrBinary, ShellError>>, ShellError>;
|
||||
fn save(
|
||||
&mut self,
|
||||
path: &PathBuf,
|
||||
contents: &[u8],
|
||||
name: Span,
|
||||
) -> Result<OutputStream, ShellError>;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::prelude::*;
|
||||
use itertools::{merge_join_by, EitherOrBoth, Itertools};
|
||||
use lazy_static::lazy_static;
|
||||
use log::trace;
|
||||
use nu_engine::Scope;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_protocol::{
|
||||
@ -14,9 +16,6 @@ use nu_source::Span;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
use itertools::{merge_join_by, EitherOrBoth, Itertools};
|
||||
use log::trace;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VarDeclaration {
|
||||
pub name: String,
|
||||
@ -82,29 +81,29 @@ lazy_static! {
|
||||
static ref MULT_DIV_LOOKUP_TABLE: HashMap<(Operator, BinarySide, SyntaxShape), Vec<SyntaxShape>> = {
|
||||
vec![
|
||||
((Operator::Divide, BinarySide::Left, SyntaxShape::Number), // expr => possible var shapes
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var / number => Unit, Int, Number
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Number, SyntaxShape::Int]), //$var / number => Unit, Int, Number
|
||||
((Operator::Divide, BinarySide::Left, SyntaxShape::Int),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var / int => Unit, Int, Number
|
||||
((Operator::Divide, BinarySide::Left, SyntaxShape::Unit),
|
||||
vec![SyntaxShape::Unit]), //$var / unit => Unit
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Number, SyntaxShape::Int]), //$var / int => Unit, Int, Number
|
||||
((Operator::Divide, BinarySide::Left, SyntaxShape::Filesize),
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Filesize]), //$var / unit => Unit
|
||||
((Operator::Divide, BinarySide::Right, SyntaxShape::Number),
|
||||
vec![SyntaxShape::Number, SyntaxShape::Int]), //number / $var => Int, Number
|
||||
((Operator::Divide, BinarySide::Right, SyntaxShape::Int),
|
||||
vec![SyntaxShape::Number, SyntaxShape::Int]), //int / $var => Int, Number
|
||||
((Operator::Divide, BinarySide::Right, SyntaxShape::Unit),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //unit / $var => unit, int, number
|
||||
((Operator::Divide, BinarySide::Right, SyntaxShape::Filesize),
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //unit / $var => unit, int, number
|
||||
|
||||
((Operator::Multiply, BinarySide::Left, SyntaxShape::Number),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var * number => Unit, Int, Number
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //$var * number => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Left, SyntaxShape::Int),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var * int => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Left, SyntaxShape::Unit),
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //$var * int => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Left, SyntaxShape::Filesize),
|
||||
vec![SyntaxShape::Int, SyntaxShape::Number]), //$var * unit => int, number //TODO this changes as soon as more complex units arrive
|
||||
((Operator::Multiply, BinarySide::Right, SyntaxShape::Number),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //number * $var => Unit, Int, Number
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //number * $var => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Right, SyntaxShape::Int),
|
||||
vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //int * $var => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Right, SyntaxShape::Unit),
|
||||
vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //int * $var => Unit, Int, Number
|
||||
((Operator::Multiply, BinarySide::Right, SyntaxShape::Filesize),
|
||||
vec![SyntaxShape::Int, SyntaxShape::Number]), //unit * $var => int, number //TODO this changes as soon as more complex units arrive
|
||||
].into_iter().collect()
|
||||
};
|
||||
@ -212,7 +211,7 @@ fn get_result_shape_of(
|
||||
l_shape: SyntaxShape,
|
||||
op_expr: &SpannedExpression,
|
||||
r_shape: SyntaxShape,
|
||||
) -> Result<SyntaxShape, ShellError> {
|
||||
) -> SyntaxShape {
|
||||
let op = match op_expr.expr {
|
||||
Expression::Literal(Literal::Operator(op)) => op,
|
||||
_ => unreachable!("Passing anything but the op expr is invalid"),
|
||||
@ -221,7 +220,7 @@ fn get_result_shape_of(
|
||||
//There is some code for that in the evaluator already.
|
||||
//One might reuse it.
|
||||
//For now we ignore this issue
|
||||
Ok(match op {
|
||||
match op {
|
||||
Operator::Equal
|
||||
| Operator::NotEqual
|
||||
| Operator::LessThan
|
||||
@ -242,8 +241,8 @@ fn get_result_shape_of(
|
||||
l_shape
|
||||
}
|
||||
Operator::Multiply => {
|
||||
if l_shape == SyntaxShape::Unit || r_shape == SyntaxShape::Unit {
|
||||
SyntaxShape::Unit
|
||||
if l_shape == SyntaxShape::Duration || r_shape == SyntaxShape::Duration {
|
||||
SyntaxShape::Duration
|
||||
} else {
|
||||
SyntaxShape::Number
|
||||
}
|
||||
@ -251,14 +250,15 @@ fn get_result_shape_of(
|
||||
Operator::Divide => {
|
||||
if l_shape == r_shape {
|
||||
SyntaxShape::Number
|
||||
} else if l_shape == SyntaxShape::Unit {
|
||||
} else if l_shape == SyntaxShape::Duration {
|
||||
l_shape
|
||||
} else {
|
||||
SyntaxShape::Number
|
||||
}
|
||||
}
|
||||
Operator::Modulo => SyntaxShape::Number,
|
||||
})
|
||||
Operator::Pow => SyntaxShape::Number,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shape_of_expr(expr: &SpannedExpression) -> Option<SyntaxShape> {
|
||||
@ -273,10 +273,11 @@ fn get_shape_of_expr(expr: &SpannedExpression) -> Option<SyntaxShape> {
|
||||
Expression::Literal(literal) => {
|
||||
match literal {
|
||||
nu_protocol::hir::Literal::Number(number) => match number {
|
||||
nu_protocol::hir::Number::BigInt(_) => Some(SyntaxShape::Int),
|
||||
nu_protocol::hir::Number::Int(_) => Some(SyntaxShape::Int),
|
||||
nu_protocol::hir::Number::Decimal(_) => Some(SyntaxShape::Number),
|
||||
},
|
||||
nu_protocol::hir::Literal::Size(_, _) => Some(SyntaxShape::Unit),
|
||||
nu_protocol::hir::Literal::Size(_, _) => Some(SyntaxShape::Duration),
|
||||
nu_protocol::hir::Literal::String(_) => Some(SyntaxShape::String),
|
||||
//Rest should have failed at parsing stage?
|
||||
nu_protocol::hir::Literal::GlobPattern(_) => Some(SyntaxShape::String),
|
||||
@ -285,7 +286,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),
|
||||
@ -295,8 +296,8 @@ fn get_shape_of_expr(expr: &SpannedExpression) -> Option<SyntaxShape> {
|
||||
Expression::List(_) => Some(SyntaxShape::Table),
|
||||
Expression::Boolean(_) => Some(SyntaxShape::String),
|
||||
|
||||
Expression::Path(_) => Some(SyntaxShape::ColumnPath),
|
||||
Expression::FilePath(_) => Some(SyntaxShape::Path),
|
||||
Expression::FullColumnPath(_) => Some(SyntaxShape::ColumnPath),
|
||||
Expression::FilePath(_) => Some(SyntaxShape::FilePath),
|
||||
Expression::Block(_) => Some(SyntaxShape::Block),
|
||||
Expression::ExternalCommand(_) => Some(SyntaxShape::String),
|
||||
Expression::Table(_, _) => Some(SyntaxShape::Table),
|
||||
@ -334,7 +335,7 @@ fn get_result_shape_of_math_expr(
|
||||
//match lhs, rhs
|
||||
match (shapes[0], shapes[1]) {
|
||||
(None, None) | (None, _) | (_, None) => Ok(None),
|
||||
(Some(left), Some(right)) => get_result_shape_of(left, &bin.op, right).map(Some),
|
||||
(Some(left), Some(right)) => Ok(Some(get_result_shape_of(left, &bin.op, right))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,17 +378,14 @@ impl VarSyntaxShapeDeductor {
|
||||
.iter()
|
||||
.map(|decl| {
|
||||
let usage: VarUsage = decl.into();
|
||||
let deduction = match deducer.inferences.get(&usage) {
|
||||
Some(vec) => Some(vec.clone()),
|
||||
None => None,
|
||||
};
|
||||
let deduction = deducer.inferences.get(&usage).cloned();
|
||||
(decl.clone(), deduction)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
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 +395,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 +427,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 +441,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 +457,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 +498,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 +532,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");
|
||||
Expression::FullColumnPath(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 +558,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 +583,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 +736,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 {
|
||||
@ -792,7 +790,7 @@ impl VarSyntaxShapeDeductor {
|
||||
| Expression::Binary(_)
|
||||
| Expression::Range(_)
|
||||
| Expression::Block(_)
|
||||
| Expression::Path(_)
|
||||
| Expression::FullColumnPath(_)
|
||||
| Expression::FilePath(_)
|
||||
| Expression::ExternalCommand(_)
|
||||
| Expression::Command
|
||||
@ -845,12 +843,21 @@ impl VarSyntaxShapeDeductor {
|
||||
),
|
||||
)?;
|
||||
}
|
||||
SyntaxShape::Unit => {
|
||||
SyntaxShape::Duration => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&[SyntaxShape::Unit],
|
||||
&[SyntaxShape::Duration],
|
||||
),
|
||||
)?;
|
||||
}
|
||||
SyntaxShape::Filesize => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&[SyntaxShape::Filesize],
|
||||
),
|
||||
)?;
|
||||
}
|
||||
@ -861,7 +868,7 @@ impl VarSyntaxShapeDeductor {
|
||||
}
|
||||
}
|
||||
}
|
||||
Operator::Multiply | Operator::Divide => {
|
||||
Operator::Multiply | Operator::Divide | Operator::Pow => {
|
||||
if let Some(shape) = self.get_shape_of_binary_arg_or_insert_dependency(
|
||||
(var, expr),
|
||||
bin_spanned,
|
||||
@ -1023,7 +1030,7 @@ impl VarSyntaxShapeDeductor {
|
||||
Some(combination)
|
||||
}
|
||||
})
|
||||
.filter_map(|elem| elem)
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
//No any's intersection of both is result
|
||||
@ -1044,7 +1051,7 @@ impl VarSyntaxShapeDeductor {
|
||||
Some(combination)
|
||||
}
|
||||
})
|
||||
.filter_map(|elem| elem)
|
||||
.flatten()
|
||||
.collect();
|
||||
if intersection.is_empty() {
|
||||
//TODO pass all labels somehow
|
||||
|
@ -1,29 +0,0 @@
|
||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn adds_a_row_to_the_end() {
|
||||
Playground::setup("append_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"los_tres_caballeros.txt",
|
||||
r#"
|
||||
Andrés N. Robalino
|
||||
Jonathan Turner
|
||||
Yehuda Katz
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open los_tres_caballeros.txt
|
||||
| lines
|
||||
| append "pollo loco"
|
||||
| nth 3
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "pollo loco");
|
||||
})
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,23 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn drop_rows() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum "#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_more_rows_than_table_has() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
date | drop 50 | count
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0");
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn echo_range_is_lazy() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo 1..10000000000 | first 3 | to json
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[1,2,3]");
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn returns_extension_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "bacon." | path extension
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_extension_with_dot_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "bacon." | path extension -r .egg
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "bacon..egg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_extension_of_empty_path() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "" | path extension -r egg
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use super::join_path_sep;
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/eggs/."
|
||||
| path filestem
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "eggs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/eggs/.."
|
||||
| path filestem
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_prefix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -s ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_prefix_and_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "" -s ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_with_wrong_prefix_and_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "bacon" -s "eggs"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_filestem_stripped_to_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "spam" -s "txt" -r ".eggs."
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["menu", "spam.eggs.txt"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn regular_columns() {
|
||||
Playground::setup("select_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"los_tres_caballeros.csv",
|
||||
r#"
|
||||
first_name,last_name,rusty_at,type
|
||||
Andrés,Robalino,10/11/2013,A
|
||||
Jonathan,Turner,10/12/2013,B
|
||||
Yehuda,Katz,10/11/2013,A
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open los_tres_caballeros.csv
|
||||
| select rusty_at last_name
|
||||
| nth 0
|
||||
| get last_name
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "Robalino");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_nested_columns() {
|
||||
Playground::setup("select_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"los_tres_caballeros.json",
|
||||
r#"
|
||||
{
|
||||
"nu": {
|
||||
"committers": [
|
||||
{"name": "Andrés N. Robalino"},
|
||||
{"name": "Jonathan Turner"},
|
||||
{"name": "Yehuda Katz"}
|
||||
],
|
||||
"releases": [
|
||||
{"version": "0.2"}
|
||||
{"version": "0.8"},
|
||||
{"version": "0.9999999"}
|
||||
],
|
||||
"0xATYKARNU": [
|
||||
["Th", "e", " "],
|
||||
["BIG", " ", "UnO"],
|
||||
["punto", "cero"]
|
||||
]
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open los_tres_caballeros.json
|
||||
| select nu."0xATYKARNU" nu.committers.name nu.releases.version
|
||||
| where "nu.releases.version" > "0.8"
|
||||
| get "nu.releases.version"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "0.9999999");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_if_given_unknown_column_name_is_missing() {
|
||||
Playground::setup("select_test_3", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"los_tres_caballeros.csv",
|
||||
r#"
|
||||
first_name,last_name,rusty_at,type
|
||||
Andrés,Robalino,10/11/2013,A
|
||||
Jonathan,Turner,10/12/2013,B
|
||||
Yehuda,Katz,10/11/2013,A
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open los_tres_caballeros.csv
|
||||
| select rrusty_at first_name
|
||||
| count
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_names_with_spaces() {
|
||||
Playground::setup("select_test_4", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"test_data.csv",
|
||||
r#"
|
||||
first name,last name
|
||||
Jonathan,Turner
|
||||
Jonathan,Arns
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open test_data.csv
|
||||
| select "last name"
|
||||
| to json
|
||||
"#
|
||||
));
|
||||
|
||||
let expected_output = r#"[{"last name":"Turner"},{"last name":"Arns"}]"#;
|
||||
assert_eq!(actual.out, expected_output);
|
||||
})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user