forked from extern/nushell
Compare commits
455 Commits
Author | SHA1 | Date | |
---|---|---|---|
17a265b197 | |||
3fabc8e1e6 | |||
517ef7cde7 | |||
ad14b763f9 | |||
f74694d5a3 | |||
1ea39abcff | |||
7402589775 | |||
e0cd5a714a | |||
c6eea5de6b | |||
809416e3f0 | |||
040d812343 | |||
72465e6724 | |||
ab480856a5 | |||
6ae497eedc | |||
421bc828ef | |||
ed65886ae5 | |||
8c7e2dbdf9 | |||
afb4209f10 | |||
1d8775d237 | |||
8787ec9fe8 | |||
1f810cd26a | |||
f4d7d19370 | |||
e616b2e247 | |||
2a39332d51 | |||
3c6b10c6b2 | |||
2a9226a55c | |||
3d65fd7cc4 | |||
36ddbfdc85 | |||
76292ef10c | |||
9ae2e528c5 | |||
2849e28c2b | |||
9d0e52b94d | |||
731f5f8523 | |||
9d6d43ee55 | |||
f9e99048c4 | |||
e1df8d14b4 | |||
b9419e0f36 | |||
e03c354e89 | |||
2e44e4d33c | |||
5cbaabeeab | |||
d64e381085 | |||
41306aa7e0 | |||
0bb2e47c98 | |||
ef660be285 | |||
4bac90a3b2 | |||
4182fc203e | |||
10e36c4233 | |||
bef397228f | |||
5cf47767d7 | |||
8f2d2535dc | |||
2aae8e6382 | |||
ba12b0de0d | |||
3552d03f6c | |||
8d5165c449 | |||
d8027656b5 | |||
4f57c5d56e | |||
2c5c81815a | |||
b97bfe9297 | |||
db07657e40 | |||
2d98d0fcc2 | |||
e6f6f17c6d | |||
166a927c20 | |||
625fe8866c | |||
69e7aa9fc9 | |||
a775cfe177 | |||
9e4a2ab824 | |||
24aa1f312a | |||
cde56741fb | |||
bbe694a622 | |||
7e575a718b | |||
ea9ca8b4ed | |||
0fe2884397 | |||
2982a2c963 | |||
6a43e1a64d | |||
3b5172a8fa | |||
be32aeee70 | |||
adcc74ab8d | |||
8acced56b2 | |||
f823c7cb5d | |||
26e6516626 | |||
5979e0cd0c | |||
3ba1bfc369 | |||
efa0e6eb62 | |||
159b4bd7dc | |||
2611c9525e | |||
0353eb4a12 | |||
92c4097f8d | |||
a909c60f05 | |||
56a9eab7eb | |||
7221eb7f39 | |||
b0b0482d71 | |||
49ab559992 | |||
3dd21c635a | |||
835bbb2e44 | |||
2ee2370a71 | |||
54dd65cfe1 | |||
b004aacd69 | |||
48b7b415e2 | |||
544cea95e1 | |||
8aa2632661 | |||
5419e8ae9d | |||
d4d28ab796 | |||
b8db928c58 | |||
bf45a5860e | |||
ca543fc8af | |||
57cf805e12 | |||
1ae9157985 | |||
206a6ae6c9 | |||
82ac590412 | |||
9a274128ce | |||
5664ee7bda | |||
9a56665c6b | |||
8044fb2db0 | |||
3a59ab9f14 | |||
f609a4f26a | |||
80463d12fb | |||
cef05d3553 | |||
5879b0df99 | |||
95cd9dd2b2 | |||
424d5611a5 | |||
9bff68a4f6 | |||
a9bdc655c1 | |||
9b617de6f0 | |||
771270d526 | |||
26d1307476 | |||
86707b9972 | |||
52cb865c5c | |||
3ea027a136 | |||
00469de93e | |||
9bc4e6794d | |||
429127793f | |||
75cb3fcc5f | |||
f0e87da830 | |||
c5639cd9fa | |||
95d4922e44 | |||
bdd52f0111 | |||
7bd07cb351 | |||
249afc5df4 | |||
d7af461173 | |||
b17e9f4ed0 | |||
6862734580 | |||
9e1f645428 | |||
c4818d79f3 | |||
d1a78a58cd | |||
65d0b5b9d9 | |||
614bc2a943 | |||
27b06358ea | |||
e56c01d0e2 | |||
ececca7ad2 | |||
e9cc417fd5 | |||
81a7d17b33 | |||
9382dd6d55 | |||
7aa2a57434 | |||
9b88ea5b60 | |||
8bfcea8054 | |||
f3d2be7a56 | |||
be31182969 | |||
b543063749 | |||
ce0060e6b0 | |||
35b12fe5ec | |||
6ac26094da | |||
8c6a0f68d4 | |||
f5d6672ccf | |||
db06edc5d3 | |||
568927349d | |||
4f812a7f34 | |||
38fc42d352 | |||
b4c5693ac6 | |||
79000aa5e0 | |||
2415381682 | |||
b499e7c682 | |||
d8cde2ae89 | |||
ddc00014be | |||
9ffa3e55c2 | |||
45fe3be83e | |||
dd6fe6a04a | |||
e76b38882c | |||
11bdab7e61 | |||
3d682fe957 | |||
a43e66ef92 | |||
3be7996e79 | |||
5041a4ffa3 | |||
b16b3c0b7f | |||
852ec3f9a0 | |||
dd7b7311b3 | |||
9364bad625 | |||
6fc5244439 | |||
8e1112c1dd | |||
9d1cb1bfaf | |||
216d7d035f | |||
ead6fbdf9c | |||
5b616770df | |||
23a5c5dc09 | |||
74656bf976 | |||
d8a2e0e9a3 | |||
046e46b962 | |||
ec08e4bc6d | |||
05e07ddf5c | |||
757d7479af | |||
440feaf74a | |||
22c50185b5 | |||
3a2c7900d6 | |||
fa8629300f | |||
37dc226996 | |||
4e1f94026c | |||
d27263af97 | |||
215f1af1da | |||
1291b647ae | |||
91df6c236f | |||
58cea7e8b4 | |||
b01f50bd70 | |||
5f48452e3b | |||
dae1b9a996 | |||
a21af0ade4 | |||
28123841ba | |||
183be911d0 | |||
1966809502 | |||
c3c41a61b0 | |||
90849a067f | |||
705f12c1d9 | |||
0826e66fe0 | |||
774769a7ad | |||
e72cecf457 | |||
9c1a3aa244 | |||
2d07c6eedb | |||
fdd92b2dda | |||
8c70189422 | |||
075c83b3a1 | |||
d3a19c5ac7 | |||
080874df10 | |||
24848a1e35 | |||
e215fbbd08 | |||
33aea56ccd | |||
b6683a3010 | |||
578ef04988 | |||
735a7a21bd | |||
80a69224f7 | |||
e0bf17930b | |||
db3177a5aa | |||
98b9839e3d | |||
0db4d89838 | |||
52278f8562 | |||
c19d9597fd | |||
d9d9916ccc | |||
26759c4af2 | |||
e529746294 | |||
0c4d4632ef | |||
e2c1216c1b | |||
d83dbc3670 | |||
0242b30027 | |||
0c656fd276 | |||
b7a3e5989d | |||
5b5f1d1b92 | |||
35bea5e044 | |||
7917cf9f00 | |||
5036672a58 | |||
585ab56ea4 | |||
9009f68e09 | |||
2bacc29d30 | |||
4d7d97e0e6 | |||
f1000a17b4 | |||
f43edbccdc | |||
6b4282eadf | |||
7e2781a2af | |||
32a53450a6 | |||
ce78817f41 | |||
f0e93c2fa9 | |||
fa6bb147ea | |||
220b105efb | |||
b56ad92e25 | |||
fc5fe4b445 | |||
c01d44e37d | |||
5a0e86aa70 | |||
b4529a20e8 | |||
b938adefaa | |||
b39d797c1f | |||
4240bfb7b1 | |||
b7572f107f | |||
379e3d70ca | |||
6fc87fad72 | |||
fa15a2856a | |||
eaec480f42 | |||
d18587330a | |||
5114dfca7d | |||
3395beaa56 | |||
df66d9fcdf | |||
1af1e0b5a3 | |||
9b41f9ecb8 | |||
48ade4993d | |||
4ecc807dbb | |||
86b69cc5d1 | |||
017a13fa3f | |||
ca12b2e30e | |||
db6c804b17 | |||
57ff668d2e | |||
9fb9b16b38 | |||
41178dff90 | |||
12deff5d1b | |||
21a645b1a9 | |||
e8a55aa647 | |||
6295b20545 | |||
850ecf648a | |||
e6cf18ea43 | |||
d28624796c | |||
380c216d77 | |||
ee5a387300 | |||
3ac36879e0 | |||
bc0c9ab698 | |||
5762489070 | |||
fcdc474731 | |||
f491d3e1e1 | |||
94c89eb623 | |||
cf0a18be51 | |||
0621ab6652 | |||
64a028cc76 | |||
f71a45235a | |||
718ee3d545 | |||
e92678ea2c | |||
1f175d4c98 | |||
4d6ccf2540 | |||
aa6c3936d2 | |||
4f05994b36 | |||
b27d6b2cb1 | |||
64f226f7da | |||
5c1606ed82 | |||
11977759ce | |||
bc3dc98b34 | |||
6fadc72553 | |||
a9e6b1ec6b | |||
cbc7b94b02 | |||
45c66e2090 | |||
11b2423544 | |||
1f9907d2ff | |||
fd503fceaf | |||
b7e5790cd1 | |||
3caab5de36 | |||
fa97e819eb | |||
bdc4bf97a7 | |||
cfb0f3961b | |||
8fa965118c | |||
9c800bcb2c | |||
ea4d8c5f49 | |||
5d2abdd1c3 | |||
7d5333db3b | |||
2a8a628b72 | |||
c4d2b787aa | |||
a4e11726cf | |||
9850fbd77d | |||
2ccb91dc6a | |||
3e76ed9122 | |||
2223fd663a | |||
3f960012cd | |||
0b094e2bf2 | |||
2388e1e80b | |||
62e34b69b3 | |||
fd68767216 | |||
93202d4529 | |||
ed1f0eb231 | |||
04612809ab | |||
8cca447e8c | |||
651e86a3c0 | |||
c3c3481ef5 | |||
0732d8bbba | |||
bdca31cc2d | |||
ed7aea8dd3 | |||
e813e44501 | |||
f46c45343a | |||
b12ffb8888 | |||
cf96677c78 | |||
ce03d8eb12 | |||
21dedef7f6 | |||
da7f77867a | |||
3415594877 | |||
0c38729735 | |||
a0b3a48e8b | |||
b662c2eb96 | |||
8cda641350 | |||
efdfeac55e | |||
e0577e15f2 | |||
bb0b0870ea | |||
74a73f9838 | |||
c9f9078726 | |||
eb875ea949 | |||
b4a0e4c0dc | |||
88a0705df1 | |||
7bcd96fc65 | |||
833825ae9a | |||
899383c30c | |||
d9d6cea5a9 | |||
d01ccd5a54 | |||
a896892ac9 | |||
d89d1894d0 | |||
587536ddcc | |||
ced5e1065f | |||
7479173811 | |||
4b83a2d27a | |||
41f72b1236 | |||
c98a6705e6 | |||
6454bf69aa | |||
bd30ea723e | |||
2dd4cb9f7d | |||
1784b4bf50 | |||
8e4b85e29b | |||
7098e56ccf | |||
02ad491dea | |||
708fee535c | |||
7636cc7fe4 | |||
f856e64fb3 | |||
a783a084d4 | |||
81b12d02ec | |||
336df6c65e | |||
35f9299fc6 | |||
649c8319e6 | |||
99cf5871aa | |||
da04e9d801 | |||
2db2d98ef0 | |||
817eacccd8 | |||
ce6d3c6eb2 | |||
ef32e1ce1a | |||
c1105e945e | |||
69b089845c | |||
75556f6c5f | |||
b650d1ef79 | |||
099b571e8f | |||
cb926f7b49 | |||
13515c5eb0 | |||
58d960d914 | |||
4a83bb6c93 | |||
3e56e81d06 | |||
312e9bf5d6 | |||
18d7e64660 | |||
f1118020a1 | |||
63433f1bc8 | |||
921a66554e | |||
bb0d08a721 | |||
fe14e52e77 | |||
24d72ca43c | |||
457f7889df | |||
7b0c0692dc | |||
c4cb3a77cb | |||
aed8d3800b | |||
53a9264b67 | |||
e18fb13616 | |||
2201bd9b09 | |||
da8f6c5682 | |||
14d7ba5cc9 | |||
5ee096232c | |||
c259ef41bd | |||
2c238aea6a | |||
c600c1ebe7 | |||
df94052180 | |||
f878276de7 | |||
cd89304706 | |||
2b9f258126 | |||
85587c0c2a |
@ -12,3 +12,17 @@ rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-stati
|
|||||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||||
# [target.x86_64-apple-darwin]
|
# [target.x86_64-apple-darwin]
|
||||||
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
||||||
|
|
||||||
|
# How to use mold in linux and mac
|
||||||
|
|
||||||
|
# [target.x86_64-unknown-linux-gnu]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"]
|
||||||
|
|
||||||
|
# [target.x86_64-apple-darwin]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
|
# [target.aarch64-apple-darwin]
|
||||||
|
# linker = "clang"
|
||||||
|
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
# docs
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-patch"]
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
22
.github/pull_request_template.md
vendored
22
.github/pull_request_template.md
vendored
@ -1,26 +1,24 @@
|
|||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)
|
_(Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.)_
|
||||||
|
|
||||||
# Major Changes
|
_(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_
|
||||||
|
|
||||||
If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose).
|
# User-Facing Changes
|
||||||
Making sure we're all on board with the change saves everybody's time.
|
|
||||||
Thanks!
|
_(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_
|
||||||
|
|
||||||
# Tests + Formatting
|
# Tests + Formatting
|
||||||
|
|
||||||
Make sure you've done the following, if applicable:
|
Don't forget to add tests that cover your changes.
|
||||||
|
|
||||||
- Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder)
|
|
||||||
- Try to think about corner cases and various ways how your changes could break. Cover those in the tests
|
|
||||||
|
|
||||||
Make sure you've run and fixed any issues with these commands:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
- `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||||
- `cargo test --workspace --features=extra` to check that all tests pass
|
- `cargo test --workspace` to check that all tests pass
|
||||||
|
|
||||||
# After Submitting
|
# After Submitting
|
||||||
|
|
||||||
* Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged.
|
If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
|
||||||
|
79
.github/workflows/ci.yml
vendored
79
.github/workflows/ci.yml
vendored
@ -11,9 +11,26 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
||||||
|
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
||||||
|
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||||
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
|
style: [default, dataframe]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
|
include:
|
||||||
|
- style: default
|
||||||
|
flags: ""
|
||||||
|
- style: dataframe
|
||||||
|
flags: "--features=dataframe "
|
||||||
|
exclude:
|
||||||
|
# only test dataframes on Ubuntu (the fastest platform)
|
||||||
|
- platform: windows-latest
|
||||||
|
style: dataframe
|
||||||
|
- platform: macos-latest
|
||||||
|
style: dataframe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
@ -23,19 +40,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.5
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: cargo fmt
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo fmt --all -- --check
|
||||||
with:
|
|
||||||
command: fmt
|
|
||||||
args: --all -- --check
|
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
|
||||||
|
|
||||||
nu-tests:
|
nu-tests:
|
||||||
env:
|
env:
|
||||||
@ -44,20 +55,21 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
style: [extra, default]
|
style: [default, dataframe]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
include:
|
include:
|
||||||
- style: extra
|
|
||||||
flags: "--features=extra"
|
|
||||||
- style: default
|
- style: default
|
||||||
flags: ""
|
flags: ""
|
||||||
|
- style: dataframe
|
||||||
|
flags: "--features=dataframe"
|
||||||
exclude:
|
exclude:
|
||||||
|
# only test dataframes on Ubuntu (the fastest platform)
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
style: default
|
style: dataframe
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
style: default
|
style: dataframe
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
@ -65,13 +77,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.5
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
|
||||||
|
|
||||||
python-virtualenv:
|
python-virtualenv:
|
||||||
env:
|
env:
|
||||||
@ -80,7 +89,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-20.04, macos-latest, windows-latest]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
py:
|
py:
|
||||||
@ -92,13 +101,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.5
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo install --locked --path=. --profile ci --no-default-features
|
||||||
with:
|
|
||||||
command: install
|
|
||||||
args: --path=. --profile ci --no-default-features
|
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
@ -107,8 +113,9 @@ jobs:
|
|||||||
|
|
||||||
- run: python -m pip install tox
|
- run: python -m pip install tox
|
||||||
|
|
||||||
|
# Get only the latest tagged version for stability reasons
|
||||||
- name: Install virtualenv
|
- name: Install virtualenv
|
||||||
run: git clone https://github.com/pypa/virtualenv.git
|
run: git clone https://github.com/pypa/virtualenv.git && cd virtualenv && git checkout $(git describe --tags | cut -d - -f 1)
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
@ -124,7 +131,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
@ -134,16 +141,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.5
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1.0.1
|
run: cargo test --profile ci --package nu_plugin_*
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --profile ci --package nu_plugin_*
|
|
||||||
|
62
.github/workflows/release-pkg.nu
vendored
62
.github/workflows/release-pkg.nu
vendored
@ -6,6 +6,32 @@
|
|||||||
# REF:
|
# REF:
|
||||||
# 1. https://github.com/volks73/cargo-wix
|
# 1. https://github.com/volks73/cargo-wix
|
||||||
|
|
||||||
|
# Added 2022-11-29 when Windows packaging wouldn't work
|
||||||
|
# To run this manual for windows
|
||||||
|
# unset CARGO_TARGET_DIR if set
|
||||||
|
# hide-env CARGO_TARGET_DIR
|
||||||
|
# let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||||
|
# let-env TARGET_RUSTFLAGS = ''
|
||||||
|
# let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||||
|
# let-env GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
|
||||||
|
# let-env OS = 'windows-latest'
|
||||||
|
# You need to run this twice. The first pass makes the output folder and builds everything
|
||||||
|
# The second pass generates the msi file
|
||||||
|
# Pass 1 let-env _EXTRA_ = 'bin'
|
||||||
|
# Pass 2 let-env _EXTRA_ = 'msi'
|
||||||
|
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
||||||
|
# let-env Path = ($env.Path | append 'c:\apps\7-zip')
|
||||||
|
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
||||||
|
# let-env Path = ($env.Path | append 'c:\path\to\aria2c')
|
||||||
|
# make sure you have the wixtools installed https://wixtoolset.org/
|
||||||
|
# let-env Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
||||||
|
# After msi is generated, if you have to update winget-pkgs repo, you'll need to patch the release
|
||||||
|
# by deleting the existing msi and uploading this new msi. Then you'll need to update the hash
|
||||||
|
# on the winget-pkgs PR. To generate the hash, run this command
|
||||||
|
# open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
|
||||||
|
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
|
||||||
|
|
||||||
|
|
||||||
# The main binary file to be released
|
# The main binary file to be released
|
||||||
let bin = 'nu'
|
let bin = 'nu'
|
||||||
let os = $env.OS
|
let os = $env.OS
|
||||||
@ -16,8 +42,13 @@ let flags = $env.TARGET_RUSTFLAGS
|
|||||||
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
||||||
let version = (open Cargo.toml | get package.version)
|
let version = (open Cargo.toml | get package.version)
|
||||||
|
|
||||||
|
$'Debugging info:'
|
||||||
|
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
||||||
|
|
||||||
# $env
|
# $env
|
||||||
|
|
||||||
|
let USE_UBUNTU = 'ubuntu-20.04'
|
||||||
|
|
||||||
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||||
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||||
|
|
||||||
@ -26,8 +57,9 @@ $'Start building ($bin)...'; hr-line
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Build for Ubuntu and macOS
|
# Build for Ubuntu and macOS
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||||
if $os == 'ubuntu-latest' {
|
if $os == $USE_UBUNTU {
|
||||||
|
sudo apt update
|
||||||
sudo apt-get install libxcb-composite0-dev -y
|
sudo apt-get install libxcb-composite0-dev -y
|
||||||
}
|
}
|
||||||
if $target == 'aarch64-unknown-linux-gnu' {
|
if $target == 'aarch64-unknown-linux-gnu' {
|
||||||
@ -38,10 +70,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
||||||
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
|
} else if $target == 'riscv64gc-unknown-linux-gnu' {
|
||||||
|
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||||
|
let-env CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
|
||||||
|
cargo-build-nu $flags
|
||||||
} else {
|
} else {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
if $os == 'ubuntu-latest' { sudo apt install musl-tools -y }
|
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,9 +87,9 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in ['windows-latest'] {
|
if $os in ['windows-latest'] {
|
||||||
if ($flags | str trim | is-empty) {
|
if ($flags | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra
|
cargo build --release --all --target $target
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra $flags
|
cargo build --release --all --target $target $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +124,7 @@ if ($ver | str trim | is-empty) {
|
|||||||
# Create a release archive and send it to output for the following steps
|
# Create a release archive and send it to output for the following steps
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||||
|
|
||||||
let files = (ls | get name)
|
let files = (ls | get name)
|
||||||
let dest = $'($bin)-($version)-($target)'
|
let dest = $'($bin)-($version)-($target)'
|
||||||
@ -101,14 +137,15 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
|
|
||||||
tar -czf $archive $dest
|
tar -czf $archive $dest
|
||||||
print $'archive: ---> ($archive)'; ls $archive
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
echo $'::set-output name=archive::($archive)'
|
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
||||||
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
} else if $os == 'windows-latest' {
|
} else if $os == 'windows-latest' {
|
||||||
|
|
||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
|
||||||
$'(char nl)Download less related stuffs...'; hr-line
|
$'(char nl)Download less related stuffs...'; hr-line
|
||||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||||
|
|
||||||
# Create Windows msi release package
|
# Create Windows msi release package
|
||||||
@ -121,7 +158,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
cp -r $'($dist)/*' target/release/
|
cp -r $'($dist)/*' target/release/
|
||||||
cargo install cargo-wix --version 0.3.3
|
cargo install cargo-wix --version 0.3.3
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
echo $'::set-output name=archive::($wixRelease)'
|
print $'archive: ---> ($wixRelease)';
|
||||||
|
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -131,16 +169,16 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
|||||||
print $'archive: ---> ($archive)';
|
print $'archive: ---> ($archive)';
|
||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | is-empty) {
|
if not ($pkg | is-empty) {
|
||||||
echo $'::set-output name=archive::($pkg | get 0)'
|
echo $"archive=($pkg | get 0)" | save --append $env.GITHUB_OUTPUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def 'cargo-build-nu' [ options: string ] {
|
def 'cargo-build-nu' [ options: string ] {
|
||||||
if ($options | str trim | is-empty) {
|
if ($options | str trim | is-empty) {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
cargo build --release --all --target $target --features=static-link-openssl
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
cargo build --release --all --target $target --features=static-link-openssl $options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@ -27,6 +27,7 @@ jobs:
|
|||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
- armv7-unknown-linux-gnueabihf
|
- armv7-unknown-linux-gnueabihf
|
||||||
|
- riscv64gc-unknown-linux-gnu
|
||||||
extra: ['bin']
|
extra: ['bin']
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
@ -44,35 +45,37 @@ jobs:
|
|||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
os: ubuntu-latest
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-latest
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: ''
|
||||||
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.0.2
|
- uses: actions/checkout@v3.1.0
|
||||||
|
|
||||||
- name: Install Rust Toolchain Components
|
- name: Update Rust Toolchain Target
|
||||||
uses: actions-rs/toolchain@v1.0.6
|
run: |
|
||||||
with:
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
override: true
|
|
||||||
profile: minimal
|
- name: Setup Rust toolchain and cache
|
||||||
toolchain: stable
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.5
|
||||||
target: ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v2.1
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.69.1
|
version: 0.72.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ jobs:
|
|||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v0.1.13
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v6
|
||||||
with:
|
with:
|
||||||
#debug-only: true
|
#debug-only: true
|
||||||
ascending: true
|
ascending: true
|
||||||
|
13
.github/workflows/typos.yml
vendored
Normal file
13
.github/workflows/typos.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: Typos
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
name: Spell Check with Typos
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Actions Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check spelling of book
|
||||||
|
uses: crate-ci/typos@master
|
12
.typos.toml
Normal file
12
.typos.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[files]
|
||||||
|
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt"]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
# Ignore false-positives
|
||||||
|
nd = "nd"
|
||||||
|
fo = "fo"
|
||||||
|
ons = "ons"
|
||||||
|
ba = "ba"
|
||||||
|
Plasticos = "Plasticos"
|
||||||
|
IIF = "IIF"
|
||||||
|
numer = "numer"
|
@ -31,6 +31,18 @@ cd nushell
|
|||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
It is a good practice to cover your changes with a test. Also, try to think about corner cases and various ways how your changes could break. Cover those in the tests as well.
|
||||||
|
|
||||||
|
Tests can be found in different places:
|
||||||
|
* `/tests`
|
||||||
|
* `src/tests`
|
||||||
|
* command examples
|
||||||
|
* crate-specific tests
|
||||||
|
|
||||||
|
The most comprehensive test suite we have is the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||||
|
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
- Build and run Nushell:
|
- Build and run Nushell:
|
||||||
@ -39,21 +51,21 @@ cargo build
|
|||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
|
- Build and run with dataframe support.
|
||||||
```shell
|
```shell
|
||||||
cargo run --features=extra
|
cargo run --features=dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run Clippy on Nushell:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests:
|
- Run all tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo test --workspace --features=extra
|
cargo test --workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests for a specific command
|
- Run all tests for a specific command
|
||||||
@ -79,11 +91,11 @@ cargo build
|
|||||||
- To view verbose logs when developing, enable the `trace` log level.
|
- To view verbose logs when developing, enable the `trace` log level.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run --release --features=extra -- --log-level trace
|
cargo run --release -- --log-level trace
|
||||||
```
|
```
|
||||||
|
|
||||||
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
||||||
```shell
|
```shell
|
||||||
cargo run --release --features=extra -- --log-level trace --log-target file
|
cargo run --release -- --log-level trace --log-target file
|
||||||
open $"($nu.temp-path)/nu-($nu.pid).log"
|
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||||
```
|
```
|
||||||
|
1132
Cargo.lock
generated
1132
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
84
Cargo.toml
84
Cargo.toml
@ -3,17 +3,24 @@ authors = ["The Nushell Project Developers"]
|
|||||||
default-run = "nu"
|
default-run = "nu"
|
||||||
description = "A new type of shell"
|
description = "A new type of shell"
|
||||||
documentation = "https://www.nushell.sh/book/"
|
documentation = "https://www.nushell.sh/book/"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
exclude = ["images"]
|
exclude = ["images"]
|
||||||
homepage = "https://www.nushell.sh"
|
homepage = "https://www.nushell.sh"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.71.0"
|
version = "0.75.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ version }-{ target }.{ archive-format }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/nu-cli",
|
"crates/nu-cli",
|
||||||
@ -32,29 +39,29 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.21", features = ["serde"] }
|
chrono = { version = "0.4.23", features = ["serde"] }
|
||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
ctrlc = "3.2.1"
|
ctrlc = "3.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.75.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.75.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.71.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.75.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.75.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.71.0" }
|
nu-json = { path = "./crates/nu-json", version = "0.75.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.75.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.71.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.75.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.75.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.75.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.75.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.71.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.75.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.71.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.75.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.75.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.75.0" }
|
||||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
rayon = "1.5.1"
|
rayon = "1.6.1"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
simplelog = "0.12.0"
|
simplelog = "0.12.0"
|
||||||
time = "0.3.12"
|
time = "0.3.12"
|
||||||
@ -69,25 +76,35 @@ signal-hook = { version = "0.3.14", default-features = false }
|
|||||||
winres = "0.1"
|
winres = "0.1"
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dependencies]
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
|
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"] }
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.75.0" }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
assert_cmd = "2.0.2"
|
assert_cmd = "2.0.2"
|
||||||
|
criterion = "0.4"
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
serial_test = "0.8.0"
|
serial_test = "1.0.0"
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = { version = "0.15.0", default-features = false }
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = [
|
||||||
extra = ["default", "dataframe", "database"]
|
"nu-plugin",
|
||||||
default = ["plugin", "which-support", "trash-support"]
|
"nu-cli/plugin",
|
||||||
|
"nu-parser/plugin",
|
||||||
|
"nu-command/plugin",
|
||||||
|
"nu-protocol/plugin",
|
||||||
|
"nu-engine/plugin",
|
||||||
|
]
|
||||||
|
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||||
|
extra = ["default"]
|
||||||
|
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
wasi = []
|
wasi = []
|
||||||
|
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
|
|
||||||
@ -100,11 +117,11 @@ trash-support = ["nu-command/trash-support"]
|
|||||||
# Dataframe feature for nushell
|
# Dataframe feature for nushell
|
||||||
dataframe = ["nu-command/dataframe"]
|
dataframe = ["nu-command/dataframe"]
|
||||||
|
|
||||||
# Database commands for nushell
|
# SQLite commands for nushell
|
||||||
database = ["nu-command/database"]
|
sqlite = ["nu-command/sqlite"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s" # Optimize for size
|
opt-level = "s" # Optimize for size
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
@ -131,3 +148,10 @@ path = "src/main.rs"
|
|||||||
# changing versions in each sub-crate of the workspace is tedious
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||||
|
|
||||||
|
# Criterion benchmarking setup
|
||||||
|
# Run all benchmarks with `cargo bench`
|
||||||
|
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||||
|
[[bench]]
|
||||||
|
name = "benchmarks"
|
||||||
|
harness = false
|
||||||
|
9
Cross.toml
Normal file
9
Cross.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||||
|
# Run cross-rs like this:
|
||||||
|
# cross build --target aarch64-unknown-linux-musl --release
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
19
README.md
19
README.md
@ -1,6 +1,6 @@
|
|||||||
# Nushell <!-- omit in toc -->
|
# Nushell <!-- omit in toc -->
|
||||||
[](https://crates.io/crates/nu)
|
[](https://crates.io/crates/nu)
|
||||||

|

|
||||||
[](https://discord.gg/NtAbbGn)
|
[](https://discord.gg/NtAbbGn)
|
||||||
[](https://changelog.com/podcast/363)
|
[](https://changelog.com/podcast/363)
|
||||||
[](https://twitter.com/nu_shell)
|
[](https://twitter.com/nu_shell)
|
||||||
@ -47,7 +47,7 @@ brew install nushell
|
|||||||
winget install nushell
|
winget install nushell
|
||||||
```
|
```
|
||||||
|
|
||||||
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||||
|
|
||||||
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||||
|
|
||||||
@ -126,12 +126,13 @@ For example, you can load a .toml file as structured data and explore it:
|
|||||||
> open Cargo.toml
|
> open Cargo.toml
|
||||||
╭──────────────────┬────────────────────╮
|
╭──────────────────┬────────────────────╮
|
||||||
│ bin │ [table 1 row] │
|
│ bin │ [table 1 row] │
|
||||||
│ dependencies │ {record 24 fields} │
|
│ dependencies │ {record 25 fields} │
|
||||||
│ dev-dependencies │ {record 8 fields} │
|
│ dev-dependencies │ {record 8 fields} │
|
||||||
│ features │ {record 10 fields} │
|
│ features │ {record 10 fields} │
|
||||||
│ package │ {record 13 fields} │
|
│ package │ {record 13 fields} │
|
||||||
|
│ patch │ {record 1 field} │
|
||||||
│ profile │ {record 3 fields} │
|
│ profile │ {record 3 fields} │
|
||||||
│ target │ {record 2 fields} │
|
│ target │ {record 3 fields} │
|
||||||
│ workspace │ {record 1 field} │
|
│ workspace │ {record 1 field} │
|
||||||
╰──────────────────┴────────────────────╯
|
╰──────────────────┴────────────────────╯
|
||||||
```
|
```
|
||||||
@ -149,11 +150,11 @@ We can pipe this into a command that gets the contents of one of the columns:
|
|||||||
│ exclude │ [list 1 item] │
|
│ exclude │ [list 1 item] │
|
||||||
│ homepage │ https://www.nushell.sh │
|
│ homepage │ https://www.nushell.sh │
|
||||||
│ license │ MIT │
|
│ license │ MIT │
|
||||||
|
│ metadata │ {record 1 field} │
|
||||||
│ name │ nu │
|
│ name │ nu │
|
||||||
│ readme │ README.md │
|
|
||||||
│ repository │ https://github.com/nushell/nushell │
|
│ repository │ https://github.com/nushell/nushell │
|
||||||
│ rust-version │ 1.60 │
|
│ rust-version │ 1.60 │
|
||||||
│ version │ 0.63.1 │
|
│ version │ 0.72.0 │
|
||||||
╰───────────────┴────────────────────────────────────╯
|
╰───────────────┴────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ And if needed we can drill down further:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version
|
> open Cargo.toml | get package.version
|
||||||
0.63.1
|
0.72.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
@ -173,6 +174,8 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
|||||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||||
|
|
||||||
|
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||||
@ -206,7 +209,7 @@ Nu is under heavy development and will naturally change as it matures. The chart
|
|||||||
| Functions | | | | X | | Functions and aliases are supported |
|
| Functions | | | | X | | Functions and aliases are supported |
|
||||||
| Variables | | | | X | | Nu supports variables and environment variables |
|
| Variables | | | | X | | Nu supports variables and environment variables |
|
||||||
| Completions | | | | X | | Completions for filepaths |
|
| Completions | | | | X | | Completions for filepaths |
|
||||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
| Type-checking | | | | x | | Commands check basic types, and input/output types |
|
||||||
|
|
||||||
## Officially Supported By
|
## Officially Supported By
|
||||||
|
|
||||||
|
7
benches/README.md
Normal file
7
benches/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Criterion benchmarks
|
||||||
|
|
||||||
|
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
||||||
|
|
||||||
|
Run all benchmarks with `cargo bench`
|
||||||
|
|
||||||
|
Or run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
187
benches/benchmarks.rs
Normal file
187
benches/benchmarks.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
|
use nu_cli::eval_source;
|
||||||
|
use nu_parser::parse;
|
||||||
|
use nu_plugin::{EncodingType, PluginResponse};
|
||||||
|
use nu_protocol::{PipelineData, Span, Value};
|
||||||
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
|
|
||||||
|
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
||||||
|
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
||||||
|
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||||
|
// a way to split things up again.
|
||||||
|
|
||||||
|
fn parser_benchmarks(c: &mut Criterion) {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
// parsing config.nu breaks without PWD set
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let default_env = get_default_env().as_bytes();
|
||||||
|
c.bench_function("parse_default_env_file", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||||
|
|mut working_set| parse(&mut working_set, None, default_env, false, &[]),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let default_config = get_default_config().as_bytes();
|
||||||
|
c.bench_function("parse_default_config_file", |b| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||||
|
|mut working_set| parse(&mut working_set, None, default_config, false, &[]),
|
||||||
|
BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("eval default_env.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_env().as_bytes(),
|
||||||
|
"default_env.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("eval default_config.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
// parsing config.nu breaks without PWD set
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||||
|
);
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_config().as_bytes(),
|
||||||
|
"default_config.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_benchmarks(c: &mut Criterion) {
|
||||||
|
c.bench_function("eval default_env.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_env().as_bytes(),
|
||||||
|
"default_env.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("eval default_config.nu", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
// parsing config.nu breaks without PWD set
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||||
|
);
|
||||||
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
get_default_config().as_bytes(),
|
||||||
|
"default_config.nu",
|
||||||
|
PipelineData::empty(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||||
|
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
||||||
|
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
|
||||||
|
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
|
||||||
|
|
||||||
|
Value::List {
|
||||||
|
vals: (0..row_cnt)
|
||||||
|
.map(|_| Value::test_record(columns.clone(), vals.clone()))
|
||||||
|
.collect(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encoding_benchmarks(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Encoding");
|
||||||
|
let test_cnt_pairs = [
|
||||||
|
(100, 5),
|
||||||
|
(100, 10),
|
||||||
|
(100, 15),
|
||||||
|
(1000, 5),
|
||||||
|
(1000, 10),
|
||||||
|
(1000, 15),
|
||||||
|
(10000, 5),
|
||||||
|
(10000, 10),
|
||||||
|
(10000, 15),
|
||||||
|
];
|
||||||
|
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||||
|
for fmt in ["json", "msgpack"] {
|
||||||
|
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
||||||
|
let mut res = vec![];
|
||||||
|
let test_data =
|
||||||
|
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||||
|
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||||
|
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoding_benchmarks(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Decoding");
|
||||||
|
let test_cnt_pairs = [
|
||||||
|
(100, 5),
|
||||||
|
(100, 10),
|
||||||
|
(100, 15),
|
||||||
|
(1000, 5),
|
||||||
|
(1000, 10),
|
||||||
|
(1000, 15),
|
||||||
|
(10000, 5),
|
||||||
|
(10000, 10),
|
||||||
|
(10000, 15),
|
||||||
|
];
|
||||||
|
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||||
|
for fmt in ["json", "msgpack"] {
|
||||||
|
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
||||||
|
let mut res = vec![];
|
||||||
|
let test_data =
|
||||||
|
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||||
|
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||||
|
encoder.encode_response(&test_data, &mut res).unwrap();
|
||||||
|
let mut binary_data = std::io::Cursor::new(res);
|
||||||
|
b.iter(|| {
|
||||||
|
binary_data.set_position(0);
|
||||||
|
encoder.decode_response(&mut binary_data)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
benches,
|
||||||
|
parser_benchmarks,
|
||||||
|
eval_benchmarks,
|
||||||
|
encoding_benchmarks,
|
||||||
|
decoding_benchmarks
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
echo "---------------------------------------------------------------"
|
echo "---------------------------------------------------------------"
|
||||||
echo "Building nushell (nu) with --features=extra and all the plugins"
|
echo "Building nushell (nu) with dataframes and all the plugins"
|
||||||
echo "---------------------------------------------------------------"
|
echo "---------------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@ -14,10 +15,10 @@ NU_PLUGINS=(
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo "Building nushell"
|
echo "Building nushell"
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
for plugin in "${NU_PLUGINS[@]}"
|
for plugin in "${NU_PLUGINS[@]}"
|
||||||
do
|
do
|
||||||
echo '' && cd crates/$plugin
|
echo '' && cd crates/"$plugin"
|
||||||
echo "Building $plugin..."
|
echo "Building $plugin..."
|
||||||
echo "-----------------------------"
|
echo "-----------------------------"
|
||||||
cargo build && cd ../..
|
cargo build && cd ../..
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
@echo off
|
@echo off
|
||||||
@echo -------------------------------------------------------------------
|
@echo -------------------------------------------------------------------
|
||||||
@echo Building nushell (nu.exe) with --features=extra and all the plugins
|
@echo Building nushell (nu.exe) with dataframes and all the plugins
|
||||||
@echo -------------------------------------------------------------------
|
@echo -------------------------------------------------------------------
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
echo Building nushell.exe
|
echo Building nushell.exe
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd crates\nu_plugin_example
|
@cd crates\nu_plugin_example
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
echo '-------------------------------------------------------------------'
|
echo '-------------------------------------------------------------------'
|
||||||
echo 'Building nushell (nu) with --features=extra and all the plugins'
|
echo 'Building nushell (nu) with dataframes and all the plugins'
|
||||||
echo '-------------------------------------------------------------------'
|
echo '-------------------------------------------------------------------'
|
||||||
|
|
||||||
echo $'(char nl)Building nushell'
|
echo $'(char nl)Building nushell'
|
||||||
echo '----------------------------'
|
echo '----------------------------'
|
||||||
cargo build --features=extra
|
cargo build --features=dataframe
|
||||||
|
|
||||||
let plugins = [
|
let plugins = [
|
||||||
nu_plugin_inc,
|
nu_plugin_inc,
|
||||||
|
@ -5,34 +5,34 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.71.0"
|
version = "0.75.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.71.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.75.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.71.0" }
|
nu-command = { path = "../nu-command", version = "0.75.0" }
|
||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = { version = "0.15.0", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
nu-engine = { path = "../nu-engine", version = "0.75.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.71.0" }
|
nu-path = { path = "../nu-path", version = "0.75.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
nu-parser = { path = "../nu-parser", version = "0.75.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.75.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
nu-utils = { path = "../nu-utils", version = "0.75.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.75.0" }
|
||||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
chrono = "0.4.21"
|
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
fancy-regex = "0.10.0"
|
fancy-regex = "0.11.0"
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
lazy_static = "1.4.0"
|
once_cell = "1.17.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
sysinfo = "0.26.2"
|
sysinfo = "0.27.7"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -94,10 +94,7 @@ impl CommandCompletion {
|
|||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
description: x.1,
|
description: x.1,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,10 +105,7 @@ impl CommandCompletion {
|
|||||||
value: String::from_utf8_lossy(&x).to_string(),
|
value: String::from_utf8_lossy(&x).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,15 +122,15 @@ impl CommandCompletion {
|
|||||||
value: x,
|
value: x,
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let results_strings: Vec<String> =
|
||||||
|
results.clone().into_iter().map(|x| x.value).collect();
|
||||||
|
|
||||||
for external in results_external {
|
for external in results_external {
|
||||||
if results.contains(&external) {
|
if results_strings.contains(&external.value) {
|
||||||
results.push(Suggestion {
|
results.push(Suggestion {
|
||||||
value: format!("^{}", external.value),
|
value: format!("^{}", external.value),
|
||||||
description: None,
|
description: None,
|
||||||
@ -170,7 +164,7 @@ impl Completer for CommandCompletion {
|
|||||||
.flattened
|
.flattened
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.skip_while(|x| x.0.end > pos)
|
.skip_while(|x| x.0.end + offset > pos)
|
||||||
.take_while(|x| {
|
.take_while(|x| {
|
||||||
matches!(
|
matches!(
|
||||||
x.1,
|
x.1,
|
||||||
@ -187,10 +181,7 @@ impl Completer for CommandCompletion {
|
|||||||
let subcommands = if let Some(last) = last {
|
let subcommands = if let Some(last) = last {
|
||||||
self.complete_commands(
|
self.complete_commands(
|
||||||
working_set,
|
working_set,
|
||||||
Span {
|
Span::new(last.0.start, pos),
|
||||||
start: last.0.start,
|
|
||||||
end: pos,
|
|
||||||
},
|
|
||||||
offset,
|
offset,
|
||||||
false,
|
false,
|
||||||
options.match_algorithm,
|
options.match_algorithm,
|
||||||
|
@ -5,6 +5,7 @@ use crate::completions::{
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
ast::PipelineElement,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
BlockId, PipelineData, Span, Value,
|
BlockId, PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
@ -76,10 +77,7 @@ impl NuCompleter {
|
|||||||
Value::List {
|
Value::List {
|
||||||
vals: spans
|
vals: spans
|
||||||
.iter()
|
.iter()
|
||||||
.map(|it| Value::String {
|
.map(|it| Value::string(it, Span::unknown()))
|
||||||
val: it.to_string(),
|
|
||||||
span: Span::unknown(),
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
@ -91,7 +89,7 @@ impl NuCompleter {
|
|||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&mut callee_stack,
|
&mut callee_stack,
|
||||||
block,
|
block,
|
||||||
PipelineData::new(span),
|
PipelineData::empty(),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@ -100,30 +98,31 @@ impl NuCompleter {
|
|||||||
Ok(pd) => {
|
Ok(pd) => {
|
||||||
let value = pd.into_value(span);
|
let value = pd.into_value(span);
|
||||||
if let Value::List { vals, span: _ } = value {
|
if let Value::List { vals, span: _ } = value {
|
||||||
let result = map_value_completions(
|
let result =
|
||||||
vals.iter(),
|
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
||||||
Span {
|
|
||||||
start: span.start,
|
|
||||||
end: span.end,
|
|
||||||
},
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Some(result);
|
return Some(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => println!("failed to eval completer block: {}", err),
|
Err(err) => println!("failed to eval completer block: {err}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
|
// pos: is the position of the cursor in the shell input.
|
||||||
|
// e.g. lets say you have an alias -> `alias ll = ls -l` and you type in the shell:
|
||||||
|
// > ll -a | c
|
||||||
|
// and your cursor is right after `c` then `pos` = 9
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let mut offset = working_set.next_span_start();
|
||||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||||
let initial_line = line.to_string();
|
// new_line: vector containing all alias "translations" so if it was `ll` now is `ls -l`.
|
||||||
let alias_total_offset: usize = alias_offset.iter().sum();
|
// alias_offset:vector the offset between the name and the alias)
|
||||||
|
let initial_line = line.to_string(); // Entire line in the shell input.
|
||||||
|
let alias_total_offset: usize = alias_offset.iter().sum(); // the sum of all alias offsets.
|
||||||
new_line.insert(alias_total_offset + pos, b'a');
|
new_line.insert(alias_total_offset + pos, b'a');
|
||||||
let pos = offset + pos;
|
let pos = offset + pos;
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
@ -131,139 +130,72 @@ impl NuCompleter {
|
|||||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||||
|
|
||||||
for pipeline in output.pipelines.into_iter() {
|
for pipeline in output.pipelines.into_iter() {
|
||||||
for expr in pipeline.expressions {
|
for pipeline_element in pipeline.elements {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
match pipeline_element {
|
||||||
let span_offset: usize = alias_offset.iter().sum();
|
PipelineElement::Expression(_, expr)
|
||||||
let mut spans: Vec<String> = vec![];
|
| PipelineElement::Redirection(_, _, expr)
|
||||||
|
| PipelineElement::And(_, expr)
|
||||||
|
| PipelineElement::Or(_, expr)
|
||||||
|
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
|
||||||
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
|
let span_offset: usize = alias_offset.iter().sum();
|
||||||
|
let mut spans: Vec<String> = vec![];
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
// Read the current spam to string
|
// Read the current spam to string
|
||||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||||
|
|
||||||
// Skip the last 'a' as span item
|
// Skip the last 'a' as span item
|
||||||
if flat_idx == flattened.len() - 1 {
|
if flat_idx == flattened.len() - 1 {
|
||||||
let mut chars = current_span_str.chars();
|
let mut chars = current_span_str.chars();
|
||||||
chars.next_back();
|
chars.next_back();
|
||||||
let current_span_str = chars.as_str().to_owned();
|
let current_span_str = chars.as_str().to_owned();
|
||||||
spans.push(current_span_str.to_string());
|
spans.push(current_span_str.to_string());
|
||||||
} else {
|
} else {
|
||||||
spans.push(current_span_str.to_string());
|
spans.push(current_span_str.to_string());
|
||||||
}
|
|
||||||
|
|
||||||
// Complete based on the last span
|
|
||||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
|
||||||
// Context variables
|
|
||||||
let most_left_var =
|
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
|
||||||
|
|
||||||
// Create a new span
|
|
||||||
let new_span = if flat_idx == 0 {
|
|
||||||
Span {
|
|
||||||
start: flat.0.start,
|
|
||||||
end: flat.0.end - 1 - span_offset,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Span {
|
|
||||||
start: flat.0.start - span_offset,
|
|
||||||
end: flat.0.end - 1 - span_offset,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
|
||||||
let index = pos - (flat.0.start - span_offset);
|
|
||||||
prefix.drain(index..);
|
|
||||||
|
|
||||||
// Variables completion
|
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
|
||||||
let mut completer = VariableCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
self.stack.clone(),
|
|
||||||
most_left_var.unwrap_or((vec![], vec![])),
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags completion
|
|
||||||
if prefix.starts_with(b"-") {
|
|
||||||
// Try to complete flag internally
|
|
||||||
let mut completer = FlagCompletion::new(expr.clone());
|
|
||||||
let result = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix.clone(),
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We got no results for internal completion
|
// Complete based on the last span
|
||||||
// now we can check if external completer is set and use it
|
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||||
if let Some(block_id) = config.external_completer {
|
// Context variables
|
||||||
if let Some(external_result) =
|
let most_left_var =
|
||||||
self.external_completion(block_id, &spans, offset, new_span)
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
{
|
|
||||||
return external_result;
|
// Create a new span
|
||||||
|
// if flat_idx == 0
|
||||||
|
let mut span_start = flat.0.start;
|
||||||
|
let mut span_end = flat.0.end - 1 - span_offset;
|
||||||
|
|
||||||
|
if flat_idx != 0 {
|
||||||
|
span_start = flat.0.start - span_offset;
|
||||||
|
span_end = flat.0.end - 1 - span_offset;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// specially check if it is currently empty - always complete commands
|
if span_end < span_start {
|
||||||
if flat_idx == 0 && working_set.get_span_contents(new_span).is_empty() {
|
span_start = flat.0.start;
|
||||||
let mut completer = CommandCompletion::new(
|
span_end = flat.0.end - 1;
|
||||||
self.engine_state.clone(),
|
offset += span_offset
|
||||||
&working_set,
|
}
|
||||||
flattened.clone(),
|
|
||||||
// flat_idx,
|
|
||||||
FlatShape::String,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Completions that depends on the previous expression (e.g: use, source-env)
|
let new_span = Span::new(span_start, span_end);
|
||||||
if flat_idx > 0 {
|
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
|
||||||
// Read the content for the previous expression
|
|
||||||
let prev_expr_str =
|
|
||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
|
||||||
|
|
||||||
// Completion for .nu files
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
if prev_expr_str == b"use" || prev_expr_str == b"source-env" {
|
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
let mut completer =
|
let index = pos - (flat.0.start - span_offset);
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
prefix.drain(index..);
|
||||||
|
|
||||||
return self.process_completion(
|
// Variables completion
|
||||||
&mut completer,
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
&working_set,
|
let mut completer = VariableCompletion::new(
|
||||||
prefix,
|
self.engine_state.clone(),
|
||||||
new_span,
|
self.stack.clone(),
|
||||||
offset,
|
most_left_var.unwrap_or((vec![], vec![])),
|
||||||
pos,
|
|
||||||
);
|
);
|
||||||
} else if prev_expr_str == b"ls" {
|
|
||||||
let mut completer =
|
if offset > new_span.start {
|
||||||
FileCompletion::new(self.engine_state.clone());
|
offset -= span_offset;
|
||||||
|
}
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
@ -274,101 +206,190 @@ impl NuCompleter {
|
|||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match other types
|
// Flags completion
|
||||||
match &flat.1 {
|
if prefix.starts_with(b"-") {
|
||||||
FlatShape::Custom(decl_id) => {
|
// Try to complete flag internally
|
||||||
let mut completer = CustomCompletion::new(
|
let mut completer = FlagCompletion::new(expr.clone());
|
||||||
self.engine_state.clone(),
|
let result = self.process_completion(
|
||||||
self.stack.clone(),
|
&mut completer,
|
||||||
*decl_id,
|
&working_set,
|
||||||
initial_line,
|
prefix.clone(),
|
||||||
);
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
return self.process_completion(
|
if !result.is_empty() {
|
||||||
&mut completer,
|
return result;
|
||||||
&working_set,
|
}
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FlatShape::Directory => {
|
|
||||||
let mut completer =
|
|
||||||
DirectoryCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
// We got no results for internal completion
|
||||||
&mut completer,
|
// now we can check if external completer is set and use it
|
||||||
&working_set,
|
if let Some(block_id) = config.external_completer {
|
||||||
prefix,
|
if let Some(external_result) = self
|
||||||
new_span,
|
.external_completion(block_id, &spans, offset, new_span)
|
||||||
offset,
|
{
|
||||||
pos,
|
return external_result;
|
||||||
);
|
}
|
||||||
}
|
|
||||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
|
||||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
flat_shape => {
|
|
||||||
let mut completer = CommandCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
&working_set,
|
|
||||||
flattened.clone(),
|
|
||||||
// flat_idx,
|
|
||||||
flat_shape.clone(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut out: Vec<_> = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix.clone(),
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !out.is_empty() {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to complete using an external completer (if set)
|
|
||||||
if let Some(block_id) = config.external_completer {
|
|
||||||
if let Some(external_result) =
|
|
||||||
self.external_completion(block_id, &spans, offset, new_span)
|
|
||||||
{
|
|
||||||
return external_result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for file completion
|
// specially check if it is currently empty - always complete commands
|
||||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
if flat_idx == 0
|
||||||
out = self.process_completion(
|
&& working_set.get_span_contents(new_span).is_empty()
|
||||||
&mut completer,
|
{
|
||||||
&working_set,
|
let mut completer = CommandCompletion::new(
|
||||||
prefix,
|
self.engine_state.clone(),
|
||||||
new_span,
|
&working_set,
|
||||||
offset,
|
flattened.clone(),
|
||||||
pos,
|
// flat_idx,
|
||||||
);
|
FlatShape::String,
|
||||||
|
true,
|
||||||
if !out.is_empty() {
|
);
|
||||||
return out;
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||||
|
if flat_idx > 0 {
|
||||||
|
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||||
|
// Read the content for the previous expression
|
||||||
|
let prev_expr_str =
|
||||||
|
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||||
|
|
||||||
|
// Completion for .nu files
|
||||||
|
if prev_expr_str == b"use" || prev_expr_str == b"source-env"
|
||||||
|
{
|
||||||
|
let mut completer =
|
||||||
|
DotNuCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
} else if prev_expr_str == b"ls" {
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match other types
|
||||||
|
match &flat.1 {
|
||||||
|
FlatShape::Custom(decl_id) => {
|
||||||
|
let mut completer = CustomCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
*decl_id,
|
||||||
|
initial_line,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Directory => {
|
||||||
|
let mut completer =
|
||||||
|
DirectoryCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
flat_shape => {
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
flat_shape.clone(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut out: Vec<_> = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to complete using an external completer (if set)
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) = self.external_completion(
|
||||||
|
block_id, &spans, offset, new_span,
|
||||||
|
) {
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for file completion
|
||||||
|
let mut completer =
|
||||||
|
FileCompletion::new(self.engine_state.clone());
|
||||||
|
out = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,13 +52,13 @@ impl Completer for CustomCompletion {
|
|||||||
head: span,
|
head: span,
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument::Positional(Expression {
|
Argument::Positional(Expression {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
ty: Type::String,
|
ty: Type::String,
|
||||||
expr: Expr::String(self.line.clone()),
|
expr: Expr::String(self.line.clone()),
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}),
|
}),
|
||||||
Argument::Positional(Expression {
|
Argument::Positional(Expression {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
ty: Type::Int,
|
ty: Type::Int,
|
||||||
expr: Expr::Int(line_pos as i64),
|
expr: Expr::Int(line_pos as i64),
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
@ -66,15 +66,16 @@ impl Completer for CustomCompletion {
|
|||||||
],
|
],
|
||||||
redirect_stdout: true,
|
redirect_stdout: true,
|
||||||
redirect_stderr: true,
|
redirect_stderr: true,
|
||||||
|
parser_info: vec![],
|
||||||
},
|
},
|
||||||
PipelineData::new(span),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut custom_completion_options = None;
|
let mut custom_completion_options = None;
|
||||||
|
|
||||||
// Parse result
|
// Parse result
|
||||||
let suggestions = match result {
|
let suggestions = result
|
||||||
Ok(pd) => {
|
.map(|pd| {
|
||||||
let value = pd.into_value(span);
|
let value = pd.into_value(span);
|
||||||
match &value {
|
match &value {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
@ -131,9 +132,8 @@ impl Completer for CustomCompletion {
|
|||||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
_ => vec![],
|
.unwrap_or_default();
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(custom_completion_options) = custom_completion_options {
|
if let Some(custom_completion_options) = custom_completion_options {
|
||||||
filter(&prefix, suggestions, &custom_completion_options)
|
filter(&prefix, suggestions, &custom_completion_options)
|
||||||
|
@ -33,14 +33,7 @@ impl Completer for DirectoryCompletion {
|
|||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
let cwd = self.engine_state.current_work_dir();
|
||||||
match d.as_string() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => "".to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
@ -126,7 +119,7 @@ pub fn directory_completion(
|
|||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
if matches(&partial, &file_name, options) {
|
if matches(&partial, &file_name, options) {
|
||||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||||
format!("{}{}", base_dir_name, file_name)
|
format!("{base_dir_name}{file_name}")
|
||||||
} else {
|
} else {
|
||||||
file_name.to_string()
|
file_name.to_string()
|
||||||
};
|
};
|
||||||
@ -136,9 +129,13 @@ pub fn directory_completion(
|
|||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
// Fix files or folders with quotes or hash
|
||||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
if path.contains('\'')
|
||||||
path = format!("`{}`", path);
|
|| path.contains('"')
|
||||||
|
|| path.contains(' ')
|
||||||
|
|| path.contains('#')
|
||||||
|
{
|
||||||
|
path = format!("`{path}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((span, path))
|
Some((span, path))
|
||||||
|
@ -58,7 +58,7 @@ impl Completer for DotNuCompletion {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check if the base_dir is a folder
|
// Check if the base_dir is a folder
|
||||||
if base_dir != format!(".{}", SEP) {
|
if base_dir != format!(".{SEP}") {
|
||||||
// Add the base dir into the directories to be searched
|
// Add the base dir into the directories to be searched
|
||||||
search_dirs.push(base_dir.clone());
|
search_dirs.push(base_dir.clone());
|
||||||
|
|
||||||
@ -70,14 +70,7 @@ impl Completer for DotNuCompletion {
|
|||||||
partial = base_dir_partial;
|
partial = base_dir_partial;
|
||||||
} else {
|
} else {
|
||||||
// Fetch the current folder
|
// Fetch the current folder
|
||||||
let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
let current_folder = self.engine_state.current_work_dir();
|
||||||
match d.as_string() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => "".to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
is_current_folder = true;
|
is_current_folder = true;
|
||||||
|
|
||||||
// Add the current folder and the lib dirs into the
|
// Add the current folder and the lib dirs into the
|
||||||
|
@ -30,14 +30,7 @@ impl Completer for FileCompletion {
|
|||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
let cwd = self.engine_state.current_work_dir();
|
||||||
match d.as_string() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => "".to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -131,7 +124,7 @@ pub fn file_path_completion(
|
|||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
if matches(&partial, &file_name, options) {
|
if matches(&partial, &file_name, options) {
|
||||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||||
format!("{}{}", base_dir_name, file_name)
|
format!("{base_dir_name}{file_name}")
|
||||||
} else {
|
} else {
|
||||||
file_name.to_string()
|
file_name.to_string()
|
||||||
};
|
};
|
||||||
@ -141,9 +134,15 @@ pub fn file_path_completion(
|
|||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
// Fix files or folders with quotes or hashes
|
||||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
if path.contains('\'')
|
||||||
path = format!("`{}`", path);
|
|| path.contains('"')
|
||||||
|
|| path.contains(' ')
|
||||||
|
|| path.contains('#')
|
||||||
|
|| path.contains('(')
|
||||||
|
|| path.contains(')')
|
||||||
|
{
|
||||||
|
path = format!("`{path}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((span, path))
|
Some((span, path))
|
||||||
@ -171,7 +170,7 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
|||||||
|
|
||||||
/// Returns whether the base_dir should be prepended to the file path
|
/// Returns whether the base_dir should be prepended to the file path
|
||||||
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
||||||
if base_dir == format!(".{}", SEP) {
|
if base_dir == format!(".{SEP}") {
|
||||||
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
||||||
// input already includes a local folder prefix.
|
// input already includes a local folder prefix.
|
||||||
let manually_entered = {
|
let manually_entered = {
|
||||||
|
@ -9,6 +9,8 @@ use reedline::Suggestion;
|
|||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::MatchAlgorithm;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||||
@ -73,10 +75,11 @@ impl Completer for VariableCompletion {
|
|||||||
for suggestion in
|
for suggestion in
|
||||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||||
{
|
{
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
suggestion.value.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,10 +89,11 @@ impl Completer for VariableCompletion {
|
|||||||
} else {
|
} else {
|
||||||
// No nesting provided, return all env vars
|
// No nesting provided, return all env vars
|
||||||
for env_var in env_vars {
|
for env_var in env_vars {
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(env_var.0.as_bytes(), &prefix)
|
env_var.0.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: env_var.0,
|
value: env_var.0,
|
||||||
description: None,
|
description: None,
|
||||||
@ -111,18 +115,16 @@ impl Completer for VariableCompletion {
|
|||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
nu_protocol::NU_VARIABLE_ID,
|
nu_protocol::NU_VARIABLE_ID,
|
||||||
nu_protocol::Span {
|
nu_protocol::Span::new(current_span.start, current_span.end),
|
||||||
start: current_span.start,
|
|
||||||
end: current_span.end,
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
for suggestion in
|
for suggestion in
|
||||||
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
||||||
{
|
{
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
suggestion.value.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,23 +136,18 @@ impl Completer for VariableCompletion {
|
|||||||
// Completion other variable types
|
// Completion other variable types
|
||||||
if let Some(var_id) = var_id {
|
if let Some(var_id) = var_id {
|
||||||
// Extract the variable value from the stack
|
// Extract the variable value from the stack
|
||||||
let var = self.stack.get_var(
|
let var = self.stack.get_var(var_id, Span::new(span.start, span.end));
|
||||||
var_id,
|
|
||||||
Span {
|
|
||||||
start: span.start,
|
|
||||||
end: span.end,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the value exists and it's of type Record
|
// If the value exists and it's of type Record
|
||||||
if let Ok(value) = var {
|
if let Ok(value) = var {
|
||||||
for suggestion in
|
for suggestion in
|
||||||
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
||||||
{
|
{
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
suggestion.value.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,10 +159,11 @@ impl Completer for VariableCompletion {
|
|||||||
|
|
||||||
// Variable completion (e.g: $en<tab> to complete $env)
|
// Variable completion (e.g: $en<tab> to complete $env)
|
||||||
for builtin in builtins {
|
for builtin in builtins {
|
||||||
if options
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
.match_algorithm
|
options.case_sensitive,
|
||||||
.matches_u8(builtin.as_bytes(), &prefix)
|
builtin.as_bytes(),
|
||||||
{
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -187,7 +185,11 @@ impl Completer for VariableCompletion {
|
|||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for v in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
v.0,
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -209,7 +211,11 @@ impl Completer for VariableCompletion {
|
|||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for v in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
|
options.case_sensitive,
|
||||||
|
v.0,
|
||||||
|
&prefix,
|
||||||
|
) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
@ -256,6 +262,20 @@ fn nested_suggestions(
|
|||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
Value::LazyRecord { val, .. } => {
|
||||||
|
// Add all the columns as completion
|
||||||
|
for column_name in val.column_names() {
|
||||||
|
output.push(Suggestion {
|
||||||
|
value: column_name.to_string(),
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
_ => output,
|
_ => output,
|
||||||
}
|
}
|
||||||
@ -281,7 +301,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::Nothing {
|
return Value::Nothing {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => return val,
|
_ => return val,
|
||||||
@ -290,3 +310,13 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MatchAlgorithm {
|
||||||
|
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
|
||||||
|
if sensitive {
|
||||||
|
self.matches_u8(haystack, needle)
|
||||||
|
} else {
|
||||||
|
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
use crate::util::{eval_source, report_error};
|
use crate::util::{eval_source, report_error};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use log::info;
|
|
||||||
#[cfg(feature = "plugin")]
|
|
||||||
use nu_parser::ParseError;
|
use nu_parser::ParseError;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_protocol::Spanned;
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
use nu_protocol::{HistoryFileFormat, PipelineData};
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use nu_utils::utils::perf;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
@ -24,6 +24,8 @@ pub fn read_plugin_file(
|
|||||||
plugin_file: Option<Spanned<String>>,
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
) {
|
) {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let mut plug_path = String::new();
|
||||||
// Reading signatures from signature file
|
// Reading signatures from signature file
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||||
@ -31,19 +33,25 @@ pub fn read_plugin_file(
|
|||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
if let Some(plugin_path) = plugin_path {
|
if let Some(plugin_path) = plugin_path {
|
||||||
let plugin_filename = plugin_path.to_string_lossy();
|
let plugin_filename = plugin_path.to_string_lossy();
|
||||||
|
plug_path = plugin_filename.to_string();
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
&plugin_filename,
|
&plugin_filename,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
perf(
|
||||||
|
&format!("read_plugin_file {}", &plug_path),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
@ -56,12 +64,11 @@ pub fn add_plugin_file(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let cwd = working_set.get_cwd();
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
match canonicalize_with(&plugin_file.item, cwd) {
|
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
||||||
Ok(path) => engine_state.plugin_signatures = Some(path),
|
engine_state.plugin_signatures = Some(path)
|
||||||
Err(_) => {
|
} else {
|
||||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
@ -85,7 +92,7 @@ pub fn eval_config_contents(
|
|||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
&config_filename,
|
&config_filename,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge the environment in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
|
@ -2,13 +2,13 @@ use crate::util::{eval_source, report_error};
|
|||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_engine::convert_env_values;
|
use nu_engine::{convert_env_values, current_dir};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::Type;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Value,
|
Config, PipelineData, ShellError, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::stdout_write_all_and_flush;
|
use nu_utils::stdout_write_all_and_flush;
|
||||||
|
|
||||||
@ -27,12 +27,75 @@ pub fn evaluate_file(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = std::fs::read(&path).into_diagnostic()?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
|
|
||||||
|
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::FileNotFoundCustom(
|
||||||
|
format!("Could not access file '{}': {:?}", path, e.to_string()),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let file_path_str = file_path.to_str().unwrap_or_else(|| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::NonUtf8Custom(
|
||||||
|
format!(
|
||||||
|
"Input file name '{}' is not valid UTF8",
|
||||||
|
file_path.to_string_lossy()
|
||||||
|
),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let file = std::fs::read(&file_path)
|
||||||
|
.into_diagnostic()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::FileNotFoundCustom(
|
||||||
|
format!(
|
||||||
|
"Could not read file '{}': {:?}",
|
||||||
|
file_path_str,
|
||||||
|
e.to_string()
|
||||||
|
),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
engine_state.start_in_file(Some(file_path_str));
|
||||||
|
|
||||||
|
let parent = file_path.parent().unwrap_or_else(|| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ShellError::FileNotFoundCustom(
|
||||||
|
format!("The file path '{file_path_str}' does not have a parent"),
|
||||||
|
Span::unknown(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
stack.add_env_var(
|
||||||
|
"FILE_PWD".to_string(),
|
||||||
|
Value::string(parent.to_string_lossy(), Span::unknown()),
|
||||||
|
);
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
trace!("parsing file: {}", path);
|
trace!("parsing file: {}", file_path_str);
|
||||||
|
let _ = parse(&mut working_set, Some(file_path_str), &file, false, &[]);
|
||||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
|
||||||
|
|
||||||
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||||
let args = format!("main {}", args.join(" "));
|
let args = format!("main {}", args.join(" "));
|
||||||
@ -41,15 +104,15 @@ pub fn evaluate_file(
|
|||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&file,
|
&file,
|
||||||
&path,
|
file_path_str,
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
) {
|
) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
} else if !eval_source(engine_state, stack, &file, file_path_str, input) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +121,7 @@ pub fn evaluate_file(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_table_or_error(
|
pub(crate) fn print_table_or_error(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
mut pipeline_data: PipelineData,
|
mut pipeline_data: PipelineData,
|
||||||
@ -72,37 +135,38 @@ pub fn print_table_or_error(
|
|||||||
// Change the engine_state config to use the passed in configuration
|
// Change the engine_state config to use the passed in configuration
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config);
|
||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||||
Some(decl_id) => {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let command = engine_state.get_decl(decl_id);
|
report_error(&working_set, error);
|
||||||
if command.get_block_id().is_some() {
|
std::process::exit(1);
|
||||||
print_or_exit(pipeline_data, engine_state, config);
|
}
|
||||||
} else {
|
|
||||||
let table = command.run(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
&Call::new(Span::new(0, 0)),
|
|
||||||
pipeline_data,
|
|
||||||
);
|
|
||||||
|
|
||||||
match table {
|
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Ok(table) => {
|
let command = engine_state.get_decl(decl_id);
|
||||||
print_or_exit(table, engine_state, config);
|
if command.get_block_id().is_some() {
|
||||||
}
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
Err(error) => {
|
} else {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let table = command.run(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&Call::new(Span::new(0, 0)),
|
||||||
|
pipeline_data,
|
||||||
|
);
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
match table {
|
||||||
|
Ok(table) => {
|
||||||
std::process::exit(1);
|
print_or_exit(table, engine_state, config);
|
||||||
}
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &error);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
} else {
|
||||||
print_or_exit(pipeline_data, engine_state, config);
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Make sure everything has finished
|
// Make sure everything has finished
|
||||||
if let Some(exit_code) = exit_code {
|
if let Some(exit_code) = exit_code {
|
||||||
@ -128,9 +192,7 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out = item.into_string("\n", config);
|
let out = item.into_string("\n", config) + "\n";
|
||||||
out.push('\n');
|
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
||||||
|
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,10 +411,10 @@ impl DescriptionMenu {
|
|||||||
RESET
|
RESET
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(" {}\r\n", example)
|
format!(" {example}\r\n")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
format!(" {}\r\n", example)
|
format!(" {example}\r\n")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -429,7 +429,7 @@ impl DescriptionMenu {
|
|||||||
examples,
|
examples,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("\r\n\r\nExamples:\r\n{}", examples,)
|
format!("\r\n\r\nExamples:\r\n{examples}",)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ impl NuHelpCompleter {
|
|||||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||||
let mut commands = full_commands
|
let mut commands = full_commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(sig, _, _, _)| {
|
.filter(|(sig, _, _, _, _)| {
|
||||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||||
|| sig
|
|| sig
|
||||||
@ -31,7 +31,7 @@ impl NuHelpCompleter {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
commands.sort_by(|(a, _, _, _), (b, _, _, _)| {
|
commands.sort_by(|(a, _, _, _, _), (b, _, _, _, _)| {
|
||||||
let a_distance = levenshtein_distance(line, &a.name);
|
let a_distance = levenshtein_distance(line, &a.name);
|
||||||
let b_distance = levenshtein_distance(line, &b.name);
|
let b_distance = levenshtein_distance(line, &b.name);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
@ -39,7 +39,7 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
commands
|
commands
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(sig, examples, _, _)| {
|
.map(|(sig, examples, _, _, _)| {
|
||||||
let mut long_desc = String::new();
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
let usage = &sig.usage;
|
let usage = &sig.usage;
|
||||||
|
@ -42,20 +42,14 @@ impl Completer for NuMenuCompleter {
|
|||||||
|
|
||||||
if let Some(buffer) = block.signature.get_positional(0) {
|
if let Some(buffer) = block.signature.get_positional(0) {
|
||||||
if let Some(buffer_id) = &buffer.var_id {
|
if let Some(buffer_id) = &buffer.var_id {
|
||||||
let line_buffer = Value::String {
|
let line_buffer = Value::string(parsed.remainder, self.span);
|
||||||
val: parsed.remainder.to_string(),
|
|
||||||
span: self.span,
|
|
||||||
};
|
|
||||||
self.stack.add_var(*buffer_id, line_buffer);
|
self.stack.add_var(*buffer_id, line_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(position) = block.signature.get_positional(1) {
|
if let Some(position) = block.signature.get_positional(1) {
|
||||||
if let Some(position_id) = &position.var_id {
|
if let Some(position_id) = &position.var_id {
|
||||||
let line_buffer = Value::Int {
|
let line_buffer = Value::int(pos as i64, self.span);
|
||||||
val: pos as i64,
|
|
||||||
span: self.span,
|
|
||||||
};
|
|
||||||
self.stack.add_var(*position_id, line_buffer);
|
self.stack.add_var(*position_id, line_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,13 +81,10 @@ fn convert_to_suggestions(
|
|||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
match value {
|
match value {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
let text = match value
|
let text = value
|
||||||
.get_data_by_key("value")
|
.get_data_by_key("value")
|
||||||
.and_then(|val| val.as_string().ok())
|
.and_then(|val| val.as_string().ok())
|
||||||
{
|
.unwrap_or_else(|| "No value key".to_string());
|
||||||
Some(val) => val,
|
|
||||||
None => "No value key".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let description = value
|
let description = value
|
||||||
.get_data_by_key("description")
|
.get_data_by_key("description")
|
||||||
@ -163,7 +154,7 @@ fn convert_to_suggestions(
|
|||||||
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||||
.collect(),
|
.collect(),
|
||||||
_ => vec![Suggestion {
|
_ => vec![Suggestion {
|
||||||
value: format!("Not a record: {:?}", value),
|
value: format!("Not a record: {value:?}"),
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
||||||
use reedline::Highlighter;
|
use reedline::Highlighter;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -12,7 +12,9 @@ impl Command for NuHighlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("nu-highlight").category(Category::Strings)
|
Signature::build("nu-highlight")
|
||||||
|
.category(Category::Strings)
|
||||||
|
.input_output_types(vec![(Type::String, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -33,7 +35,7 @@ impl Command for NuHighlight {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let engine_state = engine_state.clone();
|
let engine_state = std::sync::Arc::new(engine_state.clone());
|
||||||
let config = engine_state.get_config().clone();
|
let config = engine_state.get_config().clone();
|
||||||
|
|
||||||
let highlighter = crate::NuHighlighter {
|
let highlighter = crate::NuHighlighter {
|
||||||
|
@ -2,7 +2,8 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +16,7 @@ impl Command for Print {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("print")
|
Signature::build("print")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||||
.switch(
|
.switch(
|
||||||
"no-newline",
|
"no-newline",
|
||||||
@ -50,14 +52,13 @@ Since this command has no output, there is no point in piping it with other comm
|
|||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag("no-newline");
|
let no_newline = call.has_flag("no-newline");
|
||||||
let to_stderr = call.has_flag("stderr");
|
let to_stderr = call.has_flag("stderr");
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg.into_pipeline_data()
|
arg.into_pipeline_data()
|
||||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::new(head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
@ -91,7 +91,7 @@ impl NushellPrompt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||||
format!("({})", str)
|
format!("({str})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ impl Prompt for NushellPrompt {
|
|||||||
if let Some(prompt_string) = &self.left_prompt_string {
|
if let Some(prompt_string) = &self.left_prompt_string {
|
||||||
prompt_string.replace('\n', "\r\n").into()
|
prompt_string.replace('\n', "\r\n").into()
|
||||||
} else {
|
} else {
|
||||||
let default = DefaultPrompt::new();
|
let default = DefaultPrompt::default();
|
||||||
default
|
default
|
||||||
.render_prompt_left()
|
.render_prompt_left()
|
||||||
.to_string()
|
.to_string()
|
||||||
@ -118,7 +118,7 @@ impl Prompt for NushellPrompt {
|
|||||||
if let Some(prompt_string) = &self.right_prompt_string {
|
if let Some(prompt_string) = &self.right_prompt_string {
|
||||||
prompt_string.replace('\n', "\r\n").into()
|
prompt_string.replace('\n', "\r\n").into()
|
||||||
} else {
|
} else {
|
||||||
let default = DefaultPrompt::new();
|
let default = DefaultPrompt::default();
|
||||||
default
|
default
|
||||||
.render_prompt_right()
|
.render_prompt_right()
|
||||||
.to_string()
|
.to_string()
|
||||||
@ -130,32 +130,36 @@ impl Prompt for NushellPrompt {
|
|||||||
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||||
match edit_mode {
|
match edit_mode {
|
||||||
PromptEditMode::Default => match &self.default_prompt_indicator {
|
PromptEditMode::Default => match &self.default_prompt_indicator {
|
||||||
Some(indicator) => indicator.as_str().into(),
|
Some(indicator) => indicator,
|
||||||
None => "〉".into(),
|
None => "〉",
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
||||||
Some(indicator) => indicator.as_str().into(),
|
Some(indicator) => indicator,
|
||||||
None => "〉".into(),
|
None => "〉",
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||||
Some(indicator) => indicator.as_str().into(),
|
Some(indicator) => indicator,
|
||||||
None => ": ".into(),
|
None => ": ",
|
||||||
},
|
},
|
||||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||||
Some(indicator) => indicator.as_str().into(),
|
Some(indicator) => indicator,
|
||||||
None => "〉".into(),
|
None => "〉",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||||
match &self.default_multiline_indicator {
|
match &self.default_multiline_indicator {
|
||||||
Some(indicator) => indicator.as_str().into(),
|
Some(indicator) => indicator,
|
||||||
None => "::: ".into(),
|
None => "::: ",
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_prompt_history_search_indicator(
|
fn render_prompt_history_search_indicator(
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::util::report_error;
|
use crate::util::report_error;
|
||||||
use crate::NushellPrompt;
|
use crate::NushellPrompt;
|
||||||
use log::info;
|
use log::trace;
|
||||||
use nu_engine::eval_subexpression;
|
use nu_engine::eval_subexpression;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Span, Value,
|
Config, PipelineData, Value,
|
||||||
};
|
};
|
||||||
use reedline::Prompt;
|
use reedline::Prompt;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ fn get_prompt_string(
|
|||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val: block_id,
|
val: block_id,
|
||||||
captures,
|
captures,
|
||||||
..
|
..
|
||||||
@ -37,27 +37,39 @@ fn get_prompt_string(
|
|||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
let mut stack = stack.captures_to_stack(&captures);
|
let mut stack = stack.captures_to_stack(&captures);
|
||||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||||
let ret_val = eval_subexpression(
|
let ret_val =
|
||||||
engine_state,
|
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
||||||
&mut stack,
|
trace!(
|
||||||
block,
|
|
||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
|
|
||||||
match ret_val {
|
ret_val
|
||||||
Ok(ret_val) => Some(ret_val),
|
.map_err(|err| {
|
||||||
Err(err) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
None
|
})
|
||||||
}
|
.ok()
|
||||||
}
|
}
|
||||||
|
Value::Block { val: block_id, .. } => {
|
||||||
|
let block = engine_state.get_block(block_id);
|
||||||
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||||
|
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
||||||
|
trace!(
|
||||||
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
);
|
||||||
|
|
||||||
|
ret_val
|
||||||
|
.map_err(|err| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -65,20 +77,17 @@ fn get_prompt_string(
|
|||||||
.and_then(|pipeline_data| {
|
.and_then(|pipeline_data| {
|
||||||
let output = pipeline_data.collect_string("", config).ok();
|
let output = pipeline_data.collect_string("", config).ok();
|
||||||
|
|
||||||
match output {
|
output.map(|mut x| {
|
||||||
Some(mut x) => {
|
// Just remove the very last newline.
|
||||||
// Just remove the very last newline.
|
if x.ends_with('\n') {
|
||||||
if x.ends_with('\n') {
|
x.pop();
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.ends_with('\r') {
|
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
Some(x)
|
|
||||||
}
|
}
|
||||||
None => None,
|
|
||||||
}
|
if x.ends_with('\r') {
|
||||||
|
x.pop();
|
||||||
|
}
|
||||||
|
x
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,12 +104,12 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
// Now that we have the prompt string lets ansify it.
|
// Now that we have the prompt string lets ansify it.
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
let left_prompt_string = if config.shell_integration {
|
let left_prompt_string = if config.shell_integration {
|
||||||
match left_prompt_string {
|
if let Some(prompt_string) = left_prompt_string {
|
||||||
Some(prompt_string) => Some(format!(
|
Some(format!(
|
||||||
"{}{}{}",
|
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
||||||
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
))
|
||||||
)),
|
} else {
|
||||||
None => left_prompt_string,
|
left_prompt_string
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
left_prompt_string
|
left_prompt_string
|
||||||
@ -132,7 +141,7 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
let ret_val = nu_prompt as &dyn Prompt;
|
||||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
use super::DescriptionMenu;
|
use super::DescriptionMenu;
|
||||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use nu_color_config::lookup_ansi_color_style;
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
color_value_string, create_menus,
|
create_menus,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
|
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value,
|
||||||
ShellError, Span, Value,
|
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||||
@ -110,7 +109,7 @@ pub(crate) fn add_menus(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut temp_stack = Stack::new();
|
let mut temp_stack = Stack::new();
|
||||||
let input = Value::nothing(Span::test_data()).into_pipeline_data();
|
let input = PipelineData::Empty;
|
||||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
@ -159,14 +158,11 @@ macro_rules! add_style {
|
|||||||
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||||
$menu = match extract_value($name, $cols, $vals, $span) {
|
$menu = match extract_value($name, $cols, $vals, $span) {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let text = match text {
|
let style = match text {
|
||||||
Value::String { val, .. } => val.clone(),
|
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||||
Value::Record { cols, vals, span } => {
|
Value::Record { .. } => color_record_to_nustyle(&text),
|
||||||
color_value_string(span, cols, vals, $config).into_string("", $config)
|
_ => lookup_ansi_color_style("green"),
|
||||||
}
|
|
||||||
_ => "green".to_string(),
|
|
||||||
};
|
};
|
||||||
let style = lookup_ansi_color_style(&text);
|
|
||||||
$f($menu, style)
|
$f($menu, style)
|
||||||
}
|
}
|
||||||
Err(_) => $menu,
|
Err(_) => $menu,
|
||||||
@ -251,7 +247,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -337,7 +333,7 @@ pub(crate) fn add_list_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -459,7 +455,7 @@ pub(crate) fn add_description_menu(
|
|||||||
completer,
|
completer,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val,
|
val,
|
||||||
captures,
|
captures,
|
||||||
span,
|
span,
|
||||||
@ -477,7 +473,7 @@ pub(crate) fn add_description_menu(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or omitted value".to_string(),
|
"closure or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span()?,
|
menu.source.span()?,
|
||||||
)),
|
)),
|
||||||
@ -655,14 +651,15 @@ fn add_parsed_keybinding(
|
|||||||
let pos1 = char_iter.next();
|
let pos1 = char_iter.next();
|
||||||
let pos2 = char_iter.next();
|
let pos2 = char_iter.next();
|
||||||
|
|
||||||
let char = match (pos1, pos2) {
|
let char = if let (Some(char), None) = (pos1, pos2) {
|
||||||
(Some(char), None) => Ok(char),
|
char
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
} else {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"char_<CHAR: unicode codepoint>".to_string(),
|
"char_<CHAR: unicode codepoint>".to_string(),
|
||||||
c.to_string(),
|
c.to_string(),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span()?,
|
||||||
)),
|
));
|
||||||
}?;
|
};
|
||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
}
|
}
|
||||||
@ -683,10 +680,10 @@ fn add_parsed_keybinding(
|
|||||||
let fn_num: u8 = c[1..]
|
let fn_num: u8 = c[1..]
|
||||||
.parse()
|
.parse()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|num| matches!(num, 1..=12))
|
.filter(|num| matches!(num, 1..=20))
|
||||||
.ok_or(ShellError::UnsupportedConfigValue(
|
.ok_or(ShellError::UnsupportedConfigValue(
|
||||||
"(f1|f2|...|f12)".to_string(),
|
"(f1|f2|...|f20)".to_string(),
|
||||||
format!("unknown function key: {}", c),
|
format!("unknown function key: {c}"),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span()?,
|
||||||
))?;
|
))?;
|
||||||
KeyCode::F(fn_num)
|
KeyCode::F(fn_num)
|
||||||
@ -993,10 +990,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_send_event() {
|
fn test_send_event() {
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||||
@ -1016,10 +1010,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_edit_event() {
|
fn test_edit_event() {
|
||||||
let cols = vec!["edit".to_string()];
|
let cols = vec!["edit".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Clear")];
|
||||||
val: "Clear".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||||
@ -1043,14 +1034,8 @@ mod test {
|
|||||||
fn test_send_menu() {
|
fn test_send_menu() {
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
@ -1076,14 +1061,8 @@ mod test {
|
|||||||
// Menu event
|
// Menu event
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1094,10 +1073,7 @@ mod test {
|
|||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1138,14 +1114,8 @@ mod test {
|
|||||||
// Menu event
|
// Menu event
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let cols = vec!["send".to_string(), "name".to_string()];
|
||||||
let vals = vec![
|
let vals = vec![
|
||||||
Value::String {
|
Value::test_string("Menu"),
|
||||||
val: "Menu".to_string(),
|
Value::test_string("history_menu"),
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "history_menu".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1156,10 +1126,7 @@ mod test {
|
|||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1187,10 +1154,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_error() {
|
fn test_error() {
|
||||||
let cols = vec!["not_exist".to_string()];
|
let cols = vec!["not_exist".to_string()];
|
||||||
let vals = vec![Value::String {
|
let vals = vec![Value::test_string("Enter")];
|
||||||
val: "Enter".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||||
|
@ -5,20 +5,21 @@ use crate::{
|
|||||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||||
NuHighlighter, NuValidator, NushellPrompt,
|
NuHighlighter, NuValidator, NushellPrompt,
|
||||||
};
|
};
|
||||||
use fancy_regex::Regex;
|
use crossterm::cursor::CursorShape;
|
||||||
use lazy_static::lazy_static;
|
use log::{trace, warn};
|
||||||
use log::{info, trace, warn};
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::get_color_config;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::PathMember,
|
ast::PathMember,
|
||||||
|
config::NuCursorShape,
|
||||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||||
Spanned, Type, Value, VarId,
|
Spanned, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
use nu_utils::utils::perf;
|
||||||
|
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
@ -41,6 +42,7 @@ pub fn evaluate_repl(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
nushell_path: &str,
|
nushell_path: &str,
|
||||||
prerun_command: Option<Spanned<String>>,
|
prerun_command: Option<Spanned<String>>,
|
||||||
|
entire_start_time: Instant,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||||
|
|
||||||
@ -58,63 +60,47 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let mut nu_prompt = NushellPrompt::new();
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
|
||||||
info!(
|
let start_time = std::time::Instant::now();
|
||||||
"translate environment vars {}:{}:{}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
"translate env vars",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
Value::String {
|
Value::string("0823", Span::unknown()),
|
||||||
val: "0823".to_string(),
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||||
"LAST_EXIT_CODE".into(),
|
|
||||||
Value::Int {
|
|
||||||
val: 0,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"load config initially {}:{}:{}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
|
let mut start_time = std::time::Instant::now();
|
||||||
let mut line_editor = Reedline::create();
|
let mut line_editor = Reedline::create();
|
||||||
|
|
||||||
// Now that reedline is created, get the history session id and store it in engine_state
|
// Now that reedline is created, get the history session id and store it in engine_state
|
||||||
let hist_sesh = match line_editor.get_history_session_id() {
|
let hist_sesh = line_editor
|
||||||
Some(id) => i64::from(id),
|
.get_history_session_id()
|
||||||
None => 0,
|
.map(i64::from)
|
||||||
};
|
.unwrap_or(0);
|
||||||
engine_state.history_session_id = hist_sesh;
|
engine_state.history_session_id = hist_sesh;
|
||||||
|
perf("setup reedline", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let history_path = crate::config_files::get_history_path(
|
let history_path = crate::config_files::get_history_path(
|
||||||
nushell_path,
|
nushell_path,
|
||||||
engine_state.config.history_file_format,
|
engine_state.config.history_file_format,
|
||||||
);
|
);
|
||||||
if let Some(history_path) = history_path.as_deref() {
|
if let Some(history_path) = history_path.as_deref() {
|
||||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||||
HistoryFileFormat::PlainText => Box::new(
|
HistoryFileFormat::PlainText => Box::new(
|
||||||
FileBackedHistory::with_file(
|
FileBackedHistory::with_file(
|
||||||
@ -129,7 +115,9 @@ pub fn evaluate_repl(
|
|||||||
};
|
};
|
||||||
line_editor = line_editor.with_history(history);
|
line_editor = line_editor.with_history(history);
|
||||||
};
|
};
|
||||||
|
perf("setup history", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let sys = sysinfo::System::new();
|
let sys = sysinfo::System::new();
|
||||||
|
|
||||||
let show_banner = config.show_banner;
|
let show_banner = config.show_banner;
|
||||||
@ -137,63 +125,89 @@ pub fn evaluate_repl(
|
|||||||
if show_banner {
|
if show_banner {
|
||||||
let banner = get_banner(engine_state, stack);
|
let banner = get_banner(engine_state, stack);
|
||||||
if use_ansi {
|
if use_ansi {
|
||||||
println!("{}", banner);
|
println!("{banner}");
|
||||||
} else {
|
} else {
|
||||||
println!("{}", nu_utils::strip_ansi_string_likely(banner));
|
println!("{}", nu_utils::strip_ansi_string_likely(banner));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
"get sysinfo/show banner",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(s) = prerun_command {
|
if let Some(s) = prerun_command {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
s.item.as_bytes(),
|
s.item.as_bytes(),
|
||||||
&format!("entry #{}", entry_num),
|
&format!("entry #{entry_num}"),
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
info!(
|
let loop_start_time = std::time::Instant::now();
|
||||||
"load config each loop {}:{}:{}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||||
// permanent state.
|
// permanent state.
|
||||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
|
perf("merge env", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
//Reset the ctrl-c handler
|
//Reset the ctrl-c handler
|
||||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||||
ctrlc.store(false, Ordering::SeqCst);
|
ctrlc.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
perf("reset ctrlc", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
// Reset the SIGQUIT handler
|
// Reset the SIGQUIT handler
|
||||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
||||||
sig_quit.store(false, Ordering::SeqCst);
|
sig_quit.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
perf("reset sig_quit", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
let color_hm = get_color_config(config);
|
|
||||||
|
|
||||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
|
||||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||||
|
|
||||||
|
// Find the configured cursor shapes for each mode
|
||||||
|
let cursor_config = CursorConfig {
|
||||||
|
vi_insert: Some(map_nucursorshape_to_cursorshape(
|
||||||
|
config.cursor_shape_vi_insert,
|
||||||
|
)),
|
||||||
|
vi_normal: Some(map_nucursorshape_to_cursorshape(
|
||||||
|
config.cursor_shape_vi_normal,
|
||||||
|
)),
|
||||||
|
emacs: Some(map_nucursorshape_to_cursorshape(config.cursor_shape_emacs)),
|
||||||
|
};
|
||||||
|
perf(
|
||||||
|
"get config/cursor config",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
line_editor = line_editor
|
line_editor = line_editor
|
||||||
.with_highlighter(Box::new(NuHighlighter {
|
.with_highlighter(Box::new(NuHighlighter {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_reference.clone(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
}))
|
}))
|
||||||
.with_validator(Box::new(NuValidator {
|
.with_validator(Box::new(NuValidator {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_reference.clone(),
|
||||||
}))
|
}))
|
||||||
.with_completer(Box::new(NuCompleter::new(
|
.with_completer(Box::new(NuCompleter::new(
|
||||||
engine_reference.clone(),
|
engine_reference.clone(),
|
||||||
@ -201,25 +215,39 @@ pub fn evaluate_repl(
|
|||||||
)))
|
)))
|
||||||
.with_quick_completions(config.quick_completions)
|
.with_quick_completions(config.quick_completions)
|
||||||
.with_partial_completions(config.partial_completions)
|
.with_partial_completions(config.partial_completions)
|
||||||
.with_ansi_colors(config.use_ansi_coloring);
|
.with_ansi_colors(config.use_ansi_coloring)
|
||||||
|
.with_cursor_config(cursor_config);
|
||||||
|
perf("reedline builder", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
line_editor = if config.use_ansi_coloring {
|
line_editor = if config.use_ansi_coloring {
|
||||||
line_editor.with_hinter(Box::new(
|
line_editor.with_hinter(Box::new({
|
||||||
DefaultHinter::default().with_style(color_hm["hints"]),
|
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||||
))
|
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||||
|
DefaultHinter::default().with_style(style)
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
line_editor.disable_hints()
|
line_editor.disable_hints()
|
||||||
};
|
};
|
||||||
|
perf(
|
||||||
|
"reedline coloring/style_computer",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
line_editor = match add_menus(line_editor, engine_reference, stack, config) {
|
start_time = std::time::Instant::now();
|
||||||
Ok(line_editor) => line_editor,
|
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
||||||
Err(e) => {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
report_error(&working_set, &e);
|
||||||
report_error(&working_set, &e);
|
Reedline::create()
|
||||||
Reedline::create()
|
});
|
||||||
}
|
perf("reedline menus", start_time, file!(), line!(), column!());
|
||||||
};
|
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let buffer_editor = if !config.buffer_editor.is_empty() {
|
let buffer_editor = if !config.buffer_editor.is_empty() {
|
||||||
Some(config.buffer_editor.clone())
|
Some(config.buffer_editor.clone())
|
||||||
} else {
|
} else {
|
||||||
@ -240,17 +268,23 @@ pub fn evaluate_repl(
|
|||||||
} else {
|
} else {
|
||||||
line_editor
|
line_editor
|
||||||
};
|
};
|
||||||
|
perf(
|
||||||
|
"reedline buffer_editor",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
if config.sync_history_on_enter {
|
if config.sync_history_on_enter {
|
||||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
if let Err(e) = line_editor.sync_history() {
|
if let Err(e) = line_editor.sync_history() {
|
||||||
warn!("Failed to sync history: {}", e);
|
warn!("Failed to sync history: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf("sync_history", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
// Changing the line editor based on the found keybindings
|
// Changing the line editor based on the found keybindings
|
||||||
line_editor = match create_keybindings(config) {
|
line_editor = match create_keybindings(config) {
|
||||||
Ok(keybindings) => match keybindings {
|
Ok(keybindings) => match keybindings {
|
||||||
@ -272,9 +306,9 @@ pub fn evaluate_repl(
|
|||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
perf("keybindings", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
// Right before we start our prompt and take input from the user,
|
// Right before we start our prompt and take input from the user,
|
||||||
// fire the "pre_prompt" hook
|
// fire the "pre_prompt" hook
|
||||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||||
@ -282,7 +316,9 @@ pub fn evaluate_repl(
|
|||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf("pre-prompt hook", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
// Next, check all the environment variables they ask for
|
// Next, check all the environment variables they ask for
|
||||||
// fire the "env_change" hook
|
// fire the "env_change" hook
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
@ -291,19 +327,23 @@ pub fn evaluate_repl(
|
|||||||
{
|
{
|
||||||
report_error_new(engine_state, &error)
|
report_error_new(engine_state, &error)
|
||||||
}
|
}
|
||||||
|
perf("env-change hook", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
||||||
|
perf("update_prompt", start_time, file!(), line!(), column!());
|
||||||
|
|
||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
info!(
|
if entry_num == 1 && show_banner {
|
||||||
"finished setup, starting repl {}:{}:{}",
|
println!(
|
||||||
file!(),
|
"Startup Time: {}",
|
||||||
line!(),
|
format_duration(entire_start_time.elapsed().as_nanos() as i64)
|
||||||
column!()
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let input = line_editor.read_line(prompt);
|
let input = line_editor.read_line(prompt);
|
||||||
let shell_integration = config.shell_integration;
|
let shell_integration = config.shell_integration;
|
||||||
|
|
||||||
@ -375,7 +415,7 @@ pub fn evaluate_repl(
|
|||||||
"OLDPWD".into(),
|
"OLDPWD".into(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: cwd.clone(),
|
val: cwd.clone(),
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -385,7 +425,7 @@ pub fn evaluate_repl(
|
|||||||
"PWD".into(),
|
"PWD".into(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: path.clone(),
|
val: path.clone(),
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let cwd = Value::String { val: cwd, span };
|
let cwd = Value::String { val: cwd, span };
|
||||||
@ -430,8 +470,8 @@ pub fn evaluate_repl(
|
|||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
s.as_bytes(),
|
s.as_bytes(),
|
||||||
&format!("entry #{}", entry_num),
|
&format!("entry #{entry_num}"),
|
||||||
PipelineData::new(Span::new(0, 0)),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let cmd_duration = start_time.elapsed();
|
let cmd_duration = start_time.elapsed();
|
||||||
@ -440,7 +480,7 @@ pub fn evaluate_repl(
|
|||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: format!("{}", cmd_duration.as_millis()),
|
val: format!("{}", cmd_duration.as_millis()),
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -488,7 +528,7 @@ pub fn evaluate_repl(
|
|||||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||||
// ESC]1;stringBEL -- Set icon name to string
|
// ESC]1;stringBEL -- Set icon name to string
|
||||||
// ESC]2;stringBEL -- Set window title to string
|
// ESC]2;stringBEL -- Set window title to string
|
||||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
||||||
}
|
}
|
||||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||||
}
|
}
|
||||||
@ -528,7 +568,7 @@ pub fn evaluate_repl(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
if !message.contains("duration") {
|
if !message.contains("duration") {
|
||||||
println!("Error: {:?}", err);
|
eprintln!("Error: {err:?}");
|
||||||
// TODO: Identify possible error cases where a hard failure is preferable
|
// TODO: Identify possible error cases where a hard failure is preferable
|
||||||
// Ignoring and reporting could hide bigger problems
|
// Ignoring and reporting could hide bigger problems
|
||||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||||
@ -539,11 +579,34 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
"processing line editor input",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
|
perf(
|
||||||
|
"finished repl loop",
|
||||||
|
loop_start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> CursorShape {
|
||||||
|
match shape {
|
||||||
|
NuCursorShape::Block => CursorShape::Block,
|
||||||
|
NuCursorShape::UnderScore => CursorShape::UnderScore,
|
||||||
|
NuCursorShape::Line => CursorShape::Line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
||||||
let age = match eval_string_with_input(
|
let age = match eval_string_with_input(
|
||||||
engine_state,
|
engine_state,
|
||||||
@ -567,15 +630,7 @@ Our {}Documentation{} is located at {}http://nushell.sh{}
|
|||||||
{}Tweet{} us at {}@nu_shell{}
|
{}Tweet{} us at {}@nu_shell{}
|
||||||
|
|
||||||
It's been this long since {}Nushell{}'s first commit:
|
It's been this long since {}Nushell{}'s first commit:
|
||||||
{}
|
{}{}
|
||||||
|
|
||||||
{}You can disable this banner using the {}config nu{}{} command
|
|
||||||
to modify the config.nu file and setting show_banner to false.
|
|
||||||
|
|
||||||
let-env config = {{
|
|
||||||
show_banner: false
|
|
||||||
...
|
|
||||||
}}{}
|
|
||||||
"#,
|
"#,
|
||||||
"\x1b[32m", //start line 1 green
|
"\x1b[32m", //start line 1 green
|
||||||
"\x1b[32m", //start line 2
|
"\x1b[32m", //start line 2
|
||||||
@ -607,11 +662,7 @@ let-env config = {{
|
|||||||
"\x1b[32m", //before Nushell
|
"\x1b[32m", //before Nushell
|
||||||
"\x1b[0m", //after Nushell
|
"\x1b[0m", //after Nushell
|
||||||
age,
|
age,
|
||||||
"\x1b[2;37m", //before banner disable dim white
|
"\x1b[0m", //after banner disable
|
||||||
"\x1b[2;36m", //before config nu dim cyan
|
|
||||||
"\x1b[0m", //after config nu
|
|
||||||
"\x1b[2;37m", //after config nu dim white
|
|
||||||
"\x1b[0m", //after banner disable
|
|
||||||
);
|
);
|
||||||
|
|
||||||
banner
|
banner
|
||||||
@ -637,7 +688,7 @@ pub fn eval_string_with_input(
|
|||||||
|
|
||||||
let input_as_pipeline_data = match input {
|
let input_as_pipeline_data = match input {
|
||||||
Some(input) => PipelineData::Value(input, None),
|
Some(input) => PipelineData::Value(input, None),
|
||||||
None => PipelineData::new(Span::test_data()),
|
None => PipelineData::empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
eval_block(
|
eval_block(
|
||||||
@ -648,7 +699,7 @@ pub fn eval_string_with_input(
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.map(|x| x.into_value(Span::test_data()))
|
.map(|x| x.into_value(Span::unknown()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||||
@ -718,11 +769,18 @@ pub fn eval_hook(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let value_span = value.span()?;
|
let value_span = value.span()?;
|
||||||
|
|
||||||
|
// Hooks can optionally be a record in this form:
|
||||||
|
// {
|
||||||
|
// condition: {|before, after| ... } # block that evaluates to true/false
|
||||||
|
// code: # block or a string
|
||||||
|
// }
|
||||||
|
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
||||||
|
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||||
let condition_path = PathMember::String {
|
let condition_path = PathMember::String {
|
||||||
val: "condition".to_string(),
|
val: "condition".to_string(),
|
||||||
span: value_span,
|
span: value_span,
|
||||||
};
|
};
|
||||||
let mut output = PipelineData::new(Span::new(0, 0));
|
let mut output = PipelineData::empty();
|
||||||
|
|
||||||
let code_path = PathMember::String {
|
let code_path = PathMember::String {
|
||||||
val: "code".to_string(),
|
val: "code".to_string(),
|
||||||
@ -736,52 +794,63 @@ pub fn eval_hook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
let do_run_hook =
|
let do_run_hook = if let Ok(condition) =
|
||||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
value
|
||||||
match condition {
|
.clone()
|
||||||
Value::Block {
|
.follow_cell_path(&[condition_path], false, false)
|
||||||
val: block_id,
|
{
|
||||||
span: block_span,
|
match condition {
|
||||||
..
|
Value::Block {
|
||||||
} => {
|
val: block_id,
|
||||||
match run_hook_block(
|
span: block_span,
|
||||||
engine_state,
|
..
|
||||||
stack,
|
}
|
||||||
block_id,
|
| Value::Closure {
|
||||||
None,
|
val: block_id,
|
||||||
arguments.clone(),
|
span: block_span,
|
||||||
block_span,
|
..
|
||||||
) {
|
} => {
|
||||||
Ok(value) => match value {
|
match run_hook_block(
|
||||||
Value::Bool { val, .. } => val,
|
engine_state,
|
||||||
other => {
|
stack,
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
block_id,
|
||||||
"boolean output".to_string(),
|
None,
|
||||||
format!("{}", other.get_type()),
|
arguments.clone(),
|
||||||
other.span()?,
|
block_span,
|
||||||
));
|
) {
|
||||||
}
|
Ok(pipeline_data) => {
|
||||||
},
|
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
||||||
Err(err) => {
|
pipeline_data
|
||||||
return Err(err);
|
{
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"boolean output".to_string(),
|
||||||
|
"other PipelineData variant".to_string(),
|
||||||
|
block_span,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(err) => {
|
||||||
other => {
|
return Err(err);
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
}
|
||||||
"block".to_string(),
|
|
||||||
format!("{}", other.get_type()),
|
|
||||||
other.span()?,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
other => {
|
||||||
// always run the hook
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
true
|
"block".to_string(),
|
||||||
};
|
format!("{}", other.get_type()),
|
||||||
|
other.span()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// always run the hook
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
if do_run_hook {
|
if do_run_hook {
|
||||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
match value.clone().follow_cell_path(&[code_path], false, false)? {
|
||||||
Value::String {
|
Value::String {
|
||||||
val,
|
val,
|
||||||
span: source_span,
|
span: source_span,
|
||||||
@ -796,6 +865,7 @@ pub fn eval_hook(
|
|||||||
name.as_bytes().to_vec(),
|
name.as_bytes().to_vec(),
|
||||||
val.span()?,
|
val.span()?,
|
||||||
Type::Any,
|
Type::Any,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
vars.push((var_id, val));
|
vars.push((var_id, val));
|
||||||
@ -817,7 +887,7 @@ pub fn eval_hook(
|
|||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
engine_state.merge_delta(delta)?;
|
||||||
let input = PipelineData::new(value_span);
|
let input = PipelineData::empty();
|
||||||
|
|
||||||
let var_ids: Vec<VarId> = vars
|
let var_ids: Vec<VarId> = vars
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -854,6 +924,20 @@ pub fn eval_hook(
|
|||||||
block_span,
|
block_span,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
block_span,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or string".to_string(),
|
"block or string".to_string(),
|
||||||
@ -869,17 +953,28 @@ pub fn eval_hook(
|
|||||||
span: block_span,
|
span: block_span,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
output = PipelineData::Value(
|
output = run_hook_block(
|
||||||
run_hook_block(
|
engine_state,
|
||||||
engine_state,
|
stack,
|
||||||
stack,
|
*block_id,
|
||||||
*block_id,
|
input,
|
||||||
input,
|
arguments,
|
||||||
arguments,
|
*block_span,
|
||||||
*block_span,
|
)?;
|
||||||
)?,
|
}
|
||||||
None,
|
Value::Closure {
|
||||||
);
|
val: block_id,
|
||||||
|
span: block_span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
output = run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
*block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
*block_span,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
@ -896,17 +991,17 @@ pub fn eval_hook(
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_hook_block(
|
fn run_hook_block(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
optional_input: Option<PipelineData>,
|
optional_input: Option<PipelineData>,
|
||||||
arguments: Vec<(String, Value)>,
|
arguments: Vec<(String, Value)>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let input = optional_input.unwrap_or_else(|| PipelineData::new(span));
|
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
||||||
|
|
||||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
@ -925,62 +1020,58 @@ pub fn run_hook_block(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
let pipeline_data =
|
||||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
||||||
Value::Error { error } => Err(error),
|
|
||||||
val => {
|
|
||||||
// If all went fine, preserve the environment of the called block
|
|
||||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
|
||||||
|
|
||||||
// remove env vars that are present in the caller but not in the callee
|
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
||||||
// (the callee hid them)
|
return Err(error);
|
||||||
for var in caller_env_vars.iter() {
|
|
||||||
if !callee_stack.has_env_var(engine_state, var) {
|
|
||||||
stack.remove_env_var(engine_state, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new env vars from callee to caller
|
|
||||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
|
||||||
stack.add_env_var(var, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If all went fine, preserve the environment of the called block
|
||||||
|
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||||
|
|
||||||
|
// remove env vars that are present in the caller but not in the callee
|
||||||
|
// (the callee hid them)
|
||||||
|
for var in caller_env_vars.iter() {
|
||||||
|
if !callee_stack.has_env_var(engine_state, var) {
|
||||||
|
stack.remove_env_var(engine_state, var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new env vars from callee to caller
|
||||||
|
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||||
|
stack.add_env_var(var, value);
|
||||||
|
}
|
||||||
|
Ok(pipeline_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||||
match io::stdout().write_all(seq.as_bytes()) {
|
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
|
||||||
Ok(it) => it,
|
ShellError::GenericError(
|
||||||
Err(err) => {
|
"Error writing ansi sequence".into(),
|
||||||
return Err(ShellError::GenericError(
|
e.to_string(),
|
||||||
"Error writing ansi sequence".into(),
|
Some(Span::unknown()),
|
||||||
err.to_string(),
|
None,
|
||||||
Some(Span { start: 0, end: 0 }),
|
Vec::new(),
|
||||||
None,
|
)
|
||||||
Vec::new(),
|
})?;
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
io::stdout().flush().map_err(|e| {
|
io::stdout().flush().map_err(|e| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Error flushing stdio".into(),
|
"Error flushing stdio".into(),
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
Some(Span { start: 0, end: 0 }),
|
Some(Span::unknown()),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
#[cfg(windows)]
|
||||||
static ref DRIVE_PATH_REGEX: Regex =
|
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> =
|
||||||
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
|
once_cell::sync::Lazy::new(|| {
|
||||||
}
|
fancy_regex::Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation")
|
||||||
|
});
|
||||||
|
|
||||||
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||||
fn looks_like_path(orig: &str) -> bool {
|
fn looks_like_path(orig: &str) -> bool {
|
||||||
|
@ -2,13 +2,14 @@ use log::trace;
|
|||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
use nu_protocol::ast::{Argument, Block, Expr, Expression};
|
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
use nu_protocol::{Config, Span};
|
use nu_protocol::{Config, Span};
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct NuHighlighter {
|
pub struct NuHighlighter {
|
||||||
pub engine_state: EngineState,
|
pub engine_state: Arc<EngineState>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +121,10 @@ impl Highlighter for NuHighlighter {
|
|||||||
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||||
|
FlatShape::Pipe => add_colored_token!(shape.1, next_token),
|
||||||
|
FlatShape::And => add_colored_token!(shape.1, next_token),
|
||||||
|
FlatShape::Or => add_colored_token!(shape.1, next_token),
|
||||||
|
FlatShape::Redirection => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||||
}
|
}
|
||||||
last_seen_span = shape.0.end;
|
last_seen_span = shape.0.end;
|
||||||
@ -145,7 +150,7 @@ fn split_span_by_highlight_positions(
|
|||||||
for pos in highlight_positions {
|
for pos in highlight_positions {
|
||||||
if start <= *pos && pos < &span.end {
|
if start <= *pos && pos < &span.end {
|
||||||
if start < *pos {
|
if start < *pos {
|
||||||
result.push((Span { start, end: *pos }, false));
|
result.push((Span::new(start, *pos), false));
|
||||||
}
|
}
|
||||||
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
||||||
let end = span_str
|
let end = span_str
|
||||||
@ -153,18 +158,12 @@ fn split_span_by_highlight_positions(
|
|||||||
.next()
|
.next()
|
||||||
.map(|c| pos + get_char_length(c))
|
.map(|c| pos + get_char_length(c))
|
||||||
.unwrap_or(pos + 1);
|
.unwrap_or(pos + 1);
|
||||||
result.push((Span { start: *pos, end }, true));
|
result.push((Span::new(*pos, end), true));
|
||||||
start = end;
|
start = end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start < span.end {
|
if start < span.end {
|
||||||
result.push((
|
result.push((Span::new(start, span.end), false));
|
||||||
Span {
|
|
||||||
start,
|
|
||||||
end: span.end,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -230,16 +229,24 @@ fn find_matching_block_end_in_block(
|
|||||||
global_cursor_offset: usize,
|
global_cursor_offset: usize,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
for p in &block.pipelines {
|
for p in &block.pipelines {
|
||||||
for e in &p.expressions {
|
for e in &p.elements {
|
||||||
if e.span.contains(global_cursor_offset) {
|
match e {
|
||||||
if let Some(pos) = find_matching_block_end_in_expr(
|
PipelineElement::Expression(_, e)
|
||||||
line,
|
| PipelineElement::Redirection(_, _, e)
|
||||||
working_set,
|
| PipelineElement::And(_, e)
|
||||||
e,
|
| PipelineElement::Or(_, e)
|
||||||
global_span_offset,
|
| PipelineElement::SeparateRedirection { out: (_, e), .. } => {
|
||||||
global_cursor_offset,
|
if e.span.contains(global_cursor_offset) {
|
||||||
) {
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
return Some(pos);
|
line,
|
||||||
|
working_set,
|
||||||
|
e,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,6 +354,7 @@ fn find_matching_block_end_in_expr(
|
|||||||
let opt_expr = match arg {
|
let opt_expr = match arg {
|
||||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||||
|
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(inner_expr) = opt_expr {
|
if let Some(inner_expr) = opt_expr {
|
||||||
@ -372,6 +380,7 @@ fn find_matching_block_end_in_expr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::Block(block_id)
|
Expr::Block(block_id)
|
||||||
|
| Expr::Closure(block_id)
|
||||||
| Expr::RowCondition(block_id)
|
| Expr::RowCondition(block_id)
|
||||||
| Expr::Subexpression(block_id) => {
|
| Expr::Subexpression(block_id) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
|
@ -9,6 +9,7 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
|
use nu_utils::utils::perf;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
@ -43,7 +44,7 @@ fn gather_env_vars(
|
|||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::GenericError(
|
&ShellError::GenericError(
|
||||||
format!("Environment variable was not captured: {}", env_str),
|
format!("Environment variable was not captured: {env_str}"),
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(msg.into()),
|
Some(msg.into()),
|
||||||
@ -79,8 +80,7 @@ fn gather_env_vars(
|
|||||||
"".to_string(),
|
"".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
|
||||||
init_cwd
|
|
||||||
)),
|
)),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
),
|
),
|
||||||
@ -204,6 +204,8 @@ pub fn eval_source(
|
|||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let (output, err) = parse(
|
let (output, err) = parse(
|
||||||
@ -250,7 +252,7 @@ pub fn eval_source(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = pipeline_data.print(engine_state, stack, false, false);
|
result = pipeline_data.print(engine_state, stack, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -282,6 +284,13 @@ pub fn eval_source(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
&format!("eval_source {}", &fname),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -289,10 +298,7 @@ pub fn eval_source(
|
|||||||
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"LAST_EXIT_CODE".to_string(),
|
"LAST_EXIT_CODE".to_string(),
|
||||||
Value::Int {
|
Value::int(exit_code, Span::unknown()),
|
||||||
val: exit_code,
|
|
||||||
span: Span { start: 0, end: 0 },
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,27 +324,19 @@ pub fn report_error_new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_init_cwd() -> PathBuf {
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
match std::env::current_dir() {
|
std::env::current_dir().unwrap_or_else(|_| {
|
||||||
Ok(cwd) => cwd,
|
std::env::var("PWD")
|
||||||
Err(_) => match std::env::var("PWD") {
|
.map(Into::into)
|
||||||
Ok(cwd) => PathBuf::from(cwd),
|
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
|
||||||
Err(_) => match nu_path::home_dir() {
|
})
|
||||||
Some(cwd) => cwd,
|
|
||||||
None => PathBuf::new(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
|
||||||
Ok(p) => p,
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
Err(e) => {
|
report_error(&working_set, &e);
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
get_init_cwd()
|
||||||
report_error(&working_set, &e);
|
})
|
||||||
get_init_cwd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use nu_parser::{parse, ParseError};
|
use nu_parser::{parse, ParseError};
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
use reedline::{ValidationResult, Validator};
|
use reedline::{ValidationResult, Validator};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct NuValidator {
|
pub struct NuValidator {
|
||||||
pub engine_state: EngineState,
|
pub engine_state: Arc<EngineState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Validator for NuValidator {
|
impl Validator for NuValidator {
|
||||||
|
@ -5,7 +5,7 @@ use nu_parser::parse;
|
|||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use support::{file, folder, match_suggestions, new_engine};
|
use support::{completions_helpers::new_quote_engine, file, folder, match_suggestions, new_engine};
|
||||||
|
|
||||||
#[fixture]
|
#[fixture]
|
||||||
fn completer() -> NuCompleter {
|
fn completer() -> NuCompleter {
|
||||||
@ -34,6 +34,26 @@ fn completer_strings() -> NuCompleter {
|
|||||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn extern_completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = r#"
|
||||||
|
def animals [] { [ "cat", "dog", "eel" ] }
|
||||||
|
extern spam [
|
||||||
|
animal: string@animals
|
||||||
|
--foo (-f): string@animals
|
||||||
|
-b: string@animals
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn variables_dollar_sign_with_varialblecompletion() {
|
fn variables_dollar_sign_with_varialblecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
@ -89,7 +109,7 @@ fn dotnu_completions() {
|
|||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
// Test source completion
|
// Test source completion
|
||||||
@ -149,11 +169,11 @@ fn file_completions() {
|
|||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
// Test completions for the current folder
|
// Test completions for the current folder
|
||||||
let target_dir = format!("cp {}", dir_str);
|
let target_dir = format!("cp {dir_str}");
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
@ -411,17 +431,36 @@ fn command_watch_with_filecompletion() {
|
|||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_completion_quoted() {
|
||||||
|
let (_, _, engine, stack) = new_quote_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "open ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"`te st.txt`".to_string(),
|
||||||
|
"`te#st.txt`".to_string(),
|
||||||
|
"`te'st.txt`".to_string(),
|
||||||
|
"`te(st).txt`".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flag_completions() {
|
fn flag_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
// Test completions for the 'ls' flags
|
// Test completions for the 'ls' flags
|
||||||
let suggestions = completer.complete("ls -", 4);
|
let suggestions = completer.complete("ls -", 4);
|
||||||
|
|
||||||
assert_eq!(14, suggestions.len());
|
assert_eq!(16, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
"--all".into(),
|
"--all".into(),
|
||||||
@ -430,6 +469,7 @@ fn flag_completions() {
|
|||||||
"--full-paths".into(),
|
"--full-paths".into(),
|
||||||
"--help".into(),
|
"--help".into(),
|
||||||
"--long".into(),
|
"--long".into(),
|
||||||
|
"--mime-type".into(),
|
||||||
"--short-names".into(),
|
"--short-names".into(),
|
||||||
"-D".into(),
|
"-D".into(),
|
||||||
"-a".into(),
|
"-a".into(),
|
||||||
@ -437,6 +477,7 @@ fn flag_completions() {
|
|||||||
"-f".into(),
|
"-f".into(),
|
||||||
"-h".into(),
|
"-h".into(),
|
||||||
"-l".into(),
|
"-l".into(),
|
||||||
|
"-m".into(),
|
||||||
"-s".into(),
|
"-s".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -449,11 +490,11 @@ fn folder_with_directorycompletions() {
|
|||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
// Test completions for the current folder
|
// Test completions for the current folder
|
||||||
let target_dir = format!("cd {}", dir_str);
|
let target_dir = format!("cd {dir_str}");
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
@ -477,7 +518,7 @@ fn variables_completions() {
|
|||||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
// Test completions for $nu
|
// Test completions for $nu
|
||||||
@ -634,7 +675,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
|||||||
config.external_completer = Some(latest_block_id);
|
config.external_completer = Some(latest_block_id);
|
||||||
engine_state.set_config(&config);
|
engine_state.set_config(&config);
|
||||||
|
|
||||||
// Instatiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||||
|
|
||||||
completer.complete(input, input.len())
|
completer.complete(input, input.len())
|
||||||
@ -732,3 +773,83 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
|
|
||||||
match_suggestions(expected_paths, suggestions);
|
match_suggestions(expected_paths, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam ", 5);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||||
|
let suggestions = extern_completer.complete("spam -", 6);
|
||||||
|
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn alias_offset_bug_7748() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Issue #7748
|
||||||
|
// Nushell crashes when an alias name is shorter than the alias command
|
||||||
|
// and the alias command is a external command
|
||||||
|
// This happens because of offset is not correct.
|
||||||
|
// This crashes before PR #7779
|
||||||
|
let _suggestions = completer.complete("e", 1);
|
||||||
|
//println!(" --------- suggestions: {:?}", suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn alias_offset_bug_7754() {
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Create an alias
|
||||||
|
let alias = r#"alias ll = ls -l"#;
|
||||||
|
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Issue #7754
|
||||||
|
// Nushell crashes when an alias name is shorter than the alias command
|
||||||
|
// and the alias command contains pipes.
|
||||||
|
// This crashes before PR #7756
|
||||||
|
let _suggestions = completer.complete("ll -a | c", 9);
|
||||||
|
|
||||||
|
//println!(" --------- suggestions: {:?}", suggestions);
|
||||||
|
}
|
||||||
|
@ -33,20 +33,53 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: dir_str.clone(),
|
val: dir_str.clone(),
|
||||||
span: nu_protocol::Span {
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
start: 0,
|
|
||||||
end: dir_str.len(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"TEST".to_string(),
|
"TEST".to_string(),
|
||||||
Value::String {
|
Value::String {
|
||||||
val: "NUSHELL".to_string(),
|
val: "NUSHELL".to_string(),
|
||||||
span: nu_protocol::Span {
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
start: 0,
|
},
|
||||||
end: dir_str.len(),
|
);
|
||||||
},
|
|
||||||
|
// Merge environment into the permanent state
|
||||||
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
|
(dir, dir_str, engine_state, stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
|
// Target folder inside assets
|
||||||
|
let dir = fs::fixtures().join("quoted_completions");
|
||||||
|
let mut dir_str = dir
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap_or_default();
|
||||||
|
dir_str.push(SEP);
|
||||||
|
|
||||||
|
// Create a new engine with default context
|
||||||
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
|
// New stack
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// Add pwd as env var
|
||||||
|
stack.add_env_var(
|
||||||
|
"PWD".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: dir_str.clone(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stack.add_env_var(
|
||||||
|
"TEST".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: "NUSHELL".to_string(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -112,7 +145,7 @@ pub fn merge_input(
|
|||||||
&block,
|
&block,
|
||||||
PipelineData::Value(
|
PipelineData::Value(
|
||||||
Value::Nothing {
|
Value::Nothing {
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
|
@ -5,11 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.71.0"
|
version = "0.75.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
|
||||||
nu-ansi-term = "0.46.0"
|
|
||||||
nu-json = { path = "../nu-json", version = "0.71.0" }
|
|
||||||
nu-table = { path = "../nu-table", version = "0.71.0" }
|
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
|
# used only for text_style Alignments
|
||||||
|
tabled = { version = "0.10.0", features = ["color"], default-features = false }
|
||||||
|
|
||||||
|
nu-protocol = { path = "../nu-protocol", version = "0.75.0" }
|
||||||
|
nu-ansi-term = "0.46.0"
|
||||||
|
nu-utils = { path = "../nu-utils", version = "0.75.0" }
|
||||||
|
nu-engine = { path = "../nu-engine", version = "0.75.0" }
|
||||||
|
nu-json = { path="../nu-json", version = "0.75.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path="../nu-test-support", version = "0.75.0" }
|
||||||
|
@ -1,418 +1,89 @@
|
|||||||
use crate::nu_style::{color_from_hex, color_string_to_nustyle};
|
use crate::{
|
||||||
use nu_ansi_term::{Color, Style};
|
nu_style::{color_from_hex, lookup_style},
|
||||||
use nu_protocol::Config;
|
parse_nustyle, NuStyle,
|
||||||
use nu_table::{Alignment, TextStyle};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_protocol::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn lookup_ansi_color_style(s: &str) -> Style {
|
pub fn lookup_ansi_color_style(s: &str) -> Style {
|
||||||
if s.starts_with('#') {
|
if s.starts_with('#') {
|
||||||
match color_from_hex(s) {
|
color_from_hex(s)
|
||||||
Ok(c) => match c {
|
.ok()
|
||||||
Some(c) => c.normal(),
|
.and_then(|c| c.map(|c| c.normal()))
|
||||||
None => Style::default(),
|
.unwrap_or_default()
|
||||||
},
|
|
||||||
Err(_) => Style::default(),
|
|
||||||
}
|
|
||||||
} else if s.starts_with('{') {
|
} else if s.starts_with('{') {
|
||||||
color_string_to_nustyle(s.to_string())
|
color_string_to_nustyle(s.to_string())
|
||||||
} else {
|
} else {
|
||||||
match s {
|
lookup_style(s)
|
||||||
"g" | "green" => Color::Green.normal(),
|
|
||||||
"gb" | "green_bold" => Color::Green.bold(),
|
|
||||||
"gu" | "green_underline" => Color::Green.underline(),
|
|
||||||
"gi" | "green_italic" => Color::Green.italic(),
|
|
||||||
"gd" | "green_dimmed" => Color::Green.dimmed(),
|
|
||||||
"gr" | "green_reverse" => Color::Green.reverse(),
|
|
||||||
"gbl" | "green_blink" => Color::Green.blink(),
|
|
||||||
"gst" | "green_strike" => Color::Green.strikethrough(),
|
|
||||||
|
|
||||||
"lg" | "light_green" => Color::LightGreen.normal(),
|
|
||||||
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
|
|
||||||
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
|
|
||||||
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
|
|
||||||
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
|
|
||||||
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
|
|
||||||
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
|
|
||||||
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
|
|
||||||
|
|
||||||
"r" | "red" => Color::Red.normal(),
|
|
||||||
"rb" | "red_bold" => Color::Red.bold(),
|
|
||||||
"ru" | "red_underline" => Color::Red.underline(),
|
|
||||||
"ri" | "red_italic" => Color::Red.italic(),
|
|
||||||
"rd" | "red_dimmed" => Color::Red.dimmed(),
|
|
||||||
"rr" | "red_reverse" => Color::Red.reverse(),
|
|
||||||
"rbl" | "red_blink" => Color::Red.blink(),
|
|
||||||
"rst" | "red_strike" => Color::Red.strikethrough(),
|
|
||||||
|
|
||||||
"lr" | "light_red" => Color::LightRed.normal(),
|
|
||||||
"lrb" | "light_red_bold" => Color::LightRed.bold(),
|
|
||||||
"lru" | "light_red_underline" => Color::LightRed.underline(),
|
|
||||||
"lri" | "light_red_italic" => Color::LightRed.italic(),
|
|
||||||
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
|
|
||||||
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
|
|
||||||
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
|
|
||||||
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
|
|
||||||
|
|
||||||
"u" | "blue" => Color::Blue.normal(),
|
|
||||||
"ub" | "blue_bold" => Color::Blue.bold(),
|
|
||||||
"uu" | "blue_underline" => Color::Blue.underline(),
|
|
||||||
"ui" | "blue_italic" => Color::Blue.italic(),
|
|
||||||
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
|
|
||||||
"ur" | "blue_reverse" => Color::Blue.reverse(),
|
|
||||||
"ubl" | "blue_blink" => Color::Blue.blink(),
|
|
||||||
"ust" | "blue_strike" => Color::Blue.strikethrough(),
|
|
||||||
|
|
||||||
"lu" | "light_blue" => Color::LightBlue.normal(),
|
|
||||||
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
|
|
||||||
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
|
|
||||||
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
|
|
||||||
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
|
|
||||||
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
|
|
||||||
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
|
|
||||||
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
|
|
||||||
|
|
||||||
"b" | "black" => Color::Black.normal(),
|
|
||||||
"bb" | "black_bold" => Color::Black.bold(),
|
|
||||||
"bu" | "black_underline" => Color::Black.underline(),
|
|
||||||
"bi" | "black_italic" => Color::Black.italic(),
|
|
||||||
"bd" | "black_dimmed" => Color::Black.dimmed(),
|
|
||||||
"br" | "black_reverse" => Color::Black.reverse(),
|
|
||||||
"bbl" | "black_blink" => Color::Black.blink(),
|
|
||||||
"bst" | "black_strike" => Color::Black.strikethrough(),
|
|
||||||
|
|
||||||
"ligr" | "light_gray" => Color::LightGray.normal(),
|
|
||||||
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
|
|
||||||
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
|
|
||||||
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
|
|
||||||
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
|
|
||||||
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
|
|
||||||
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
|
|
||||||
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
|
|
||||||
|
|
||||||
"y" | "yellow" => Color::Yellow.normal(),
|
|
||||||
"yb" | "yellow_bold" => Color::Yellow.bold(),
|
|
||||||
"yu" | "yellow_underline" => Color::Yellow.underline(),
|
|
||||||
"yi" | "yellow_italic" => Color::Yellow.italic(),
|
|
||||||
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
|
|
||||||
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
|
|
||||||
"ybl" | "yellow_blink" => Color::Yellow.blink(),
|
|
||||||
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
|
|
||||||
|
|
||||||
"ly" | "light_yellow" => Color::LightYellow.normal(),
|
|
||||||
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
|
|
||||||
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
|
|
||||||
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
|
|
||||||
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
|
|
||||||
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
|
|
||||||
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
|
|
||||||
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
|
|
||||||
|
|
||||||
"p" | "purple" => Color::Purple.normal(),
|
|
||||||
"pb" | "purple_bold" => Color::Purple.bold(),
|
|
||||||
"pu" | "purple_underline" => Color::Purple.underline(),
|
|
||||||
"pi" | "purple_italic" => Color::Purple.italic(),
|
|
||||||
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
|
|
||||||
"pr" | "purple_reverse" => Color::Purple.reverse(),
|
|
||||||
"pbl" | "purple_blink" => Color::Purple.blink(),
|
|
||||||
"pst" | "purple_strike" => Color::Purple.strikethrough(),
|
|
||||||
|
|
||||||
"lp" | "light_purple" => Color::LightPurple.normal(),
|
|
||||||
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
|
|
||||||
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
|
|
||||||
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
|
|
||||||
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
|
|
||||||
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
|
|
||||||
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
|
|
||||||
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
|
|
||||||
|
|
||||||
"c" | "cyan" => Color::Cyan.normal(),
|
|
||||||
"cb" | "cyan_bold" => Color::Cyan.bold(),
|
|
||||||
"cu" | "cyan_underline" => Color::Cyan.underline(),
|
|
||||||
"ci" | "cyan_italic" => Color::Cyan.italic(),
|
|
||||||
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
|
|
||||||
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
|
|
||||||
"cbl" | "cyan_blink" => Color::Cyan.blink(),
|
|
||||||
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
|
|
||||||
|
|
||||||
"lc" | "light_cyan" => Color::LightCyan.normal(),
|
|
||||||
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
|
|
||||||
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
|
|
||||||
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
|
|
||||||
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
|
|
||||||
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
|
|
||||||
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
|
|
||||||
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
|
|
||||||
|
|
||||||
"w" | "white" => Color::White.normal(),
|
|
||||||
"wb" | "white_bold" => Color::White.bold(),
|
|
||||||
"wu" | "white_underline" => Color::White.underline(),
|
|
||||||
"wi" | "white_italic" => Color::White.italic(),
|
|
||||||
"wd" | "white_dimmed" => Color::White.dimmed(),
|
|
||||||
"wr" | "white_reverse" => Color::White.reverse(),
|
|
||||||
"wbl" | "white_blink" => Color::White.blink(),
|
|
||||||
"wst" | "white_strike" => Color::White.strikethrough(),
|
|
||||||
|
|
||||||
"dgr" | "dark_gray" => Color::DarkGray.normal(),
|
|
||||||
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
|
|
||||||
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
|
|
||||||
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
|
|
||||||
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
|
|
||||||
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
|
|
||||||
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
|
|
||||||
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
|
|
||||||
|
|
||||||
"def" | "default" => Color::Default.normal(),
|
|
||||||
"defb" | "default_bold" => Color::Default.bold(),
|
|
||||||
"defu" | "default_underline" => Color::Default.underline(),
|
|
||||||
"defi" | "default_italic" => Color::Default.italic(),
|
|
||||||
"defd" | "default_dimmed" => Color::Default.dimmed(),
|
|
||||||
"defr" | "default_reverse" => Color::Default.reverse(),
|
|
||||||
|
|
||||||
_ => Color::White.normal(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hashmap(key: &str, val: &str, hm: &mut HashMap<String, Style>) {
|
pub fn get_color_map(colors: &HashMap<String, Value>) -> HashMap<String, Style> {
|
||||||
// eprintln!("key: {}, val: {}", &key, &val);
|
|
||||||
let color = lookup_ansi_color_style(val);
|
|
||||||
if let Some(v) = hm.get_mut(key) {
|
|
||||||
*v = color;
|
|
||||||
} else {
|
|
||||||
hm.insert(key.to_string(), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_color_config(config: &Config) -> HashMap<String, Style> {
|
|
||||||
let config = config;
|
|
||||||
|
|
||||||
// create the hashmap
|
|
||||||
let mut hm: HashMap<String, Style> = HashMap::new();
|
let mut hm: HashMap<String, Style> = HashMap::new();
|
||||||
// set some defaults
|
|
||||||
// hm.insert("primitive_line".to_string(), Color::White.normal());
|
|
||||||
// hm.insert("primitive_pattern".to_string(), Color::White.normal());
|
|
||||||
// hm.insert("primitive_path".to_string(), Color::White.normal());
|
|
||||||
hm.insert("separator".to_string(), Color::White.normal());
|
|
||||||
hm.insert(
|
|
||||||
"leading_trailing_space_bg".to_string(),
|
|
||||||
Style::default().on(Color::Rgb(128, 128, 128)),
|
|
||||||
);
|
|
||||||
hm.insert("header".to_string(), Color::Green.bold());
|
|
||||||
hm.insert("empty".to_string(), Color::Blue.normal());
|
|
||||||
hm.insert("bool".to_string(), Color::White.normal());
|
|
||||||
hm.insert("int".to_string(), Color::White.normal());
|
|
||||||
hm.insert("filesize".to_string(), Color::White.normal());
|
|
||||||
hm.insert("duration".to_string(), Color::White.normal());
|
|
||||||
hm.insert("date".to_string(), Color::White.normal());
|
|
||||||
hm.insert("range".to_string(), Color::White.normal());
|
|
||||||
hm.insert("float".to_string(), Color::White.normal());
|
|
||||||
hm.insert("string".to_string(), Color::White.normal());
|
|
||||||
hm.insert("nothing".to_string(), Color::White.normal());
|
|
||||||
hm.insert("binary".to_string(), Color::White.normal());
|
|
||||||
hm.insert("cellpath".to_string(), Color::White.normal());
|
|
||||||
hm.insert("row_index".to_string(), Color::Green.bold());
|
|
||||||
hm.insert("record".to_string(), Color::White.normal());
|
|
||||||
hm.insert("list".to_string(), Color::White.normal());
|
|
||||||
hm.insert("block".to_string(), Color::White.normal());
|
|
||||||
hm.insert("hints".to_string(), Color::DarkGray.normal());
|
|
||||||
|
|
||||||
for (key, value) in &config.color_config {
|
for (key, value) in colors {
|
||||||
let value = value
|
parse_map_entry(&mut hm, key, value);
|
||||||
.as_string()
|
|
||||||
.expect("the only values for config color must be strings");
|
|
||||||
update_hashmap(key, &value, &mut hm);
|
|
||||||
|
|
||||||
// eprintln!(
|
|
||||||
// "config: {}:{}\t\t\thashmap: {}:{:?}",
|
|
||||||
// &key, &value, &key, &hm[key]
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hm
|
hm
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will assign a text style to a primitive, or really any string that's
|
fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) {
|
||||||
// in the hashmap. The hashmap actually contains the style to be applied.
|
let value = match value {
|
||||||
pub fn style_primitive(primitive: &str, color_hm: &HashMap<String, Style>) -> TextStyle {
|
Value::String { val, .. } => Some(lookup_ansi_color_style(val)),
|
||||||
match primitive {
|
Value::Record { cols, vals, .. } => get_style_from_value(cols, vals).map(parse_nustyle),
|
||||||
"bool" => {
|
_ => None,
|
||||||
let style = color_hm.get(primitive);
|
};
|
||||||
match style {
|
if let Some(value) = value {
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
hm.entry(key.to_owned()).or_insert(value);
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"int" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"filesize" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"duration" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"date" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"range" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"float" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::basic_right(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"string" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"nothing" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not sure what to do with error
|
|
||||||
// "error" => {}
|
|
||||||
"binary" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"cellpath" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"row_index" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Right, *s),
|
|
||||||
None => TextStyle::new()
|
|
||||||
.alignment(Alignment::Right)
|
|
||||||
.fg(Color::Green)
|
|
||||||
.bold(Some(true)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"record" | "list" | "block" => {
|
|
||||||
let style = color_hm.get(primitive);
|
|
||||||
match style {
|
|
||||||
Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
None => TextStyle::basic_left(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// types in nushell but not in engine-q
|
|
||||||
// "Line" => {
|
|
||||||
// let style = color_hm.get("Primitive::Line");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "GlobPattern" => {
|
|
||||||
// let style = color_hm.get("Primitive::GlobPattern");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "FilePath" => {
|
|
||||||
// let style = color_hm.get("Primitive::FilePath");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "BeginningOfStream" => {
|
|
||||||
// let style = color_hm.get("Primitive::BeginningOfStream");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "EndOfStream" => {
|
|
||||||
// let style = color_hm.get("Primitive::EndOfStream");
|
|
||||||
// match style {
|
|
||||||
// Some(s) => TextStyle::with_style(Alignment::Left, *s),
|
|
||||||
// None => TextStyle::basic_left(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
_ => TextStyle::basic_left(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn get_style_from_value(cols: &[String], vals: &[Value]) -> Option<NuStyle> {
|
||||||
fn test_hm() {
|
let mut was_set = false;
|
||||||
use nu_ansi_term::{Color, Style};
|
let mut style = NuStyle::from(Style::default());
|
||||||
|
for (col, val) in cols.iter().zip(vals) {
|
||||||
|
match col.as_str() {
|
||||||
|
"bg" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.bg = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"fg" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.fg = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"attr" => {
|
||||||
|
if let Value::String { val, .. } = val {
|
||||||
|
style.attr = Some(val.clone());
|
||||||
|
was_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut hm: HashMap<String, Style> = HashMap::new();
|
if was_set {
|
||||||
hm.insert("primitive_int".to_string(), Color::White.normal());
|
Some(style)
|
||||||
hm.insert("primitive_decimal".to_string(), Color::White.normal());
|
} else {
|
||||||
hm.insert("primitive_filesize".to_string(), Color::White.normal());
|
None
|
||||||
hm.insert("primitive_string".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_line".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_columnpath".to_string(), Color::White.normal());
|
|
||||||
hm.insert("primitive_pattern".to_string(), Color::White.normal());
|
fn color_string_to_nustyle(color_string: String) -> Style {
|
||||||
hm.insert("primitive_boolean".to_string(), Color::White.normal());
|
// eprintln!("color_string: {}", &color_string);
|
||||||
hm.insert("primitive_date".to_string(), Color::White.normal());
|
if color_string.is_empty() {
|
||||||
hm.insert("primitive_duration".to_string(), Color::White.normal());
|
return Style::default();
|
||||||
hm.insert("primitive_range".to_string(), Color::White.normal());
|
}
|
||||||
hm.insert("primitive_path".to_string(), Color::White.normal());
|
|
||||||
hm.insert("primitive_binary".to_string(), Color::White.normal());
|
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
|
||||||
hm.insert("separator".to_string(), Color::White.normal());
|
Ok(s) => s,
|
||||||
hm.insert("header_align".to_string(), Color::Green.bold());
|
Err(_) => return Style::default(),
|
||||||
hm.insert("header".to_string(), Color::Green.bold());
|
};
|
||||||
hm.insert("header_style".to_string(), Style::default());
|
|
||||||
hm.insert("row_index".to_string(), Color::Green.bold());
|
parse_nustyle(nu_style)
|
||||||
hm.insert(
|
|
||||||
"leading_trailing_space_bg".to_string(),
|
|
||||||
Style::default().on(Color::Rgb(128, 128, 128)),
|
|
||||||
);
|
|
||||||
|
|
||||||
update_hashmap("primitive_int", "green", &mut hm);
|
|
||||||
|
|
||||||
assert_eq!(hm["primitive_int"], Color::Green.normal());
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,12 @@ mod color_config;
|
|||||||
mod matching_brackets_style;
|
mod matching_brackets_style;
|
||||||
mod nu_style;
|
mod nu_style;
|
||||||
mod shape_color;
|
mod shape_color;
|
||||||
|
mod style_computer;
|
||||||
|
mod text_style;
|
||||||
|
|
||||||
pub use color_config::*;
|
pub use color_config::*;
|
||||||
pub use matching_brackets_style::*;
|
pub use matching_brackets_style::*;
|
||||||
pub use nu_style::*;
|
pub use nu_style::*;
|
||||||
pub use shape_color::*;
|
pub use shape_color::*;
|
||||||
|
pub use style_computer::*;
|
||||||
|
pub use text_style::*;
|
||||||
|
@ -1,88 +1,113 @@
|
|||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use serde::Deserialize;
|
use nu_protocol::Value;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug)]
|
||||||
pub struct NuStyle {
|
pub struct NuStyle {
|
||||||
pub fg: Option<String>,
|
pub fg: Option<String>,
|
||||||
pub bg: Option<String>,
|
pub bg: Option<String>,
|
||||||
pub attr: Option<String>,
|
pub attr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
impl From<Style> for NuStyle {
|
||||||
// get the nu_ansi_term::Color foreground color
|
fn from(s: Style) -> Self {
|
||||||
let fg_color = match nu_style.fg {
|
Self {
|
||||||
Some(fg) => color_from_hex(&fg).unwrap_or_default(),
|
bg: s.background.and_then(color_to_string),
|
||||||
_ => None,
|
fg: s.foreground.and_then(color_to_string),
|
||||||
};
|
attr: style_get_attr(s),
|
||||||
// get the nu_ansi_term::Color background color
|
|
||||||
let bg_color = match nu_style.bg {
|
|
||||||
Some(bg) => color_from_hex(&bg).unwrap_or_default(),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
// get the attributes
|
|
||||||
let color_attr = match nu_style.attr {
|
|
||||||
Some(attr) => attr,
|
|
||||||
_ => "".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// setup the attributes available in nu_ansi_term::Style
|
|
||||||
let mut bold = false;
|
|
||||||
let mut dimmed = false;
|
|
||||||
let mut italic = false;
|
|
||||||
let mut underline = false;
|
|
||||||
let mut blink = false;
|
|
||||||
let mut reverse = false;
|
|
||||||
let mut hidden = false;
|
|
||||||
let mut strikethrough = false;
|
|
||||||
|
|
||||||
// since we can combine styles like bold-italic, iterate through the chars
|
|
||||||
// and set the bools for later use in the nu_ansi_term::Style application
|
|
||||||
for ch in color_attr.to_lowercase().chars() {
|
|
||||||
match ch {
|
|
||||||
'l' => blink = true,
|
|
||||||
'b' => bold = true,
|
|
||||||
'd' => dimmed = true,
|
|
||||||
'h' => hidden = true,
|
|
||||||
'i' => italic = true,
|
|
||||||
'r' => reverse = true,
|
|
||||||
's' => strikethrough = true,
|
|
||||||
'u' => underline = true,
|
|
||||||
'n' => (),
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// here's where we build the nu_ansi_term::Style
|
|
||||||
Style {
|
|
||||||
foreground: fg_color,
|
|
||||||
background: bg_color,
|
|
||||||
is_blink: blink,
|
|
||||||
is_bold: bold,
|
|
||||||
is_dimmed: dimmed,
|
|
||||||
is_hidden: hidden,
|
|
||||||
is_italic: italic,
|
|
||||||
is_reverse: reverse,
|
|
||||||
is_strikethrough: strikethrough,
|
|
||||||
is_underline: underline,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_string_to_nustyle(color_string: String) -> Style {
|
fn style_get_attr(s: Style) -> Option<String> {
|
||||||
// eprintln!("color_string: {}", &color_string);
|
macro_rules! check {
|
||||||
if color_string.chars().count() < 1 {
|
($attrs:expr, $b:expr, $c:expr) => {
|
||||||
Style::default()
|
if $b {
|
||||||
} else {
|
$attrs.push($c);
|
||||||
let nu_style = match nu_json::from_str::<NuStyle>(&color_string) {
|
}
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => NuStyle {
|
|
||||||
fg: None,
|
|
||||||
bg: None,
|
|
||||||
attr: None,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
parse_nustyle(nu_style)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut attrs = String::new();
|
||||||
|
|
||||||
|
check!(attrs, s.is_blink, 'l');
|
||||||
|
check!(attrs, s.is_bold, 'b');
|
||||||
|
check!(attrs, s.is_dimmed, 'd');
|
||||||
|
check!(attrs, s.is_reverse, 'r');
|
||||||
|
check!(attrs, s.is_strikethrough, 's');
|
||||||
|
check!(attrs, s.is_underline, 'u');
|
||||||
|
|
||||||
|
if attrs.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_to_string(color: Color) -> Option<String> {
|
||||||
|
match color {
|
||||||
|
Color::Black => Some(String::from("black")),
|
||||||
|
Color::DarkGray => Some(String::from("dark_gray")),
|
||||||
|
Color::Red => Some(String::from("red")),
|
||||||
|
Color::LightRed => Some(String::from("light_red")),
|
||||||
|
Color::Green => Some(String::from("green")),
|
||||||
|
Color::LightGreen => Some(String::from("light_green")),
|
||||||
|
Color::Yellow => Some(String::from("yellow")),
|
||||||
|
Color::LightYellow => Some(String::from("light_yellow")),
|
||||||
|
Color::Blue => Some(String::from("blue")),
|
||||||
|
Color::LightBlue => Some(String::from("light_blue")),
|
||||||
|
Color::Purple => Some(String::from("purple")),
|
||||||
|
Color::LightPurple => Some(String::from("light_purple")),
|
||||||
|
Color::Magenta => Some(String::from("magenta")),
|
||||||
|
Color::LightMagenta => Some(String::from("light_magenta")),
|
||||||
|
Color::Cyan => Some(String::from("cyan")),
|
||||||
|
Color::LightCyan => Some(String::from("light_cyan")),
|
||||||
|
Color::White => Some(String::from("white")),
|
||||||
|
Color::LightGray => Some(String::from("light_gray")),
|
||||||
|
Color::Default => Some(String::from("default")),
|
||||||
|
Color::Rgb(r, g, b) => Some(format!("#{r:X}{g:X}{b:X}")),
|
||||||
|
Color::Fixed(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_nustyle(nu_style: NuStyle) -> Style {
|
||||||
|
let mut style = Style {
|
||||||
|
foreground: nu_style.fg.and_then(|fg| lookup_color_str(&fg)),
|
||||||
|
background: nu_style.bg.and_then(|bg| lookup_color_str(&bg)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(attrs) = nu_style.attr {
|
||||||
|
fill_modifiers(&attrs, &mut style)
|
||||||
|
}
|
||||||
|
|
||||||
|
style
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the color_config records, { fg, bg, attr }, into a Style.
|
||||||
|
pub fn color_record_to_nustyle(value: &Value) -> Style {
|
||||||
|
let mut fg = None;
|
||||||
|
let mut bg = None;
|
||||||
|
let mut attr = None;
|
||||||
|
let v = value.as_record();
|
||||||
|
if let Ok((cols, inner_vals)) = v {
|
||||||
|
for (k, v) in cols.iter().zip(inner_vals) {
|
||||||
|
// Because config already type-checked the color_config records, this doesn't bother giving errors
|
||||||
|
// if there are unrecognised keys or bad values.
|
||||||
|
if let Ok(v) = v.as_string() {
|
||||||
|
match k.as_str() {
|
||||||
|
"fg" => fg = Some(v),
|
||||||
|
|
||||||
|
"bg" => bg = Some(v),
|
||||||
|
|
||||||
|
"attr" => attr = Some(v),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_nustyle(NuStyle { fg, bg, attr })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_from_hex(
|
pub fn color_from_hex(
|
||||||
@ -101,3 +126,471 @@ pub fn color_from_hex(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lookup_style(s: &str) -> Style {
|
||||||
|
match s {
|
||||||
|
"g" | "green" => Color::Green.normal(),
|
||||||
|
"gb" | "green_bold" => Color::Green.bold(),
|
||||||
|
"gu" | "green_underline" => Color::Green.underline(),
|
||||||
|
"gi" | "green_italic" => Color::Green.italic(),
|
||||||
|
"gd" | "green_dimmed" => Color::Green.dimmed(),
|
||||||
|
"gr" | "green_reverse" => Color::Green.reverse(),
|
||||||
|
"gbl" | "green_blink" => Color::Green.blink(),
|
||||||
|
"gst" | "green_strike" => Color::Green.strikethrough(),
|
||||||
|
|
||||||
|
"lg" | "light_green" => Color::LightGreen.normal(),
|
||||||
|
"lgb" | "light_green_bold" => Color::LightGreen.bold(),
|
||||||
|
"lgu" | "light_green_underline" => Color::LightGreen.underline(),
|
||||||
|
"lgi" | "light_green_italic" => Color::LightGreen.italic(),
|
||||||
|
"lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(),
|
||||||
|
"lgr" | "light_green_reverse" => Color::LightGreen.reverse(),
|
||||||
|
"lgbl" | "light_green_blink" => Color::LightGreen.blink(),
|
||||||
|
"lgst" | "light_green_strike" => Color::LightGreen.strikethrough(),
|
||||||
|
|
||||||
|
"r" | "red" => Color::Red.normal(),
|
||||||
|
"rb" | "red_bold" => Color::Red.bold(),
|
||||||
|
"ru" | "red_underline" => Color::Red.underline(),
|
||||||
|
"ri" | "red_italic" => Color::Red.italic(),
|
||||||
|
"rd" | "red_dimmed" => Color::Red.dimmed(),
|
||||||
|
"rr" | "red_reverse" => Color::Red.reverse(),
|
||||||
|
"rbl" | "red_blink" => Color::Red.blink(),
|
||||||
|
"rst" | "red_strike" => Color::Red.strikethrough(),
|
||||||
|
|
||||||
|
"lr" | "light_red" => Color::LightRed.normal(),
|
||||||
|
"lrb" | "light_red_bold" => Color::LightRed.bold(),
|
||||||
|
"lru" | "light_red_underline" => Color::LightRed.underline(),
|
||||||
|
"lri" | "light_red_italic" => Color::LightRed.italic(),
|
||||||
|
"lrd" | "light_red_dimmed" => Color::LightRed.dimmed(),
|
||||||
|
"lrr" | "light_red_reverse" => Color::LightRed.reverse(),
|
||||||
|
"lrbl" | "light_red_blink" => Color::LightRed.blink(),
|
||||||
|
"lrst" | "light_red_strike" => Color::LightRed.strikethrough(),
|
||||||
|
|
||||||
|
"u" | "blue" => Color::Blue.normal(),
|
||||||
|
"ub" | "blue_bold" => Color::Blue.bold(),
|
||||||
|
"uu" | "blue_underline" => Color::Blue.underline(),
|
||||||
|
"ui" | "blue_italic" => Color::Blue.italic(),
|
||||||
|
"ud" | "blue_dimmed" => Color::Blue.dimmed(),
|
||||||
|
"ur" | "blue_reverse" => Color::Blue.reverse(),
|
||||||
|
"ubl" | "blue_blink" => Color::Blue.blink(),
|
||||||
|
"ust" | "blue_strike" => Color::Blue.strikethrough(),
|
||||||
|
|
||||||
|
"lu" | "light_blue" => Color::LightBlue.normal(),
|
||||||
|
"lub" | "light_blue_bold" => Color::LightBlue.bold(),
|
||||||
|
"luu" | "light_blue_underline" => Color::LightBlue.underline(),
|
||||||
|
"lui" | "light_blue_italic" => Color::LightBlue.italic(),
|
||||||
|
"lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(),
|
||||||
|
"lur" | "light_blue_reverse" => Color::LightBlue.reverse(),
|
||||||
|
"lubl" | "light_blue_blink" => Color::LightBlue.blink(),
|
||||||
|
"lust" | "light_blue_strike" => Color::LightBlue.strikethrough(),
|
||||||
|
|
||||||
|
"b" | "black" => Color::Black.normal(),
|
||||||
|
"bb" | "black_bold" => Color::Black.bold(),
|
||||||
|
"bu" | "black_underline" => Color::Black.underline(),
|
||||||
|
"bi" | "black_italic" => Color::Black.italic(),
|
||||||
|
"bd" | "black_dimmed" => Color::Black.dimmed(),
|
||||||
|
"br" | "black_reverse" => Color::Black.reverse(),
|
||||||
|
"bbl" | "black_blink" => Color::Black.blink(),
|
||||||
|
"bst" | "black_strike" => Color::Black.strikethrough(),
|
||||||
|
|
||||||
|
"ligr" | "light_gray" => Color::LightGray.normal(),
|
||||||
|
"ligrb" | "light_gray_bold" => Color::LightGray.bold(),
|
||||||
|
"ligru" | "light_gray_underline" => Color::LightGray.underline(),
|
||||||
|
"ligri" | "light_gray_italic" => Color::LightGray.italic(),
|
||||||
|
"ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(),
|
||||||
|
"ligrr" | "light_gray_reverse" => Color::LightGray.reverse(),
|
||||||
|
"ligrbl" | "light_gray_blink" => Color::LightGray.blink(),
|
||||||
|
"ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(),
|
||||||
|
|
||||||
|
"y" | "yellow" => Color::Yellow.normal(),
|
||||||
|
"yb" | "yellow_bold" => Color::Yellow.bold(),
|
||||||
|
"yu" | "yellow_underline" => Color::Yellow.underline(),
|
||||||
|
"yi" | "yellow_italic" => Color::Yellow.italic(),
|
||||||
|
"yd" | "yellow_dimmed" => Color::Yellow.dimmed(),
|
||||||
|
"yr" | "yellow_reverse" => Color::Yellow.reverse(),
|
||||||
|
"ybl" | "yellow_blink" => Color::Yellow.blink(),
|
||||||
|
"yst" | "yellow_strike" => Color::Yellow.strikethrough(),
|
||||||
|
|
||||||
|
"ly" | "light_yellow" => Color::LightYellow.normal(),
|
||||||
|
"lyb" | "light_yellow_bold" => Color::LightYellow.bold(),
|
||||||
|
"lyu" | "light_yellow_underline" => Color::LightYellow.underline(),
|
||||||
|
"lyi" | "light_yellow_italic" => Color::LightYellow.italic(),
|
||||||
|
"lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(),
|
||||||
|
"lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(),
|
||||||
|
"lybl" | "light_yellow_blink" => Color::LightYellow.blink(),
|
||||||
|
"lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(),
|
||||||
|
|
||||||
|
"p" | "purple" => Color::Purple.normal(),
|
||||||
|
"pb" | "purple_bold" => Color::Purple.bold(),
|
||||||
|
"pu" | "purple_underline" => Color::Purple.underline(),
|
||||||
|
"pi" | "purple_italic" => Color::Purple.italic(),
|
||||||
|
"pd" | "purple_dimmed" => Color::Purple.dimmed(),
|
||||||
|
"pr" | "purple_reverse" => Color::Purple.reverse(),
|
||||||
|
"pbl" | "purple_blink" => Color::Purple.blink(),
|
||||||
|
"pst" | "purple_strike" => Color::Purple.strikethrough(),
|
||||||
|
|
||||||
|
"lp" | "light_purple" => Color::LightPurple.normal(),
|
||||||
|
"lpb" | "light_purple_bold" => Color::LightPurple.bold(),
|
||||||
|
"lpu" | "light_purple_underline" => Color::LightPurple.underline(),
|
||||||
|
"lpi" | "light_purple_italic" => Color::LightPurple.italic(),
|
||||||
|
"lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(),
|
||||||
|
"lpr" | "light_purple_reverse" => Color::LightPurple.reverse(),
|
||||||
|
"lpbl" | "light_purple_blink" => Color::LightPurple.blink(),
|
||||||
|
"lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(),
|
||||||
|
|
||||||
|
"c" | "cyan" => Color::Cyan.normal(),
|
||||||
|
"cb" | "cyan_bold" => Color::Cyan.bold(),
|
||||||
|
"cu" | "cyan_underline" => Color::Cyan.underline(),
|
||||||
|
"ci" | "cyan_italic" => Color::Cyan.italic(),
|
||||||
|
"cd" | "cyan_dimmed" => Color::Cyan.dimmed(),
|
||||||
|
"cr" | "cyan_reverse" => Color::Cyan.reverse(),
|
||||||
|
"cbl" | "cyan_blink" => Color::Cyan.blink(),
|
||||||
|
"cst" | "cyan_strike" => Color::Cyan.strikethrough(),
|
||||||
|
|
||||||
|
"lc" | "light_cyan" => Color::LightCyan.normal(),
|
||||||
|
"lcb" | "light_cyan_bold" => Color::LightCyan.bold(),
|
||||||
|
"lcu" | "light_cyan_underline" => Color::LightCyan.underline(),
|
||||||
|
"lci" | "light_cyan_italic" => Color::LightCyan.italic(),
|
||||||
|
"lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(),
|
||||||
|
"lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(),
|
||||||
|
"lcbl" | "light_cyan_blink" => Color::LightCyan.blink(),
|
||||||
|
"lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(),
|
||||||
|
|
||||||
|
"w" | "white" => Color::White.normal(),
|
||||||
|
"wb" | "white_bold" => Color::White.bold(),
|
||||||
|
"wu" | "white_underline" => Color::White.underline(),
|
||||||
|
"wi" | "white_italic" => Color::White.italic(),
|
||||||
|
"wd" | "white_dimmed" => Color::White.dimmed(),
|
||||||
|
"wr" | "white_reverse" => Color::White.reverse(),
|
||||||
|
"wbl" | "white_blink" => Color::White.blink(),
|
||||||
|
"wst" | "white_strike" => Color::White.strikethrough(),
|
||||||
|
|
||||||
|
"dgr" | "dark_gray" => Color::DarkGray.normal(),
|
||||||
|
"dgrb" | "dark_gray_bold" => Color::DarkGray.bold(),
|
||||||
|
"dgru" | "dark_gray_underline" => Color::DarkGray.underline(),
|
||||||
|
"dgri" | "dark_gray_italic" => Color::DarkGray.italic(),
|
||||||
|
"dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(),
|
||||||
|
"dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(),
|
||||||
|
"dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(),
|
||||||
|
"dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(),
|
||||||
|
|
||||||
|
"def" | "default" => Color::Default.normal(),
|
||||||
|
"defb" | "default_bold" => Color::Default.bold(),
|
||||||
|
"defu" | "default_underline" => Color::Default.underline(),
|
||||||
|
"defi" | "default_italic" => Color::Default.italic(),
|
||||||
|
"defd" | "default_dimmed" => Color::Default.dimmed(),
|
||||||
|
"defr" | "default_reverse" => Color::Default.reverse(),
|
||||||
|
|
||||||
|
// Add xterm 256 colors adding an x prefix where the name conflicts
|
||||||
|
"xblack" | "xterm_black" => Color::Fixed(0).normal(),
|
||||||
|
"maroon" | "xterm_maroon" => Color::Fixed(1).normal(),
|
||||||
|
"xgreen" | "xterm_green" => Color::Fixed(2).normal(),
|
||||||
|
"olive" | "xterm_olive" => Color::Fixed(3).normal(),
|
||||||
|
"navy" | "xterm_navy" => Color::Fixed(4).normal(),
|
||||||
|
"xpurplea" | "xterm_purplea" => Color::Fixed(5).normal(),
|
||||||
|
"teal" | "xterm_teal" => Color::Fixed(6).normal(),
|
||||||
|
"silver" | "xterm_silver" => Color::Fixed(7).normal(),
|
||||||
|
"grey" | "xterm_grey" => Color::Fixed(8).normal(),
|
||||||
|
"xred" | "xterm_red" => Color::Fixed(9).normal(),
|
||||||
|
"lime" | "xterm_lime" => Color::Fixed(10).normal(),
|
||||||
|
"xyellow" | "xterm_yellow" => Color::Fixed(11).normal(),
|
||||||
|
"xblue" | "xterm_blue" => Color::Fixed(12).normal(),
|
||||||
|
"fuchsia" | "xterm_fuchsia" => Color::Fixed(13).normal(),
|
||||||
|
"aqua" | "xterm_aqua" => Color::Fixed(14).normal(),
|
||||||
|
"xwhite" | "xterm_white" => Color::Fixed(15).normal(),
|
||||||
|
"grey0" | "xterm_grey0" => Color::Fixed(16).normal(),
|
||||||
|
"navyblue" | "xterm_navyblue" => Color::Fixed(17).normal(),
|
||||||
|
"darkblue" | "xterm_darkblue" => Color::Fixed(18).normal(),
|
||||||
|
"blue3a" | "xterm_blue3a" => Color::Fixed(19).normal(),
|
||||||
|
"blue3b" | "xterm_blue3b" => Color::Fixed(20).normal(),
|
||||||
|
"blue1" | "xterm_blue1" => Color::Fixed(21).normal(),
|
||||||
|
"darkgreen" | "xterm_darkgreen" => Color::Fixed(22).normal(),
|
||||||
|
"deepskyblue4a" | "xterm_deepskyblue4a" => Color::Fixed(23).normal(),
|
||||||
|
"deepskyblue4b" | "xterm_deepskyblue4b" => Color::Fixed(24).normal(),
|
||||||
|
"deepskyblue4c" | "xterm_deepskyblue4c" => Color::Fixed(25).normal(),
|
||||||
|
"dodgerblue3" | "xterm_dodgerblue3" => Color::Fixed(26).normal(),
|
||||||
|
"dodgerblue2" | "xterm_dodgerblue2" => Color::Fixed(27).normal(),
|
||||||
|
"green4" | "xterm_green4" => Color::Fixed(28).normal(),
|
||||||
|
"springgreen4" | "xterm_springgreen4" => Color::Fixed(29).normal(),
|
||||||
|
"turquoise4" | "xterm_turquoise4" => Color::Fixed(30).normal(),
|
||||||
|
"deepskyblue3a" | "xterm_deepskyblue3a" => Color::Fixed(31).normal(),
|
||||||
|
"deepskyblue3b" | "xterm_deepskyblue3b" => Color::Fixed(32).normal(),
|
||||||
|
"dodgerblue1" | "xterm_dodgerblue1" => Color::Fixed(33).normal(),
|
||||||
|
"green3a" | "xterm_green3a" => Color::Fixed(34).normal(),
|
||||||
|
"springgreen3a" | "xterm_springgreen3a" => Color::Fixed(35).normal(),
|
||||||
|
"darkcyan" | "xterm_darkcyan" => Color::Fixed(36).normal(),
|
||||||
|
"lightseagreen" | "xterm_lightseagreen" => Color::Fixed(37).normal(),
|
||||||
|
"deepskyblue2" | "xterm_deepskyblue2" => Color::Fixed(38).normal(),
|
||||||
|
"deepskyblue1" | "xterm_deepskyblue1" => Color::Fixed(39).normal(),
|
||||||
|
"green3b" | "xterm_green3b" => Color::Fixed(40).normal(),
|
||||||
|
"springgreen3b" | "xterm_springgreen3b" => Color::Fixed(41).normal(),
|
||||||
|
"springgreen2a" | "xterm_springgreen2a" => Color::Fixed(42).normal(),
|
||||||
|
"cyan3" | "xterm_cyan3" => Color::Fixed(43).normal(),
|
||||||
|
"darkturquoise" | "xterm_darkturquoise" => Color::Fixed(44).normal(),
|
||||||
|
"turquoise2" | "xterm_turquoise2" => Color::Fixed(45).normal(),
|
||||||
|
"green1" | "xterm_green1" => Color::Fixed(46).normal(),
|
||||||
|
"springgreen2b" | "xterm_springgreen2b" => Color::Fixed(47).normal(),
|
||||||
|
"springgreen1" | "xterm_springgreen1" => Color::Fixed(48).normal(),
|
||||||
|
"mediumspringgreen" | "xterm_mediumspringgreen" => Color::Fixed(49).normal(),
|
||||||
|
"cyan2" | "xterm_cyan2" => Color::Fixed(50).normal(),
|
||||||
|
"cyan1" | "xterm_cyan1" => Color::Fixed(51).normal(),
|
||||||
|
"darkreda" | "xterm_darkreda" => Color::Fixed(52).normal(),
|
||||||
|
"deeppink4a" | "xterm_deeppink4a" => Color::Fixed(53).normal(),
|
||||||
|
"purple4a" | "xterm_purple4a" => Color::Fixed(54).normal(),
|
||||||
|
"purple4b" | "xterm_purple4b" => Color::Fixed(55).normal(),
|
||||||
|
"purple3" | "xterm_purple3" => Color::Fixed(56).normal(),
|
||||||
|
"blueviolet" | "xterm_blueviolet" => Color::Fixed(57).normal(),
|
||||||
|
"orange4a" | "xterm_orange4a" => Color::Fixed(58).normal(),
|
||||||
|
"grey37" | "xterm_grey37" => Color::Fixed(59).normal(),
|
||||||
|
"mediumpurple4" | "xterm_mediumpurple4" => Color::Fixed(60).normal(),
|
||||||
|
"slateblue3a" | "xterm_slateblue3a" => Color::Fixed(61).normal(),
|
||||||
|
"slateblue3b" | "xterm_slateblue3b" => Color::Fixed(62).normal(),
|
||||||
|
"royalblue1" | "xterm_royalblue1" => Color::Fixed(63).normal(),
|
||||||
|
"chartreuse4" | "xterm_chartreuse4" => Color::Fixed(64).normal(),
|
||||||
|
"darkseagreen4a" | "xterm_darkseagreen4a" => Color::Fixed(65).normal(),
|
||||||
|
"paleturquoise4" | "xterm_paleturquoise4" => Color::Fixed(66).normal(),
|
||||||
|
"steelblue" | "xterm_steelblue" => Color::Fixed(67).normal(),
|
||||||
|
"steelblue3" | "xterm_steelblue3" => Color::Fixed(68).normal(),
|
||||||
|
"cornflowerblue" | "xterm_cornflowerblue" => Color::Fixed(69).normal(),
|
||||||
|
"chartreuse3a" | "xterm_chartreuse3a" => Color::Fixed(70).normal(),
|
||||||
|
"darkseagreen4b" | "xterm_darkseagreen4b" => Color::Fixed(71).normal(),
|
||||||
|
"cadetbluea" | "xterm_cadetbluea" => Color::Fixed(72).normal(),
|
||||||
|
"cadetblueb" | "xterm_cadetblueb" => Color::Fixed(73).normal(),
|
||||||
|
"skyblue3" | "xterm_skyblue3" => Color::Fixed(74).normal(),
|
||||||
|
"steelblue1a" | "xterm_steelblue1a" => Color::Fixed(75).normal(),
|
||||||
|
"chartreuse3b" | "xterm_chartreuse3b" => Color::Fixed(76).normal(),
|
||||||
|
"palegreen3a" | "xterm_palegreen3a" => Color::Fixed(77).normal(),
|
||||||
|
"seagreen3" | "xterm_seagreen3" => Color::Fixed(78).normal(),
|
||||||
|
"aquamarine3" | "xterm_aquamarine3" => Color::Fixed(79).normal(),
|
||||||
|
"mediumturquoise" | "xterm_mediumturquoise" => Color::Fixed(80).normal(),
|
||||||
|
"steelblue1b" | "xterm_steelblue1b" => Color::Fixed(81).normal(),
|
||||||
|
"chartreuse2a" | "xterm_chartreuse2a" => Color::Fixed(82).normal(),
|
||||||
|
"seagreen2" | "xterm_seagreen2" => Color::Fixed(83).normal(),
|
||||||
|
"seagreen1a" | "xterm_seagreen1a" => Color::Fixed(84).normal(),
|
||||||
|
"seagreen1b" | "xterm_seagreen1b" => Color::Fixed(85).normal(),
|
||||||
|
"aquamarine1a" | "xterm_aquamarine1a" => Color::Fixed(86).normal(),
|
||||||
|
"darkslategray2" | "xterm_darkslategray2" => Color::Fixed(87).normal(),
|
||||||
|
"darkredb" | "xterm_darkredb" => Color::Fixed(88).normal(),
|
||||||
|
"deeppink4b" | "xterm_deeppink4b" => Color::Fixed(89).normal(),
|
||||||
|
"darkmagentaa" | "xterm_darkmagentaa" => Color::Fixed(90).normal(),
|
||||||
|
"darkmagentab" | "xterm_darkmagentab" => Color::Fixed(91).normal(),
|
||||||
|
"darkvioleta" | "xterm_darkvioleta" => Color::Fixed(92).normal(),
|
||||||
|
"xpurpleb" | "xterm_purpleb" => Color::Fixed(93).normal(),
|
||||||
|
"orange4b" | "xterm_orange4b" => Color::Fixed(94).normal(),
|
||||||
|
"lightpink4" | "xterm_lightpink4" => Color::Fixed(95).normal(),
|
||||||
|
"plum4" | "xterm_plum4" => Color::Fixed(96).normal(),
|
||||||
|
"mediumpurple3a" | "xterm_mediumpurple3a" => Color::Fixed(97).normal(),
|
||||||
|
"mediumpurple3b" | "xterm_mediumpurple3b" => Color::Fixed(98).normal(),
|
||||||
|
"slateblue1" | "xterm_slateblue1" => Color::Fixed(99).normal(),
|
||||||
|
"yellow4a" | "xterm_yellow4a" => Color::Fixed(100).normal(),
|
||||||
|
"wheat4" | "xterm_wheat4" => Color::Fixed(101).normal(),
|
||||||
|
"grey53" | "xterm_grey53" => Color::Fixed(102).normal(),
|
||||||
|
"lightslategrey" | "xterm_lightslategrey" => Color::Fixed(103).normal(),
|
||||||
|
"mediumpurple" | "xterm_mediumpurple" => Color::Fixed(104).normal(),
|
||||||
|
"lightslateblue" | "xterm_lightslateblue" => Color::Fixed(105).normal(),
|
||||||
|
"yellow4b" | "xterm_yellow4b" => Color::Fixed(106).normal(),
|
||||||
|
"darkolivegreen3a" | "xterm_darkolivegreen3a" => Color::Fixed(107).normal(),
|
||||||
|
"darkseagreen" | "xterm_darkseagreen" => Color::Fixed(108).normal(),
|
||||||
|
"lightskyblue3a" | "xterm_lightskyblue3a" => Color::Fixed(109).normal(),
|
||||||
|
"lightskyblue3b" | "xterm_lightskyblue3b" => Color::Fixed(110).normal(),
|
||||||
|
"skyblue2" | "xterm_skyblue2" => Color::Fixed(111).normal(),
|
||||||
|
"chartreuse2b" | "xterm_chartreuse2b" => Color::Fixed(112).normal(),
|
||||||
|
"darkolivegreen3b" | "xterm_darkolivegreen3b" => Color::Fixed(113).normal(),
|
||||||
|
"palegreen3b" | "xterm_palegreen3b" => Color::Fixed(114).normal(),
|
||||||
|
"darkseagreen3a" | "xterm_darkseagreen3a" => Color::Fixed(115).normal(),
|
||||||
|
"darkslategray3" | "xterm_darkslategray3" => Color::Fixed(116).normal(),
|
||||||
|
"skyblue1" | "xterm_skyblue1" => Color::Fixed(117).normal(),
|
||||||
|
"chartreuse1" | "xterm_chartreuse1" => Color::Fixed(118).normal(),
|
||||||
|
"lightgreena" | "xterm_lightgreena" => Color::Fixed(119).normal(),
|
||||||
|
"lightgreenb" | "xterm_lightgreenb" => Color::Fixed(120).normal(),
|
||||||
|
"palegreen1a" | "xterm_palegreen1a" => Color::Fixed(121).normal(),
|
||||||
|
"aquamarine1b" | "xterm_aquamarine1b" => Color::Fixed(122).normal(),
|
||||||
|
"darkslategray1" | "xterm_darkslategray1" => Color::Fixed(123).normal(),
|
||||||
|
"red3a" | "xterm_red3a" => Color::Fixed(124).normal(),
|
||||||
|
"deeppink4c" | "xterm_deeppink4c" => Color::Fixed(125).normal(),
|
||||||
|
"mediumvioletred" | "xterm_mediumvioletred" => Color::Fixed(126).normal(),
|
||||||
|
"magenta3" | "xterm_magenta3" => Color::Fixed(127).normal(),
|
||||||
|
"darkvioletb" | "xterm_darkvioletb" => Color::Fixed(128).normal(),
|
||||||
|
"purplec" | "xterm_purplec" => Color::Fixed(129).normal(),
|
||||||
|
"darkorange3a" | "xterm_darkorange3a" => Color::Fixed(130).normal(),
|
||||||
|
"indianreda" | "xterm_indianreda" => Color::Fixed(131).normal(),
|
||||||
|
"hotpink3a" | "xterm_hotpink3a" => Color::Fixed(132).normal(),
|
||||||
|
"mediumorchid3" | "xterm_mediumorchid3" => Color::Fixed(133).normal(),
|
||||||
|
"mediumorchid" | "xterm_mediumorchid" => Color::Fixed(134).normal(),
|
||||||
|
"mediumpurple2a" | "xterm_mediumpurple2a" => Color::Fixed(135).normal(),
|
||||||
|
"darkgoldenrod" | "xterm_darkgoldenrod" => Color::Fixed(136).normal(),
|
||||||
|
"lightsalmon3a" | "xterm_lightsalmon3a" => Color::Fixed(137).normal(),
|
||||||
|
"rosybrown" | "xterm_rosybrown" => Color::Fixed(138).normal(),
|
||||||
|
"grey63" | "xterm_grey63" => Color::Fixed(139).normal(),
|
||||||
|
"mediumpurple2b" | "xterm_mediumpurple2b" => Color::Fixed(140).normal(),
|
||||||
|
"mediumpurple1" | "xterm_mediumpurple1" => Color::Fixed(141).normal(),
|
||||||
|
"gold3a" | "xterm_gold3a" => Color::Fixed(142).normal(),
|
||||||
|
"darkkhaki" | "xterm_darkkhaki" => Color::Fixed(143).normal(),
|
||||||
|
"navajowhite3" | "xterm_navajowhite3" => Color::Fixed(144).normal(),
|
||||||
|
"grey69" | "xterm_grey69" => Color::Fixed(145).normal(),
|
||||||
|
"lightsteelblue3" | "xterm_lightsteelblue3" => Color::Fixed(146).normal(),
|
||||||
|
"lightsteelblue" | "xterm_lightsteelblue" => Color::Fixed(147).normal(),
|
||||||
|
"yellow3a" | "xterm_yellow3a" => Color::Fixed(148).normal(),
|
||||||
|
"darkolivegreen3c" | "xterm_darkolivegreen3c" => Color::Fixed(149).normal(),
|
||||||
|
"darkseagreen3b" | "xterm_darkseagreen3b" => Color::Fixed(150).normal(),
|
||||||
|
"darkseagreen2a" | "xterm_darkseagreen2a" => Color::Fixed(151).normal(),
|
||||||
|
"lightcyan3" | "xterm_lightcyan3" => Color::Fixed(152).normal(),
|
||||||
|
"lightskyblue1" | "xterm_lightskyblue1" => Color::Fixed(153).normal(),
|
||||||
|
"greenyellow" | "xterm_greenyellow" => Color::Fixed(154).normal(),
|
||||||
|
"darkolivegreen2" | "xterm_darkolivegreen2" => Color::Fixed(155).normal(),
|
||||||
|
"palegreen1b" | "xterm_palegreen1b" => Color::Fixed(156).normal(),
|
||||||
|
"darkseagreen2b" | "xterm_darkseagreen2b" => Color::Fixed(157).normal(),
|
||||||
|
"darkseagreen1a" | "xterm_darkseagreen1a" => Color::Fixed(158).normal(),
|
||||||
|
"paleturquoise1" | "xterm_paleturquoise1" => Color::Fixed(159).normal(),
|
||||||
|
"red3b" | "xterm_red3b" => Color::Fixed(160).normal(),
|
||||||
|
"deeppink3a" | "xterm_deeppink3a" => Color::Fixed(161).normal(),
|
||||||
|
"deeppink3b" | "xterm_deeppink3b" => Color::Fixed(162).normal(),
|
||||||
|
"magenta3a" | "xterm_magenta3a" => Color::Fixed(163).normal(),
|
||||||
|
"magenta3b" | "xterm_magenta3b" => Color::Fixed(164).normal(),
|
||||||
|
"magenta2a" | "xterm_magenta2a" => Color::Fixed(165).normal(),
|
||||||
|
"darkorange3b" | "xterm_darkorange3b" => Color::Fixed(166).normal(),
|
||||||
|
"indianredb" | "xterm_indianredb" => Color::Fixed(167).normal(),
|
||||||
|
"hotpink3b" | "xterm_hotpink3b" => Color::Fixed(168).normal(),
|
||||||
|
"hotpink2" | "xterm_hotpink2" => Color::Fixed(169).normal(),
|
||||||
|
"orchid" | "xterm_orchid" => Color::Fixed(170).normal(),
|
||||||
|
"mediumorchid1a" | "xterm_mediumorchid1a" => Color::Fixed(171).normal(),
|
||||||
|
"orange3" | "xterm_orange3" => Color::Fixed(172).normal(),
|
||||||
|
"lightsalmon3b" | "xterm_lightsalmon3b" => Color::Fixed(173).normal(),
|
||||||
|
"lightpink3" | "xterm_lightpink3" => Color::Fixed(174).normal(),
|
||||||
|
"pink3" | "xterm_pink3" => Color::Fixed(175).normal(),
|
||||||
|
"plum3" | "xterm_plum3" => Color::Fixed(176).normal(),
|
||||||
|
"violet" | "xterm_violet" => Color::Fixed(177).normal(),
|
||||||
|
"gold3b" | "xterm_gold3b" => Color::Fixed(178).normal(),
|
||||||
|
"lightgoldenrod3" | "xterm_lightgoldenrod3" => Color::Fixed(179).normal(),
|
||||||
|
"tan" | "xterm_tan" => Color::Fixed(180).normal(),
|
||||||
|
"mistyrose3" | "xterm_mistyrose3" => Color::Fixed(181).normal(),
|
||||||
|
"thistle3" | "xterm_thistle3" => Color::Fixed(182).normal(),
|
||||||
|
"plum2" | "xterm_plum2" => Color::Fixed(183).normal(),
|
||||||
|
"yellow3b" | "xterm_yellow3b" => Color::Fixed(184).normal(),
|
||||||
|
"khaki3" | "xterm_khaki3" => Color::Fixed(185).normal(),
|
||||||
|
"lightgoldenrod2" | "xterm_lightgoldenrod2" => Color::Fixed(186).normal(),
|
||||||
|
"lightyellow3" | "xterm_lightyellow3" => Color::Fixed(187).normal(),
|
||||||
|
"grey84" | "xterm_grey84" => Color::Fixed(188).normal(),
|
||||||
|
"lightsteelblue1" | "xterm_lightsteelblue1" => Color::Fixed(189).normal(),
|
||||||
|
"yellow2" | "xterm_yellow2" => Color::Fixed(190).normal(),
|
||||||
|
"darkolivegreen1a" | "xterm_darkolivegreen1a" => Color::Fixed(191).normal(),
|
||||||
|
"darkolivegreen1b" | "xterm_darkolivegreen1b" => Color::Fixed(192).normal(),
|
||||||
|
"darkseagreen1b" | "xterm_darkseagreen1b" => Color::Fixed(193).normal(),
|
||||||
|
"honeydew2" | "xterm_honeydew2" => Color::Fixed(194).normal(),
|
||||||
|
"lightcyan1" | "xterm_lightcyan1" => Color::Fixed(195).normal(),
|
||||||
|
"red1" | "xterm_red1" => Color::Fixed(196).normal(),
|
||||||
|
"deeppink2" | "xterm_deeppink2" => Color::Fixed(197).normal(),
|
||||||
|
"deeppink1a" | "xterm_deeppink1a" => Color::Fixed(198).normal(),
|
||||||
|
"deeppink1b" | "xterm_deeppink1b" => Color::Fixed(199).normal(),
|
||||||
|
"magenta2b" | "xterm_magenta2b" => Color::Fixed(200).normal(),
|
||||||
|
"magenta1" | "xterm_magenta1" => Color::Fixed(201).normal(),
|
||||||
|
"orangered1" | "xterm_orangered1" => Color::Fixed(202).normal(),
|
||||||
|
"indianred1a" | "xterm_indianred1a" => Color::Fixed(203).normal(),
|
||||||
|
"indianred1b" | "xterm_indianred1b" => Color::Fixed(204).normal(),
|
||||||
|
"hotpinka" | "xterm_hotpinka" => Color::Fixed(205).normal(),
|
||||||
|
"hotpinkb" | "xterm_hotpinkb" => Color::Fixed(206).normal(),
|
||||||
|
"mediumorchid1b" | "xterm_mediumorchid1b" => Color::Fixed(207).normal(),
|
||||||
|
"darkorange" | "xterm_darkorange" => Color::Fixed(208).normal(),
|
||||||
|
"salmon1" | "xterm_salmon1" => Color::Fixed(209).normal(),
|
||||||
|
"lightcoral" | "xterm_lightcoral" => Color::Fixed(210).normal(),
|
||||||
|
"palevioletred1" | "xterm_palevioletred1" => Color::Fixed(211).normal(),
|
||||||
|
"orchid2" | "xterm_orchid2" => Color::Fixed(212).normal(),
|
||||||
|
"orchid1" | "xterm_orchid1" => Color::Fixed(213).normal(),
|
||||||
|
"orange1" | "xterm_orange1" => Color::Fixed(214).normal(),
|
||||||
|
"sandybrown" | "xterm_sandybrown" => Color::Fixed(215).normal(),
|
||||||
|
"lightsalmon1" | "xterm_lightsalmon1" => Color::Fixed(216).normal(),
|
||||||
|
"lightpink1" | "xterm_lightpink1" => Color::Fixed(217).normal(),
|
||||||
|
"pink1" | "xterm_pink1" => Color::Fixed(218).normal(),
|
||||||
|
"plum1" | "xterm_plum1" => Color::Fixed(219).normal(),
|
||||||
|
"gold1" | "xterm_gold1" => Color::Fixed(220).normal(),
|
||||||
|
"lightgoldenrod2a" | "xterm_lightgoldenrod2a" => Color::Fixed(221).normal(),
|
||||||
|
"lightgoldenrod2b" | "xterm_lightgoldenrod2b" => Color::Fixed(222).normal(),
|
||||||
|
"navajowhite1" | "xterm_navajowhite1" => Color::Fixed(223).normal(),
|
||||||
|
"mistyrose1" | "xterm_mistyrose1" => Color::Fixed(224).normal(),
|
||||||
|
"thistle1" | "xterm_thistle1" => Color::Fixed(225).normal(),
|
||||||
|
"yellow1" | "xterm_yellow1" => Color::Fixed(226).normal(),
|
||||||
|
"lightgoldenrod1" | "xterm_lightgoldenrod1" => Color::Fixed(227).normal(),
|
||||||
|
"khaki1" | "xterm_khaki1" => Color::Fixed(228).normal(),
|
||||||
|
"wheat1" | "xterm_wheat1" => Color::Fixed(229).normal(),
|
||||||
|
"cornsilk1" | "xterm_cornsilk1" => Color::Fixed(230).normal(),
|
||||||
|
"grey100" | "xterm_grey100" => Color::Fixed(231).normal(),
|
||||||
|
"grey3" | "xterm_grey3" => Color::Fixed(232).normal(),
|
||||||
|
"grey7" | "xterm_grey7" => Color::Fixed(233).normal(),
|
||||||
|
"grey11" | "xterm_grey11" => Color::Fixed(234).normal(),
|
||||||
|
"grey15" | "xterm_grey15" => Color::Fixed(235).normal(),
|
||||||
|
"grey19" | "xterm_grey19" => Color::Fixed(236).normal(),
|
||||||
|
"grey23" | "xterm_grey23" => Color::Fixed(237).normal(),
|
||||||
|
"grey27" | "xterm_grey27" => Color::Fixed(238).normal(),
|
||||||
|
"grey30" | "xterm_grey30" => Color::Fixed(239).normal(),
|
||||||
|
"grey35" | "xterm_grey35" => Color::Fixed(240).normal(),
|
||||||
|
"grey39" | "xterm_grey39" => Color::Fixed(241).normal(),
|
||||||
|
"grey42" | "xterm_grey42" => Color::Fixed(242).normal(),
|
||||||
|
"grey46" | "xterm_grey46" => Color::Fixed(243).normal(),
|
||||||
|
"grey50" | "xterm_grey50" => Color::Fixed(244).normal(),
|
||||||
|
"grey54" | "xterm_grey54" => Color::Fixed(245).normal(),
|
||||||
|
"grey58" | "xterm_grey58" => Color::Fixed(246).normal(),
|
||||||
|
"grey62" | "xterm_grey62" => Color::Fixed(247).normal(),
|
||||||
|
"grey66" | "xterm_grey66" => Color::Fixed(248).normal(),
|
||||||
|
"grey70" | "xterm_grey70" => Color::Fixed(249).normal(),
|
||||||
|
"grey74" | "xterm_grey74" => Color::Fixed(250).normal(),
|
||||||
|
"grey78" | "xterm_grey78" => Color::Fixed(251).normal(),
|
||||||
|
"grey82" | "xterm_grey82" => Color::Fixed(252).normal(),
|
||||||
|
"grey85" | "xterm_grey85" => Color::Fixed(253).normal(),
|
||||||
|
"grey89" | "xterm_grey89" => Color::Fixed(254).normal(),
|
||||||
|
"grey93" | "xterm_grey93" => Color::Fixed(255).normal(),
|
||||||
|
_ => Color::White.normal(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_color(s: &str) -> Option<Color> {
|
||||||
|
let color = match s {
|
||||||
|
"g" | "green" => Color::Green,
|
||||||
|
"lg" | "light_green" => Color::LightGreen,
|
||||||
|
"r" | "red" => Color::Red,
|
||||||
|
"lr" | "light_red" => Color::LightRed,
|
||||||
|
"u" | "blue" => Color::Blue,
|
||||||
|
"lu" | "light_blue" => Color::LightBlue,
|
||||||
|
"b" | "black" => Color::Black,
|
||||||
|
"ligr" | "light_gray" => Color::LightGray,
|
||||||
|
"y" | "yellow" => Color::Yellow,
|
||||||
|
"ly" | "light_yellow" => Color::LightYellow,
|
||||||
|
"p" | "purple" => Color::Purple,
|
||||||
|
"lp" | "light_purple" => Color::LightPurple,
|
||||||
|
"c" | "cyan" => Color::Cyan,
|
||||||
|
"lc" | "light_cyan" => Color::LightCyan,
|
||||||
|
"w" | "white" => Color::White,
|
||||||
|
"dgr" | "dark_gray" => Color::DarkGray,
|
||||||
|
"def" | "default" => Color::Default,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_modifiers(attrs: &str, style: &mut Style) {
|
||||||
|
// setup the attributes available in nu_ansi_term::Style
|
||||||
|
//
|
||||||
|
// since we can combine styles like bold-italic, iterate through the chars
|
||||||
|
// and set the bools for later use in the nu_ansi_term::Style application
|
||||||
|
for ch in attrs.to_lowercase().chars() {
|
||||||
|
match ch {
|
||||||
|
'l' => style.is_blink = true,
|
||||||
|
'b' => style.is_bold = true,
|
||||||
|
'd' => style.is_dimmed = true,
|
||||||
|
'h' => style.is_hidden = true,
|
||||||
|
'i' => style.is_italic = true,
|
||||||
|
'r' => style.is_reverse = true,
|
||||||
|
's' => style.is_strikethrough = true,
|
||||||
|
'u' => style.is_underline = true,
|
||||||
|
'n' => (),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_color_str(s: &str) -> Option<Color> {
|
||||||
|
if s.starts_with('#') {
|
||||||
|
color_from_hex(s).ok().and_then(|c| c)
|
||||||
|
} else {
|
||||||
|
lookup_color(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,41 +1,56 @@
|
|||||||
use crate::color_config::lookup_ansi_color_style;
|
use crate::{color_config::lookup_ansi_color_style, color_record_to_nustyle};
|
||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use nu_protocol::Config;
|
use nu_protocol::{Config, Value};
|
||||||
|
|
||||||
|
// The default colors for shapes, used when there is no config for them.
|
||||||
|
pub fn default_shape_color(shape: String) -> Style {
|
||||||
|
match shape.as_ref() {
|
||||||
|
"shape_and" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_bool" => Style::new().fg(Color::LightCyan),
|
||||||
|
"shape_custom" => Style::new().fg(Color::Green),
|
||||||
|
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_directory" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_external" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
|
||||||
|
"shape_filepath" => Style::new().fg(Color::Cyan),
|
||||||
|
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
||||||
|
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_list" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_literal" => Style::new().fg(Color::Blue),
|
||||||
|
"shape_nothing" => Style::new().fg(Color::LightCyan),
|
||||||
|
"shape_operator" => Style::new().fg(Color::Yellow),
|
||||||
|
"shape_or" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_pipe" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
||||||
|
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_redirection" => Style::new().fg(Color::Purple).bold(),
|
||||||
|
"shape_signature" => Style::new().fg(Color::Green).bold(),
|
||||||
|
"shape_string" => Style::new().fg(Color::Green),
|
||||||
|
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
||||||
|
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
||||||
|
"shape_variable" => Style::new().fg(Color::Purple),
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
||||||
match conf.color_config.get(shape.as_str()) {
|
match conf.color_config.get(shape.as_str()) {
|
||||||
Some(int_color) => match int_color.as_string() {
|
Some(int_color) => {
|
||||||
Ok(int_color) => lookup_ansi_color_style(&int_color),
|
// Shapes do not use color_config closures, currently.
|
||||||
Err(_) => Style::default(),
|
match int_color {
|
||||||
},
|
Value::Record { .. } => color_record_to_nustyle(int_color),
|
||||||
None => match shape.as_ref() {
|
Value::String { val, .. } => lookup_ansi_color_style(val),
|
||||||
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
|
// Defer to the default in the event of incorrect types being given
|
||||||
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
// (i.e. treat null, etc. as the value being unset)
|
||||||
"shape_bool" => Style::new().fg(Color::LightCyan),
|
_ => default_shape_color(shape),
|
||||||
"shape_int" => Style::new().fg(Color::Purple).bold(),
|
}
|
||||||
"shape_float" => Style::new().fg(Color::Purple).bold(),
|
}
|
||||||
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
None => default_shape_color(shape),
|
||||||
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_external" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_externalarg" => Style::new().fg(Color::Green).bold(),
|
|
||||||
"shape_literal" => Style::new().fg(Color::Blue),
|
|
||||||
"shape_operator" => Style::new().fg(Color::Yellow),
|
|
||||||
"shape_signature" => Style::new().fg(Color::Green).bold(),
|
|
||||||
"shape_string" => Style::new().fg(Color::Green),
|
|
||||||
"shape_string_interpolation" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_datetime" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_list" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_filepath" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_directory" => Style::new().fg(Color::Cyan),
|
|
||||||
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
|
|
||||||
"shape_variable" => Style::new().fg(Color::Purple),
|
|
||||||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
|
||||||
"shape_custom" => Style::new().fg(Color::Green),
|
|
||||||
"shape_nothing" => Style::new().fg(Color::LightCyan),
|
|
||||||
_ => Style::default(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
285
crates/nu-color-config/src/style_computer.rs
Normal file
285
crates/nu-color-config/src/style_computer.rs
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle};
|
||||||
|
use nu_ansi_term::{Color, Style};
|
||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
CliError, IntoPipelineData, Value,
|
||||||
|
};
|
||||||
|
use tabled::alignment::AlignmentHorizontal;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::{Debug, Formatter, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ComputableStyle represents the valid user style types: a single color value, or a closure which
|
||||||
|
// takes an input value and produces a color value. The latter represents a value which
|
||||||
|
// is computed at use-time.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ComputableStyle {
|
||||||
|
Static(Style),
|
||||||
|
Closure(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
// macro used for adding initial values to the style hashmap
|
||||||
|
macro_rules! initial {
|
||||||
|
($a:expr, $b:expr) => {
|
||||||
|
($a.to_string(), ComputableStyle::Static($b))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// An alias for the mapping used internally by StyleComputer.
|
||||||
|
pub type StyleMapping = HashMap<String, ComputableStyle>;
|
||||||
|
//
|
||||||
|
// A StyleComputer is an all-in-one way to compute styles. A nu command can
|
||||||
|
// simply create it with from_config(), and then use it with compute().
|
||||||
|
// It stores the engine state and stack needed to run closures that
|
||||||
|
// may be defined as a user style.
|
||||||
|
//
|
||||||
|
pub struct StyleComputer<'a> {
|
||||||
|
engine_state: &'a EngineState,
|
||||||
|
stack: &'a Stack,
|
||||||
|
map: StyleMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StyleComputer<'a> {
|
||||||
|
// This is NOT meant to be used in most cases - please use from_config() instead.
|
||||||
|
// This only exists for testing purposes.
|
||||||
|
pub fn new(
|
||||||
|
engine_state: &'a EngineState,
|
||||||
|
stack: &'a Stack,
|
||||||
|
map: StyleMapping,
|
||||||
|
) -> StyleComputer<'a> {
|
||||||
|
StyleComputer {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The main method. Takes a string name which maps to a color_config style name,
|
||||||
|
// and a Nu value to pipe into any closures that may have been defined there.
|
||||||
|
pub fn compute(&self, style_name: &str, value: &Value) -> Style {
|
||||||
|
match self.map.get(style_name) {
|
||||||
|
// Static values require no computation.
|
||||||
|
Some(ComputableStyle::Static(s)) => *s,
|
||||||
|
// Closures are run here.
|
||||||
|
Some(ComputableStyle::Closure(Value::Closure {
|
||||||
|
val: block_id,
|
||||||
|
captures,
|
||||||
|
span,
|
||||||
|
})) => {
|
||||||
|
let block = self.engine_state.get_block(*block_id).clone();
|
||||||
|
// Because captures_to_stack() clones, we don't need to use with_env() here
|
||||||
|
// (contrast with_env() usage in `each` or `do`).
|
||||||
|
let mut stack = self.stack.captures_to_stack(captures);
|
||||||
|
|
||||||
|
// Support 1-argument blocks as well as 0-argument blocks.
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack.add_var(*var_id, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the block.
|
||||||
|
match eval_block(
|
||||||
|
self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
&block,
|
||||||
|
value.clone().into_pipeline_data(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
Ok(v) => {
|
||||||
|
let value = v.into_value(*span);
|
||||||
|
// These should be the same color data forms supported by color_config.
|
||||||
|
match value {
|
||||||
|
Value::Record { .. } => color_record_to_nustyle(&value),
|
||||||
|
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is basically a copy of nu_cli::report_error(), but that isn't usable due to
|
||||||
|
// dependencies. While crudely spitting out a bunch of errors like this is not ideal,
|
||||||
|
// currently hook closure errors behave roughly the same.
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Error: {:?}",
|
||||||
|
CliError(&e, &StateWorkingSet::new(self.engine_state))
|
||||||
|
);
|
||||||
|
Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There should be no other kinds of values (due to create_map() in config.rs filtering them out)
|
||||||
|
// so this is just a fallback.
|
||||||
|
_ => Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used only by the `table` command.
|
||||||
|
pub fn style_primitive(&self, value: &Value) -> TextStyle {
|
||||||
|
let s = self.compute(&value.get_type().to_string(), value);
|
||||||
|
match *value {
|
||||||
|
Value::Bool { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Int { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Filesize { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Duration { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::Date { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Range { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Float { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s),
|
||||||
|
|
||||||
|
Value::String { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Nothing { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Binary { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::CellPath { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s),
|
||||||
|
|
||||||
|
Value::Record { .. } | Value::List { .. } | Value::Block { .. } => {
|
||||||
|
TextStyle::with_style(AlignmentHorizontal::Left, s)
|
||||||
|
}
|
||||||
|
_ => TextStyle::basic_left(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main constructor.
|
||||||
|
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
|
// Create the hashmap
|
||||||
|
let mut map: StyleMapping = HashMap::from([
|
||||||
|
initial!("separator", Color::White.normal()),
|
||||||
|
initial!(
|
||||||
|
"leading_trailing_space_bg",
|
||||||
|
Style::default().on(Color::Rgb(128, 128, 128))
|
||||||
|
),
|
||||||
|
initial!("header", Color::White.normal()),
|
||||||
|
initial!("empty", Color::White.normal()),
|
||||||
|
initial!("bool", Color::White.normal()),
|
||||||
|
initial!("int", Color::White.normal()),
|
||||||
|
initial!("filesize", Color::White.normal()),
|
||||||
|
initial!("duration", Color::White.normal()),
|
||||||
|
initial!("date", Color::White.normal()),
|
||||||
|
initial!("range", Color::White.normal()),
|
||||||
|
initial!("float", Color::White.normal()),
|
||||||
|
initial!("string", Color::White.normal()),
|
||||||
|
initial!("nothing", Color::White.normal()),
|
||||||
|
initial!("binary", Color::White.normal()),
|
||||||
|
initial!("cellpath", Color::White.normal()),
|
||||||
|
initial!("row_index", Color::Green.bold()),
|
||||||
|
initial!("record", Color::White.normal()),
|
||||||
|
initial!("list", Color::White.normal()),
|
||||||
|
initial!("block", Color::White.normal()),
|
||||||
|
initial!("hints", Color::DarkGray.normal()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (key, value) in &config.color_config {
|
||||||
|
match value {
|
||||||
|
Value::Closure { .. } => {
|
||||||
|
map.insert(key.to_string(), ComputableStyle::Closure(value.clone()));
|
||||||
|
}
|
||||||
|
Value::Record { .. } => {
|
||||||
|
map.insert(
|
||||||
|
key.to_string(),
|
||||||
|
ComputableStyle::Static(color_record_to_nustyle(value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
// update the stylemap with the found key
|
||||||
|
let color = lookup_ansi_color_style(val.as_str());
|
||||||
|
if let Some(v) = map.get_mut(key) {
|
||||||
|
*v = ComputableStyle::Static(color);
|
||||||
|
} else {
|
||||||
|
map.insert(key.to_string(), ComputableStyle::Static(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never occur.
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleComputer::new(engine_state, stack, map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because EngineState doesn't have Debug (Dec 2022),
|
||||||
|
// this incomplete representation must be used.
|
||||||
|
impl<'a> Debug for StyleComputer<'a> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
|
f.debug_struct("StyleComputer")
|
||||||
|
.field("map", &self.map)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_static() {
|
||||||
|
use nu_protocol::Span;
|
||||||
|
|
||||||
|
let style1 = Style::default().italic();
|
||||||
|
let style2 = Style::default().underline();
|
||||||
|
// Create a "dummy" style_computer for this test.
|
||||||
|
let dummy_engine_state = EngineState::new();
|
||||||
|
let dummy_stack = Stack::new();
|
||||||
|
let style_computer = StyleComputer::new(
|
||||||
|
&dummy_engine_state,
|
||||||
|
&dummy_stack,
|
||||||
|
HashMap::from([
|
||||||
|
("string".into(), ComputableStyle::Static(style1)),
|
||||||
|
("row_index".into(), ComputableStyle::Static(style2)),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
style_computer.compute("string", &Value::nothing(Span::unknown())),
|
||||||
|
style1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
style_computer.compute("row_index", &Value::nothing(Span::unknown())),
|
||||||
|
style2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because each closure currently runs in a separate environment, checks that the closures have run
|
||||||
|
// must use the filesystem.
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_closure_basic() {
|
||||||
|
use nu_test_support::{nu, nu_repl_code, playground::Playground};
|
||||||
|
Playground::setup("computable_style_closure_basic", |dirs, _| {
|
||||||
|
let inp = [
|
||||||
|
r#"let-env config = {
|
||||||
|
color_config: {
|
||||||
|
string: {|e| touch ($e + '.obj'); 'red' }
|
||||||
|
}
|
||||||
|
};"#,
|
||||||
|
"[bell book candle] | table | ignore",
|
||||||
|
"ls | get name | to nuon",
|
||||||
|
];
|
||||||
|
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
|
||||||
|
assert_eq!(actual_repl.err, "");
|
||||||
|
assert_eq!(actual_repl.out, "[bell.obj, book.obj, candle.obj]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_computable_style_closure_errors() {
|
||||||
|
use nu_test_support::{nu, nu_repl_code};
|
||||||
|
let inp = [
|
||||||
|
r#"let-env config = {
|
||||||
|
color_config: {
|
||||||
|
string: {|e| $e + 2 }
|
||||||
|
}
|
||||||
|
};"#,
|
||||||
|
"[bell] | table",
|
||||||
|
];
|
||||||
|
let actual_repl = nu!(cwd: ".", nu_repl_code(&inp));
|
||||||
|
// Check that the error was printed
|
||||||
|
assert!(actual_repl.err.contains("type mismatch for operator"));
|
||||||
|
// Check that the value was printed
|
||||||
|
assert!(actual_repl.out.contains("bell"));
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
pub type Alignment = tabled::alignment::AlignmentHorizontal;
|
pub type Alignment = tabled::alignment::AlignmentHorizontal;
|
||||||
|
|
||||||
@ -239,3 +240,23 @@ impl Default for TextStyle {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl tabled::papergrid::Color for TextStyle {
|
||||||
|
fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(color) = &self.color_style {
|
||||||
|
color.prefix().fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(color) = &self.color_style {
|
||||||
|
if !color.is_plain() {
|
||||||
|
f.write_str("\u{1b}[0m")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -5,91 +5,97 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.71.0"
|
version = "0.75.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.75.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
nu-engine = { path = "../nu-engine", version = "0.75.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.71.0" }
|
nu-glob = { path = "../nu-glob", version = "0.75.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.71.0" }
|
nu-json = { path = "../nu-json", version = "0.75.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
nu-parser = { path = "../nu-parser", version = "0.75.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.71.0" }
|
nu-path = { path = "../nu-path", version = "0.75.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.75.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.75.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.71.0" }
|
nu-system = { path = "../nu-system", version = "0.75.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.71.0" }
|
nu-table = { path = "../nu-table", version = "0.75.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.75.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
nu-utils = { path = "../nu-utils", version = "0.75.0" }
|
||||||
|
nu-explore = { path = "../nu-explore", version = "0.75.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
num-format = { version = "0.4.3" }
|
num-format = { version = "0.4.3" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
base64 = "0.13.0"
|
atty = "0.2.14"
|
||||||
|
base64 = "0.21.0"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
calamine = "0.18.0"
|
calamine = "0.19.1"
|
||||||
chrono = { version = "0.4.21", features = ["serde", "unstable-locales"] }
|
chrono = { version = "0.4.23", features = ["unstable-locales", "std"], default-features = false }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
chrono-tz = "0.6.3"
|
chrono-tz = "0.8.1"
|
||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
dialoguer = "0.9.0"
|
dialoguer = { default-features = false, version = "0.10.3" }
|
||||||
digest = "0.10.0"
|
digest = { default-features = false, version = "0.10.0" }
|
||||||
dtparse = "1.2.0"
|
dtparse = "1.2.0"
|
||||||
eml-parser = "0.1.0"
|
eml-parser = "0.1.0"
|
||||||
encoding_rs = "0.8.30"
|
encoding_rs = "0.8.30"
|
||||||
fancy-regex = "0.10.0"
|
fancy-regex = "0.11.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
filetime = "0.2.15"
|
filetime = "0.2.15"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
htmlescape = "0.3.1"
|
htmlescape = "0.3.1"
|
||||||
ical = "0.7.0"
|
ical = "0.7.0"
|
||||||
indexmap = { version="1.7", features=["serde-1"] }
|
indexmap = { version = "1.7", features = ["serde-1"] }
|
||||||
|
indicatif = "0.17.2"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
is-root = "0.1.2"
|
is-root = "0.1.2"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
meval = "0.2.0"
|
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
|
mime_guess = "2.0.4"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
num = { version = "0.4.0", optional = true }
|
num = { version = "0.4.0", optional = true }
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.14"
|
||||||
|
once_cell = "1.17"
|
||||||
|
open = "3.2.0"
|
||||||
pathdiff = "0.2.1"
|
pathdiff = "0.2.1"
|
||||||
powierza-coefficient = "1.0.1"
|
powierza-coefficient = "1.0.2"
|
||||||
quick-xml = "0.23.0"
|
quick-xml = "0.27"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1.5.1"
|
rayon = "1.6.1"
|
||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
regex = "1.7.1"
|
||||||
roxmltree = "0.14.0"
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
|
roxmltree = "0.17.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
same-file = "1.0.6"
|
same-file = "1.0.6"
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version = "1.0.123", features = ["derive"] }
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.9.4"
|
serde_yaml = "0.9.4"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
shadow-rs = { version = "0.16.1", default-features = false }
|
shadow-rs = { version = "0.20.0", default-features = false }
|
||||||
sysinfo = "0.26.2"
|
sysinfo = "0.27.7"
|
||||||
terminal_size = "0.2.1"
|
terminal_size = "0.2.1"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
titlecase = "2.0.0"
|
titlecase = "2.0.0"
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
unicode-segmentation = "1.8.0"
|
unicode-segmentation = "1.8.0"
|
||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
percent-encoding = "2.2.0"
|
||||||
|
uuid = { version = "1.2.2", features = ["v4"] }
|
||||||
which = { version = "4.3.0", optional = true }
|
which = { version = "4.3.0", optional = true }
|
||||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
||||||
wax = { version = "0.5.0" }
|
wax = { version = "0.5.0" }
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
@ -100,11 +106,11 @@ users = "0.11.0"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
version = "2.1.3"
|
version = "3.0.1"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
version = "0.23.2"
|
version = "0.26.1"
|
||||||
optional = true
|
optional = true
|
||||||
features = [
|
features = [
|
||||||
"arg_where",
|
"arg_where",
|
||||||
@ -135,29 +141,25 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
version = "0.42.0"
|
version = "0.44.0"
|
||||||
features = [
|
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"]
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_Storage_FileSystem",
|
|
||||||
"Win32_System_SystemServices",
|
|
||||||
]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
plugin = ["nu-parser/plugin"]
|
plugin = ["nu-parser/plugin"]
|
||||||
dataframe = ["polars", "num"]
|
dataframe = ["polars", "num", "sqlparser"]
|
||||||
database = ["sqlparser", "rusqlite"]
|
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.16.1", default-features = false }
|
shadow-rs = { version = "0.20.0", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.75.0" }
|
||||||
|
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
proptest = "1.0.0"
|
proptest = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = { version = "0.15.0", default-features = false }
|
||||||
|
@ -4,7 +4,7 @@ fn main() -> shadow_rs::SdResult<()> {
|
|||||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||||
let hash = get_git_hash().unwrap_or_default();
|
let hash = get_git_hash().unwrap_or_default();
|
||||||
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
|
println!("cargo:rustc-env=NU_COMMIT_HASH={hash}");
|
||||||
|
|
||||||
shadow_rs::new()
|
shadow_rs::new()
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +15,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits and")
|
Signature::build("bits and")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
@ -41,6 +43,10 @@ impl Command for SubCommand {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| operate(value, target, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -52,10 +58,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Apply bits and to two numbers",
|
description: "Apply bits and to two numbers",
|
||||||
example: "2 | bits and 2",
|
example: "2 | bits and 2",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(2)),
|
||||||
val: 2,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical and to a list of numbers",
|
description: "Apply logical and to a list of numbers",
|
||||||
@ -75,13 +78,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
|
|||||||
val: val & target,
|
val: val & target,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,13 +14,19 @@ impl Command for Bits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits").category(Category::Bits)
|
Signature::build("bits")
|
||||||
|
.category(Category::Bits)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Various commands for working with bits"
|
"Various commands for working with bits"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -29,21 +35,15 @@ impl Command for Bits {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(&Bits.signature(), &Bits.examples(), engine_state, stack),
|
val: get_full_help(
|
||||||
|
&Bits.signature(),
|
||||||
|
&Bits.examples(),
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
span: call.head,
|
span: call.head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::Bits;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Bits {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,6 +16,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits not")
|
Signature::build("bits not")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
"always treat input number as a signed number",
|
"always treat input number as a signed number",
|
||||||
@ -54,11 +56,17 @@ impl Command for SubCommand {
|
|||||||
if let Some(val) = number_bytes {
|
if let Some(val) = number_bytes {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
val.span,
|
val.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, head, signed, bytes_len),
|
move |value| operate(value, head, signed, bytes_len),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -138,14 +146,17 @@ fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) ->
|
|||||||
Value::Int { val: out_val, span }
|
Value::Int { val: out_val, span }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other => Value::Error {
|
other => match other {
|
||||||
error: ShellError::UnsupportedInput(
|
// Propagate errors inside the value
|
||||||
format!(
|
Value::Error { .. } => other,
|
||||||
"Only numerical values are supported, input type: {:?}",
|
_ => Value::Error {
|
||||||
other.get_type()
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"numeric".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
other.span().unwrap_or(head),
|
},
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +15,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits or")
|
Signature::build("bits or")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
@ -41,6 +43,10 @@ impl Command for SubCommand {
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| operate(value, target, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -52,10 +58,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Apply bits or to two numbers",
|
description: "Apply bits or to two numbers",
|
||||||
example: "2 | bits or 6",
|
example: "2 | bits or 6",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(6)),
|
||||||
val: 6,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical or to a list of numbers",
|
description: "Apply logical or to a list of numbers",
|
||||||
@ -75,13 +78,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
|
|||||||
val: val | target,
|
val: val | target,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::int::PrimInt;
|
use num_traits::int::PrimInt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -18,6 +18,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits rol")
|
Signature::build("bits rol")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -58,11 +60,16 @@ impl Command for SubCommand {
|
|||||||
if let Some(val) = number_bytes {
|
if let Some(val) = number_bytes {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
val.span,
|
val.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -74,10 +81,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Rotate left a number with 2 bits",
|
description: "Rotate left a number with 2 bits",
|
||||||
example: "17 | bits rol 2",
|
example: "17 | bits rol 2",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(68)),
|
||||||
val: 68,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Rotate left a list of numbers with 2 bits",
|
description: "Rotate left a list of numbers with 2 bits",
|
||||||
@ -102,8 +106,7 @@ where
|
|||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
"Rotate left result beyond the range of 64 bit signed number".to_string(),
|
||||||
format!(
|
format!(
|
||||||
"{} of the specified number of bytes rotate left {} bits exceed limit",
|
"{val} of the specified number of bytes rotate left {bits} bits exceed limit"
|
||||||
val, bits
|
|
||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
@ -128,16 +131,18 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
|
|||||||
SignedOne => get_rotate_left(val as i8, bits, span),
|
SignedOne => get_rotate_left(val as i8, bits, span),
|
||||||
SignedTwo => get_rotate_left(val as i16, bits, span),
|
SignedTwo => get_rotate_left(val as i16, bits, span),
|
||||||
SignedFour => get_rotate_left(val as i32, bits, span),
|
SignedFour => get_rotate_left(val as i32, bits, span),
|
||||||
SignedEight => get_rotate_left(val as i64, bits, span),
|
SignedEight => get_rotate_left(val, bits, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::int::PrimInt;
|
use num_traits::int::PrimInt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -18,6 +18,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits ror")
|
Signature::build("bits ror")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -58,11 +60,16 @@ impl Command for SubCommand {
|
|||||||
if let Some(val) = number_bytes {
|
if let Some(val) = number_bytes {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
val.span,
|
val.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -74,10 +81,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Rotate right a number with 60 bits",
|
description: "Rotate right a number with 60 bits",
|
||||||
example: "17 | bits ror 60",
|
example: "17 | bits ror 60",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(272)),
|
||||||
val: 272,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Rotate right a list of numbers of one byte",
|
description: "Rotate right a list of numbers of one byte",
|
||||||
@ -106,8 +110,7 @@ where
|
|||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
"Rotate right result beyond the range of 64 bit signed number".to_string(),
|
||||||
format!(
|
format!(
|
||||||
"{} of the specified number of bytes rotate right {} bits exceed limit",
|
"{val} of the specified number of bytes rotate right {bits} bits exceed limit"
|
||||||
val, bits
|
|
||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
@ -132,16 +135,18 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
|
|||||||
SignedOne => get_rotate_right(val as i8, bits, span),
|
SignedOne => get_rotate_right(val as i8, bits, span),
|
||||||
SignedTwo => get_rotate_right(val as i16, bits, span),
|
SignedTwo => get_rotate_right(val as i16, bits, span),
|
||||||
SignedFour => get_rotate_right(val as i32, bits, span),
|
SignedFour => get_rotate_right(val as i32, bits, span),
|
||||||
SignedEight => get_rotate_right(val as i64, bits, span),
|
SignedEight => get_rotate_right(val, bits, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::CheckedShl;
|
use num_traits::CheckedShl;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -18,6 +18,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits shl")
|
Signature::build("bits shl")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -58,11 +60,16 @@ impl Command for SubCommand {
|
|||||||
if let Some(val) = number_bytes {
|
if let Some(val) = number_bytes {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
val.span,
|
val.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -74,26 +81,17 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Shift left a number by 7 bits",
|
description: "Shift left a number by 7 bits",
|
||||||
example: "2 | bits shl 7",
|
example: "2 | bits shl 7",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(256)),
|
||||||
val: 256,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a number with 1 byte by 7 bits",
|
description: "Shift left a number with 1 byte by 7 bits",
|
||||||
example: "2 | bits shl 7 -n 1",
|
example: "2 | bits shl 7 -n 1",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(0)),
|
||||||
val: 0,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a signed number by 1 bit",
|
description: "Shift left a signed number by 1 bit",
|
||||||
example: "0x7F | bits shl 1 -s",
|
example: "0x7F | bits shl 1 -s",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(254)),
|
||||||
val: 254,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift left a list of numbers",
|
description: "Shift left a list of numbers",
|
||||||
@ -120,8 +118,7 @@ where
|
|||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
"Shift left result beyond the range of 64 bit signed number".to_string(),
|
||||||
format!(
|
format!(
|
||||||
"{} of the specified number of bytes shift left {} bits exceed limit",
|
"{val} of the specified number of bytes shift left {bits} bits exceed limit"
|
||||||
val, bits
|
|
||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
@ -133,10 +130,7 @@ where
|
|||||||
None => Value::Error {
|
None => Value::Error {
|
||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Shift left failed".to_string(),
|
"Shift left failed".to_string(),
|
||||||
format!(
|
format!("{val} shift left {bits} bits failed, you may shift too many bits"),
|
||||||
"{} shift left {} bits failed, you may shift too many bits",
|
|
||||||
val, bits
|
|
||||||
),
|
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@ -160,16 +154,18 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
|
|||||||
SignedOne => get_shift_left(val as i8, bits, span),
|
SignedOne => get_shift_left(val as i8, bits, span),
|
||||||
SignedTwo => get_shift_left(val as i16, bits, span),
|
SignedTwo => get_shift_left(val as i16, bits, span),
|
||||||
SignedFour => get_shift_left(val as i32, bits, span),
|
SignedFour => get_shift_left(val as i32, bits, span),
|
||||||
SignedEight => get_shift_left(val as i64, bits, span),
|
SignedEight => get_shift_left(val, bits, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use num_traits::CheckedShr;
|
use num_traits::CheckedShr;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -18,6 +18,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits shr")
|
Signature::build("bits shr")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
@ -58,11 +60,16 @@ impl Command for SubCommand {
|
|||||||
if let Some(val) = number_bytes {
|
if let Some(val) = number_bytes {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
val.span,
|
val.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, bits, head, signed, bytes_len),
|
move |value| operate(value, bits, head, signed, bytes_len),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -74,10 +81,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Shift right a number with 2 bits",
|
description: "Shift right a number with 2 bits",
|
||||||
example: "8 | bits shr 2",
|
example: "8 | bits shr 2",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(2)),
|
||||||
val: 2,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shift right a list of numbers",
|
description: "Shift right a list of numbers",
|
||||||
@ -104,8 +108,7 @@ where
|
|||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
"Shift right result beyond the range of 64 bit signed number".to_string(),
|
||||||
format!(
|
format!(
|
||||||
"{} of the specified number of bytes shift right {} bits exceed limit",
|
"{val} of the specified number of bytes shift right {bits} bits exceed limit"
|
||||||
val, bits
|
|
||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
@ -117,10 +120,7 @@ where
|
|||||||
None => Value::Error {
|
None => Value::Error {
|
||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"Shift right failed".to_string(),
|
"Shift right failed".to_string(),
|
||||||
format!(
|
format!("{val} shift right {bits} bits failed, you may shift too many bits"),
|
||||||
"{} shift right {} bits failed, you may shift too many bits",
|
|
||||||
val, bits
|
|
||||||
),
|
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@ -144,16 +144,18 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
|
|||||||
SignedOne => get_shift_right(val as i8, bits, span),
|
SignedOne => get_shift_right(val as i8, bits, span),
|
||||||
SignedTwo => get_shift_right(val as i16, bits, span),
|
SignedTwo => get_shift_right(val as i16, bits, span),
|
||||||
SignedFour => get_shift_right(val as i32, bits, span),
|
SignedFour => get_shift_right(val as i32, bits, span),
|
||||||
SignedEight => get_shift_right(val as i64, bits, span),
|
SignedEight => get_shift_right(val, bits, span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +15,8 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bits xor")
|
Signature::build("bits xor")
|
||||||
|
.input_output_types(vec![(Type::Int, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
@ -40,7 +42,10 @@ impl Command for SubCommand {
|
|||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let target: i64 = call.req(engine_state, stack, 0)?;
|
let target: i64 = call.req(engine_state, stack, 0)?;
|
||||||
|
// This doesn't match explicit nulls
|
||||||
|
if matches!(input, PipelineData::Empty) {
|
||||||
|
return Err(ShellError::PipelineEmpty(head));
|
||||||
|
}
|
||||||
input.map(
|
input.map(
|
||||||
move |value| operate(value, target, head),
|
move |value| operate(value, target, head),
|
||||||
engine_state.ctrlc.clone(),
|
engine_state.ctrlc.clone(),
|
||||||
@ -52,10 +57,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Apply bits xor to two numbers",
|
description: "Apply bits xor to two numbers",
|
||||||
example: "2 | bits xor 2",
|
example: "2 | bits xor 2",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(0)),
|
||||||
val: 0,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Apply logical xor to a list of numbers",
|
description: "Apply logical xor to a list of numbers",
|
||||||
@ -75,13 +77,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
|
|||||||
val: val ^ target,
|
val: val ^ target,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => value,
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Only integer values are supported, input type: {:?}",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(head),
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
added_data: Vec<u8>,
|
added_data: Vec<u8>,
|
||||||
@ -30,6 +30,8 @@ impl Command for BytesAdd {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes add")
|
Signature::build("bytes add")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("data", SyntaxShape::Binary, "the binary to add")
|
.required("data", SyntaxShape::Binary, "the binary to add")
|
||||||
.named(
|
.named(
|
||||||
"index",
|
"index",
|
||||||
@ -41,7 +43,7 @@ impl Command for BytesAdd {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally matches prefix of text by column paths",
|
"for a data structure input, add bytes to the data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -120,13 +122,15 @@ fn add(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => add_impl(val, args, *val_span),
|
} => add_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
@ -30,7 +30,9 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
|
|||||||
Value::List { mut vals, span } => {
|
Value::List { mut vals, span } => {
|
||||||
if vals.len() != 2 {
|
if vals.len() != 2 {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"More than two indices given".to_string(),
|
"More than two indices in range".to_string(),
|
||||||
|
"value originates from here".to_string(),
|
||||||
|
head,
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@ -38,10 +40,14 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
|
|||||||
let end = match end {
|
let end = match end {
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::String { val, .. } => val,
|
Value::String { val, .. } => val,
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
"Only string or list<int> ranges are supported".into(),
|
||||||
other.span().unwrap_or(head),
|
format!("input type: {:?}", other.get_type()),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -49,10 +55,14 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
|
|||||||
let start = match start {
|
let start = match start {
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::String { val, .. } => val,
|
Value::String { val, .. } => val,
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
"Only string or list<int> ranges are supported".into(),
|
||||||
other.span().unwrap_or(head),
|
format!("input type: {:?}", other.get_type()),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -60,21 +70,27 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::String { val, span } => {
|
Value::String { val, span } => {
|
||||||
let splitted_result = val.split_once(',');
|
let split_result = val.split_once(',');
|
||||||
match splitted_result {
|
match split_result {
|
||||||
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
||||||
None => {
|
None => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"could not perform subbytes".to_string(),
|
"could not perform subbytes".to_string(),
|
||||||
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
span,
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
"could not perform subbytes".to_string(),
|
"could not perform subbytes".to_string(),
|
||||||
other.span().unwrap_or(head),
|
"with this range".to_string(),
|
||||||
|
head,
|
||||||
|
other.expect_span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,28 +98,26 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
|
|||||||
let start: isize = if start.is_empty() || start == "_" {
|
let start: isize = if start.is_empty() || start == "_" {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
match start.trim().parse() {
|
start.trim().parse().map_err(|_| {
|
||||||
Ok(s) => s,
|
ShellError::UnsupportedInput(
|
||||||
Err(_) => {
|
"could not perform subbytes".to_string(),
|
||||||
return Err(ShellError::UnsupportedInput(
|
"with this range".to_string(),
|
||||||
"could not perform subbytes".to_string(),
|
head,
|
||||||
span,
|
span,
|
||||||
))
|
)
|
||||||
}
|
})?
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let end: isize = if end.is_empty() || end == "_" {
|
let end: isize = if end.is_empty() || end == "_" {
|
||||||
isize::max_value()
|
isize::max_value()
|
||||||
} else {
|
} else {
|
||||||
match end.trim().parse() {
|
end.trim().parse().map_err(|_| {
|
||||||
Ok(s) => s,
|
ShellError::UnsupportedInput(
|
||||||
Err(_) => {
|
"could not perform subbytes".to_string(),
|
||||||
return Err(ShellError::UnsupportedInput(
|
"with this range".to_string(),
|
||||||
"could not perform subbytes".to_string(),
|
head,
|
||||||
span,
|
span,
|
||||||
))
|
)
|
||||||
}
|
})?
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Ok((start, end, span))
|
Ok((start, end, span))
|
||||||
}
|
}
|
||||||
@ -115,11 +129,13 @@ impl Command for BytesAt {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes at")
|
Signature::build("bytes at")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally get bytes by column paths",
|
"for a data structure input, get bytes from data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -230,13 +246,15 @@ fn at(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => at_impl(val, args, *val_span),
|
} => at_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -260,7 +278,7 @@ fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
|||||||
match start.cmp(&end) {
|
match start.cmp(&end) {
|
||||||
Ordering::Equal => Value::Binary { val: vec![], span },
|
Ordering::Equal => Value::Binary { val: vec![], span },
|
||||||
Ordering::Greater => Value::Error {
|
Ordering::Greater => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::TypeMismatch(
|
||||||
"End must be greater than or equal to Start".to_string(),
|
"End must be greater than or equal to Start".to_string(),
|
||||||
arg.arg_span,
|
arg.arg_span,
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -24,6 +24,7 @@ impl Command for BytesBuild {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("bytes build")
|
Signature::build("bytes build")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Binary)])
|
||||||
.rest("rest", SyntaxShape::Any, "list of bytes")
|
.rest("rest", SyntaxShape::Any, "list of bytes")
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -51,10 +52,12 @@ impl Command for BytesBuild {
|
|||||||
let val = eval_expression(engine_state, stack, expr)?;
|
let val = eval_expression(engine_state, stack, expr)?;
|
||||||
match val {
|
match val {
|
||||||
Value::Binary { mut val, .. } => output.append(&mut val),
|
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"only support expression which yields to binary data".to_string(),
|
"only binary data arguments are supported".to_string(),
|
||||||
other.span().unwrap_or(call.head),
|
other.expect_span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,13 +14,19 @@ impl Command for Bytes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes").category(Category::Bytes)
|
Signature::build("bytes")
|
||||||
|
.category(Category::Bytes)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Various commands for working with byte data"
|
"Various commands for working with byte data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -29,21 +35,15 @@ impl Command for Bytes {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
|
val: get_full_help(
|
||||||
|
&Bytes.signature(),
|
||||||
|
&Bytes.examples(),
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
span: call.head,
|
span: call.head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::Bytes;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Bytes {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -16,6 +16,7 @@ impl Command for BytesCollect {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes collect")
|
Signature::build("bytes collect")
|
||||||
|
.input_output_types(vec![(Type::List(Box::new(Type::Binary)), Type::Binary)])
|
||||||
.optional(
|
.optional(
|
||||||
"separator",
|
"separator",
|
||||||
SyntaxShape::Binary,
|
SyntaxShape::Binary,
|
||||||
@ -53,14 +54,16 @@ impl Command for BytesCollect {
|
|||||||
output_binary.append(&mut work_sep)
|
output_binary.append(&mut work_sep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Explicitly propagate errors instead of dropping them.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"integer".into(),
|
||||||
"The element type is {}, this command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
call.head,
|
||||||
),
|
// This line requires the Value::Error match above.
|
||||||
other.span().unwrap_or(call.head),
|
other.expect_span(),
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
@ -28,11 +28,12 @@ impl Command for BytesEndsWith {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes ends-with")
|
Signature::build("bytes ends-with")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Bool)])
|
||||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally matches prefix of text by column paths",
|
"for a data structure input, check if bytes at the given cell paths end with the pattern",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -67,26 +68,17 @@ impl Command for BytesEndsWith {
|
|||||||
Example {
|
Example {
|
||||||
description: "Checks if binary ends with `0x[AA]`",
|
description: "Checks if binary ends with `0x[AA]`",
|
||||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(true)),
|
||||||
val: true,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Checks if binary ends with `0x[FF AA AA]`",
|
description: "Checks if binary ends with `0x[FF AA AA]`",
|
||||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(true)),
|
||||||
val: true,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Checks if binary ends with `0x[11]`",
|
description: "Checks if binary ends with `0x[11]`",
|
||||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(false)),
|
||||||
val: false,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -97,17 +89,16 @@ fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Value::Binary {
|
Value::Binary {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => Value::Bool {
|
} => Value::boolean(val.ends_with(&args.pattern), *val_span),
|
||||||
val: val.ends_with(&args.pattern),
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
span: *val_span,
|
Value::Error { .. } => val.clone(),
|
||||||
},
|
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::{Call, CellPath};
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -29,6 +29,10 @@ impl Command for BytesIndexOf {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes index-of")
|
Signature::build("bytes index-of")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::Int),
|
||||||
|
(Type::Binary, Type::List(Box::new(Type::Int))),
|
||||||
|
])
|
||||||
.required(
|
.required(
|
||||||
"pattern",
|
"pattern",
|
||||||
SyntaxShape::Binary,
|
SyntaxShape::Binary,
|
||||||
@ -37,7 +41,7 @@ impl Command for BytesIndexOf {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally returns index of pattern in string by column paths",
|
"for a data structure input, find the indexes at the given cell paths",
|
||||||
)
|
)
|
||||||
.switch("all", "returns all matched index", Some('a'))
|
.switch("all", "returns all matched index", Some('a'))
|
||||||
.switch("end", "search from the end of the binary", Some('e'))
|
.switch("end", "search from the end of the binary", Some('e'))
|
||||||
@ -128,13 +132,15 @@ fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => index_of_impl(val, args, *val_span),
|
} => index_of_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BytesLen;
|
pub struct BytesLen;
|
||||||
@ -16,10 +16,12 @@ impl Command for BytesLen {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes length")
|
Signature::build("bytes length")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Int)])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally find length of binary by column paths",
|
"for a data structure input, find the length of data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -68,17 +70,16 @@ fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
Value::Binary {
|
Value::Binary {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => Value::Int {
|
} => Value::int(val.len() as i64, *val_span),
|
||||||
val: val.len() as i64,
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
span: *val_span,
|
Value::Error { .. } => val.clone(),
|
||||||
},
|
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -29,11 +30,12 @@ impl Command for BytesRemove {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes remove")
|
Signature::build("bytes remove")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally remove bytes by column paths",
|
"for a data structure input, remove bytes from data at the given cell paths",
|
||||||
)
|
)
|
||||||
.switch("end", "remove from end of binary", Some('e'))
|
.switch("end", "remove from end of binary", Some('e'))
|
||||||
.switch("all", "remove occurrences of finding binary", Some('a'))
|
.switch("all", "remove occurrences of finding binary", Some('a'))
|
||||||
@ -59,7 +61,7 @@ impl Command for BytesRemove {
|
|||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
if pattern_to_remove.item.is_empty() {
|
if pattern_to_remove.item.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"the pattern to remove cannot be empty".to_string(),
|
"the pattern to remove cannot be empty".to_string(),
|
||||||
pattern_to_remove.span,
|
pattern_to_remove.span,
|
||||||
));
|
));
|
||||||
@ -137,13 +139,15 @@ fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => remove_impl(val, args, *val_span),
|
} => remove_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -157,7 +161,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
|||||||
|
|
||||||
// Note:
|
// Note:
|
||||||
// remove_all from start and end will generate the same result.
|
// remove_all from start and end will generate the same result.
|
||||||
// so we'll put `remove_all` relative logic into else clouse.
|
// so we'll put `remove_all` relative logic into else clause.
|
||||||
if arg.end && !remove_all {
|
if arg.end && !remove_all {
|
||||||
let (mut left, mut right) = (
|
let (mut left, mut right) = (
|
||||||
input.len() as isize - arg.pattern.len() as isize,
|
input.len() as isize - arg.pattern.len() as isize,
|
||||||
@ -168,7 +172,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
|||||||
left -= 1;
|
left -= 1;
|
||||||
right -= 1;
|
right -= 1;
|
||||||
}
|
}
|
||||||
// append the remaining thing to result, this can be happeneed when
|
// append the remaining thing to result, this can be happening when
|
||||||
// we have something to remove and remove_all is False.
|
// we have something to remove and remove_all is False.
|
||||||
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||||
result.append(&mut remain);
|
result.append(&mut remain);
|
||||||
@ -189,7 +193,7 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
|||||||
right += 1;
|
right += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// append the remaing thing to result, this can happened when
|
// append the remaining thing to result, this can happened when
|
||||||
// we have something to remove and remove_all is False.
|
// we have something to remove and remove_all is False.
|
||||||
let mut remain = input[left..].to_vec();
|
let mut remain = input[left..].to_vec();
|
||||||
result.append(&mut remain);
|
result.append(&mut remain);
|
||||||
|
@ -3,7 +3,8 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -29,12 +30,13 @@ impl Command for BytesReplace {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes replace")
|
Signature::build("bytes replace")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
.required("find", SyntaxShape::Binary, "the pattern to find")
|
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||||
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally find and replace text by column paths",
|
"for a data structure input, replace bytes in data at the given cell paths",
|
||||||
)
|
)
|
||||||
.switch("all", "replace all occurrences of find binary", Some('a'))
|
.switch("all", "replace all occurrences of find binary", Some('a'))
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
@ -59,7 +61,7 @@ impl Command for BytesReplace {
|
|||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
if find.item.is_empty() {
|
if find.item.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"the pattern to find cannot be empty".to_string(),
|
"the pattern to find cannot be empty".to_string(),
|
||||||
find.span,
|
find.span,
|
||||||
));
|
));
|
||||||
@ -128,13 +130,15 @@ fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => replace_impl(val, args, *val_span),
|
} => replace_impl(val, args, *val_span),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
||||||
@ -17,16 +17,17 @@ impl Command for BytesReverse {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes reverse")
|
Signature::build("bytes reverse")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Binary)])
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally matches prefix of text by column paths",
|
"for a data structure input, reverse data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Reverse every bytes in the pipeline"
|
"Reverse the bytes in the pipeline"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -80,13 +81,15 @@ fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
span: *val_span,
|
span: *val_span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => val.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
@ -28,11 +28,12 @@ impl Command for BytesStartsWith {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("bytes starts-with")
|
Signature::build("bytes starts-with")
|
||||||
|
.input_output_types(vec![(Type::Binary, Type::Bool)])
|
||||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally matches prefix of text by column paths",
|
"for a data structure input, check if bytes at the given cell paths start with the pattern",
|
||||||
)
|
)
|
||||||
.category(Category::Bytes)
|
.category(Category::Bytes)
|
||||||
}
|
}
|
||||||
@ -73,26 +74,17 @@ impl Command for BytesStartsWith {
|
|||||||
Example {
|
Example {
|
||||||
description: "Checks if binary starts with `0x[1F FF AA]`",
|
description: "Checks if binary starts with `0x[1F FF AA]`",
|
||||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(true)),
|
||||||
val: true,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Checks if binary starts with `0x[1F]`",
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(true)),
|
||||||
val: true,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Checks if binary starts with `0x[1F]`",
|
description: "Checks if binary starts with `0x[1F]`",
|
||||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||||
result: Some(Value::Bool {
|
result: Some(Value::test_bool(false)),
|
||||||
val: false,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -103,17 +95,16 @@ fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Value::Binary {
|
Value::Binary {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => Value::Bool {
|
} => Value::boolean(val.starts_with(&args.pattern), *val_span),
|
||||||
val: val.starts_with(&args.pattern),
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
span: *val_span,
|
Value::Error { .. } => val.clone(),
|
||||||
},
|
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!(
|
"binary".into(),
|
||||||
"Input's type is {}. This command only works with bytes.",
|
other.get_type().to_string(),
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher};
|
|||||||
/// ```text
|
/// ```text
|
||||||
/// assert_eq!(HashableValue::Bool {val: true, span: Span{start: 0, end: 1}}, HashableValue::Bool {val: true, span: Span{start: 90, end: 1000}})
|
/// assert_eq!(HashableValue::Bool {val: true, span: Span{start: 0, end: 1}}, HashableValue::Bool {val: true, span: Span{start: 90, end: 1000}})
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Eq, Debug)]
|
#[derive(Eq, Debug, Ord, PartialOrd)]
|
||||||
pub enum HashableValue {
|
pub enum HashableValue {
|
||||||
Bool {
|
Bool {
|
||||||
val: bool,
|
val: bool,
|
||||||
@ -53,7 +53,7 @@ impl Default for HashableValue {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: false,
|
val: false,
|
||||||
span: Span { start: 0, end: 0 },
|
span: Span::unknown(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,13 +78,14 @@ impl HashableValue {
|
|||||||
Value::String { val, span } => Ok(HashableValue::String { val, span }),
|
Value::String { val, span } => Ok(HashableValue::String { val, span }),
|
||||||
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
|
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
|
||||||
|
|
||||||
_ => {
|
// Explicitly propagate errors instead of dropping them.
|
||||||
let input_span = value.span().unwrap_or(span);
|
Value::Error { error } => Err(error),
|
||||||
Err(ShellError::UnsupportedInput(
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
format!("input value {value:?} is not hashable"),
|
"input value is not hashable".into(),
|
||||||
input_span,
|
format!("input type: {:?}", value.get_type()),
|
||||||
))
|
span,
|
||||||
}
|
value.expect_span(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +215,7 @@ mod test {
|
|||||||
];
|
];
|
||||||
for (val, expect_hashable_val) in values.into_iter() {
|
for (val, expect_hashable_val) in values.into_iter() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::from_value(val, Span { start: 0, end: 0 }).unwrap(),
|
HashableValue::from_value(val, Span::unknown()).unwrap(),
|
||||||
expect_hashable_val
|
expect_hashable_val
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -228,7 +229,7 @@ mod test {
|
|||||||
vals: vec![Value::Bool { val: true, span }],
|
vals: vec![Value::Bool { val: true, span }],
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Block {
|
Value::Closure {
|
||||||
val: 0,
|
val: 0,
|
||||||
captures: HashMap::new(),
|
captures: HashMap::new(),
|
||||||
span,
|
span,
|
||||||
@ -245,7 +246,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
for v in values {
|
for v in values {
|
||||||
assert!(HashableValue::from_value(v, Span { start: 0, end: 0 }).is_err())
|
assert!(HashableValue::from_value(v, Span::unknown()).is_err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +267,7 @@ mod test {
|
|||||||
for val in values.into_iter() {
|
for val in values.into_iter() {
|
||||||
let expected_val = val.clone();
|
let expected_val = val.clone();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::from_value(val, Span { start: 0, end: 0 })
|
HashableValue::from_value(val, Span::unknown())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_value(),
|
.into_value(),
|
||||||
expected_val
|
expected_val
|
||||||
@ -279,14 +280,11 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: Span { start: 0, end: 1 }
|
span: Span::new(0, 1)
|
||||||
},
|
},
|
||||||
HashableValue::Bool {
|
HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: Span {
|
span: Span::new(90, 1000)
|
||||||
start: 90,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -299,7 +297,7 @@ mod test {
|
|||||||
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
assert!(set.contains(&HashableValue::Bool { val: true, span }));
|
||||||
|
|
||||||
// hashable value doesn't care about span.
|
// hashable value doesn't care about span.
|
||||||
let diff_span = Span { start: 1, end: 2 };
|
let diff_span = Span::new(1, 2);
|
||||||
set.insert(HashableValue::Bool {
|
set.insert(HashableValue::Bool {
|
||||||
val: true,
|
val: true,
|
||||||
span: diff_span,
|
span: diff_span,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use super::hashable_value::HashableValue;
|
use super::hashable_value::HashableValue;
|
||||||
|
use itertools::Itertools;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
|
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
@ -24,6 +25,7 @@ impl Command for Histogram {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("histogram")
|
Signature::build("histogram")
|
||||||
|
.input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Table(vec![])),])
|
||||||
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
|
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
|
||||||
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
|
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
|
||||||
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
|
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
|
||||||
@ -36,24 +38,49 @@ impl Command for Histogram {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for the types of files",
|
description: "Compute a histogram of file types",
|
||||||
example: "ls | histogram type",
|
example: "ls | histogram type",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Get a histogram for the types of files, with frequency column named freq",
|
"Compute a histogram for the types of files, with frequency column named freq",
|
||||||
example: "ls | histogram type freq",
|
example: "ls | histogram type freq",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for a list of numbers",
|
description: "Compute a histogram for a list of numbers",
|
||||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
|
example: "[1 2 1] | histogram",
|
||||||
result: None,
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_float(0.6666666666666666),
|
||||||
|
Value::test_string("66.67%"),
|
||||||
|
Value::test_string("******************************************************************"),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_float(0.3333333333333333),
|
||||||
|
Value::test_string("33.33%"),
|
||||||
|
Value::test_string("*********************************"),
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Get a histogram for a list of numbers, and percentage is based on the maximum value",
|
description: "Compute a histogram for a list of numbers, and percentage is based on the maximum value",
|
||||||
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
|
example: "[1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
|
||||||
result: None,
|
result: None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -71,12 +98,11 @@ impl Command for Histogram {
|
|||||||
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
|
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
|
||||||
let frequency_column_name = match frequency_name_arg {
|
let frequency_column_name = match frequency_name_arg {
|
||||||
Some(inner) => {
|
Some(inner) => {
|
||||||
let span = inner.span;
|
|
||||||
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
|
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"frequency-column-name can't be 'value', 'count' or 'percentage'"
|
"frequency-column-name can't be 'value', 'count' or 'percentage'"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
span,
|
inner.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
inner.item
|
inner.item
|
||||||
@ -92,7 +118,7 @@ impl Command for Histogram {
|
|||||||
"normalize" => PercentageCalcMethod::Normalize,
|
"normalize" => PercentageCalcMethod::Normalize,
|
||||||
"relative" => PercentageCalcMethod::Relative,
|
"relative" => PercentageCalcMethod::Relative,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"calc method can only be 'normalize' or 'relative'".to_string(),
|
"calc method can only be 'normalize' or 'relative'".to_string(),
|
||||||
inner.span,
|
inner.span,
|
||||||
))
|
))
|
||||||
@ -103,16 +129,15 @@ impl Command for Histogram {
|
|||||||
let span = call.head;
|
let span = call.head;
|
||||||
let data_as_value = input.into_value(span);
|
let data_as_value = input.into_value(span);
|
||||||
// `input` is not a list, here we can return an error.
|
// `input` is not a list, here we can return an error.
|
||||||
match data_as_value.as_list() {
|
run_histogram(
|
||||||
Ok(list_value) => run_histogram(
|
data_as_value.as_list()?.to_vec(),
|
||||||
list_value.to_vec(),
|
column_name,
|
||||||
column_name,
|
frequency_column_name,
|
||||||
frequency_column_name,
|
calc_method,
|
||||||
calc_method,
|
span,
|
||||||
span,
|
// Note that as_list() filters out Value::Error here.
|
||||||
),
|
data_as_value.expect_span(),
|
||||||
Err(e) => Err(e),
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +147,7 @@ fn run_histogram(
|
|||||||
freq_column: String,
|
freq_column: String,
|
||||||
calc_method: PercentageCalcMethod,
|
calc_method: PercentageCalcMethod,
|
||||||
head_span: Span,
|
head_span: Span,
|
||||||
|
list_span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let mut inputs = vec![];
|
let mut inputs = vec![];
|
||||||
// convert from inputs to hashable values.
|
// convert from inputs to hashable values.
|
||||||
@ -130,14 +156,24 @@ fn run_histogram(
|
|||||||
// some invalid input scenario needs to handle:
|
// some invalid input scenario needs to handle:
|
||||||
// Expect input is a list of hashable value, if one value is not hashable, throw out error.
|
// Expect input is a list of hashable value, if one value is not hashable, throw out error.
|
||||||
for v in values {
|
for v in values {
|
||||||
let current_span = v.span().unwrap_or(head_span);
|
match v {
|
||||||
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
|
// Propagate existing errors.
|
||||||
ShellError::UnsupportedInput(
|
Value::Error { error } => return Err(error),
|
||||||
"--column-name is not provided, can only support a list of simple value."
|
_ => {
|
||||||
.to_string(),
|
let t = v.get_type();
|
||||||
current_span,
|
let span = v.expect_span();
|
||||||
)
|
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
|
||||||
})?);
|
ShellError::UnsupportedInput(
|
||||||
|
"Since --column-name was not provided, only lists of hashable values are supported.".to_string(),
|
||||||
|
format!(
|
||||||
|
"input type: {t:?}"
|
||||||
|
),
|
||||||
|
head_span,
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ref col) => {
|
Some(ref col) => {
|
||||||
@ -159,14 +195,17 @@ fn run_histogram(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate existing errors.
|
||||||
|
Value::Error { error } => return Err(error),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::CantFindColumn(
|
||||||
format!("expect input is table, and inputs doesn't contain any value which has {col_name} column"),
|
col_name.clone(),
|
||||||
head_span,
|
head_span,
|
||||||
|
list_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +252,7 @@ fn histogram_impl(
|
|||||||
freq_column.to_string(),
|
freq_column.to_string(),
|
||||||
];
|
];
|
||||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter() {
|
for (val, count) in counter.into_iter().sorted() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||||
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||||
@ -222,25 +261,33 @@ fn histogram_impl(
|
|||||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||||
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
|
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
|
||||||
|
|
||||||
result.push(Value::Record {
|
result.push((
|
||||||
cols: result_cols.clone(),
|
count, // attach count first for easily sorting.
|
||||||
vals: vec![
|
Value::Record {
|
||||||
val.into_value(),
|
cols: result_cols.clone(),
|
||||||
Value::Int { val: count, span },
|
vals: vec![
|
||||||
Value::Float {
|
val.into_value(),
|
||||||
val: quantile,
|
Value::Int { val: count, span },
|
||||||
span,
|
Value::Float {
|
||||||
},
|
val: quantile,
|
||||||
Value::String {
|
span,
|
||||||
val: percentage,
|
},
|
||||||
span,
|
Value::String {
|
||||||
},
|
val: percentage,
|
||||||
Value::String { val: freq, span },
|
span,
|
||||||
],
|
},
|
||||||
span,
|
Value::String { val: freq, span },
|
||||||
});
|
],
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Value::List { vals: result, span }.into_pipeline_data()
|
result.sort_by(|a, b| b.0.cmp(&a.0));
|
||||||
|
Value::List {
|
||||||
|
vals: result.into_iter().map(|x| x.1).collect(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -19,7 +19,9 @@ impl Command for Fmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("fmt").category(Category::Conversions)
|
Signature::build("fmt")
|
||||||
|
.input_output_types(vec![(Type::Number, Type::Record(vec![]))])
|
||||||
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -42,38 +44,14 @@ impl Command for Fmt {
|
|||||||
"upperhex".into(),
|
"upperhex".into(),
|
||||||
],
|
],
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::String {
|
Value::test_string("0b101010"),
|
||||||
val: "0b101010".to_string(),
|
Value::test_string("42"),
|
||||||
span: Span::test_data(),
|
Value::test_string("42"),
|
||||||
},
|
Value::test_string("4.2e1"),
|
||||||
Value::String {
|
Value::test_string("0x2a"),
|
||||||
val: "42".to_string(),
|
Value::test_string("0o52"),
|
||||||
span: Span::test_data(),
|
Value::test_string("4.2E1"),
|
||||||
},
|
Value::test_string("0x2A"),
|
||||||
Value::String {
|
|
||||||
val: "42".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "4.2e1".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0x2a".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0o52".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "4.2E1".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
Value::String {
|
|
||||||
val: "0x2A".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
}),
|
}),
|
||||||
@ -106,10 +84,15 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => fmt_it(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
Value::Filesize { val, .. } => fmt_it(*val, span),
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
format!("unsupported input type: {:?}", input.get_type()),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer or filesize".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -120,31 +103,31 @@ fn fmt_it(num: i64, span: Span) -> Value {
|
|||||||
let mut vals = vec![];
|
let mut vals = vec![];
|
||||||
|
|
||||||
cols.push("binary".into());
|
cols.push("binary".into());
|
||||||
vals.push(Value::string(format!("{:#b}", num), span));
|
vals.push(Value::string(format!("{num:#b}"), span));
|
||||||
|
|
||||||
cols.push("debug".into());
|
cols.push("debug".into());
|
||||||
vals.push(Value::string(format!("{:#?}", num), span));
|
vals.push(Value::string(format!("{num:#?}"), span));
|
||||||
|
|
||||||
cols.push("display".into());
|
cols.push("display".into());
|
||||||
vals.push(Value::string(format!("{}", num), span));
|
vals.push(Value::string(format!("{num}"), span));
|
||||||
|
|
||||||
cols.push("lowerexp".into());
|
cols.push("lowerexp".into());
|
||||||
vals.push(Value::string(format!("{:#e}", num), span));
|
vals.push(Value::string(format!("{num:#e}"), span));
|
||||||
|
|
||||||
cols.push("lowerhex".into());
|
cols.push("lowerhex".into());
|
||||||
vals.push(Value::string(format!("{:#x}", num), span));
|
vals.push(Value::string(format!("{num:#x}"), span));
|
||||||
|
|
||||||
cols.push("octal".into());
|
cols.push("octal".into());
|
||||||
vals.push(Value::string(format!("{:#o}", num), span));
|
vals.push(Value::string(format!("{num:#o}"), span));
|
||||||
|
|
||||||
// cols.push("pointer".into());
|
// cols.push("pointer".into());
|
||||||
// vals.push(Value::string(format!("{:#p}", &num), span));
|
// vals.push(Value::string(format!("{:#p}", &num), span));
|
||||||
|
|
||||||
cols.push("upperexp".into());
|
cols.push("upperexp".into());
|
||||||
vals.push(Value::string(format!("{:#E}", num), span));
|
vals.push(Value::string(format!("{num:#E}"), span));
|
||||||
|
|
||||||
cols.push("upperhex".into());
|
cols.push("upperhex".into());
|
||||||
vals.push(Value::string(format!("{:#X}", num), span));
|
vals.push(Value::string(format!("{num:#X}"), span));
|
||||||
|
|
||||||
Value::Record { cols, vals, span }
|
Value::Record { cols, vals, span }
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
|||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,10 +17,20 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into binary")
|
Signature::build("into binary")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::Binary),
|
||||||
|
(Type::Int, Type::Binary),
|
||||||
|
(Type::Number, Type::Binary),
|
||||||
|
(Type::String, Type::Binary),
|
||||||
|
(Type::Bool, Type::Binary),
|
||||||
|
(Type::Filesize, Type::Binary),
|
||||||
|
(Type::Date, Type::Binary),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to binary (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -167,13 +177,24 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
val: int_to_endian(i64::from(*val)),
|
val: int_to_endian(i64::from(*val)),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Duration { val, .. } => Value::Binary {
|
||||||
|
val: int_to_endian(*val),
|
||||||
|
span,
|
||||||
|
},
|
||||||
Value::Date { val, .. } => Value::Binary {
|
Value::Date { val, .. } => Value::Binary {
|
||||||
val: val.format("%c").to_string().as_bytes().to_vec(),
|
val: val.format("%c").to_string().as_bytes().to_vec(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
_ => Value::Error {
|
Value::Error { .. } => input.clone(),
|
||||||
error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer, float, filesize, string, date, duration, binary or bool".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,10 +16,17 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into bool")
|
Signature::build("into bool")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::Bool),
|
||||||
|
(Type::Number, Type::Bool),
|
||||||
|
(Type::String, Type::Bool),
|
||||||
|
(Type::Bool, Type::Bool),
|
||||||
|
(Type::List(Box::new(Type::Any)), Type::Table(vec![])),
|
||||||
|
])
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to boolean (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -47,7 +54,7 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert value to boolean in table",
|
description: "Convert value to boolean in table",
|
||||||
example: "echo [[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
|
example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::Record {
|
Value::Record {
|
||||||
@ -89,6 +96,11 @@ impl Command for SubCommand {
|
|||||||
example: "1 | into bool",
|
example: "1 | into bool",
|
||||||
result: Some(Value::boolean(true, span)),
|
result: Some(Value::boolean(true, span)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert decimal to boolean",
|
||||||
|
example: "0.3 | into bool",
|
||||||
|
result: Some(Value::boolean(true, span)),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal string to boolean",
|
description: "convert decimal string to boolean",
|
||||||
example: "'0.0' | into bool",
|
example: "'0.0' | into bool",
|
||||||
@ -151,10 +163,15 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
Ok(val) => Value::Bool { val, span },
|
Ok(val) => Value::Bool { val, span },
|
||||||
Err(error) => Value::Error { error },
|
Err(error) => Value::Error { error },
|
||||||
},
|
},
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
"'into bool' does not support this input".into(),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"bool, integer, float or string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
Category, IntoPipelineData, PipelineData, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,13 +14,19 @@ impl Command for Into {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into").category(Category::Conversions)
|
Signature::build("into")
|
||||||
|
.category(Category::Conversions)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Commands to convert data from one type to another."
|
"Commands to convert data from one type to another."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
@ -29,21 +35,15 @@ impl Command for Into {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(&Into.signature(), &[], engine_state, stack),
|
val: get_full_help(
|
||||||
|
&Into.signature(),
|
||||||
|
&[],
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
self.is_parser_keyword(),
|
||||||
|
),
|
||||||
span: call.head,
|
span: call.head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Into {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::input_handler::{operate, CmdArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use crate::{generate_strftime_list, parse_date_from_string};
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone, Utc};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -64,7 +64,11 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into datetime")
|
Signature::build("into datetime")
|
||||||
.named(
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::Date),
|
||||||
|
(Type::String, Type::Date),
|
||||||
|
])
|
||||||
|
.named(
|
||||||
"timezone",
|
"timezone",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
|
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
|
||||||
@ -90,7 +94,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"optionally convert text into datetime by column paths",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -143,38 +147,41 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
let example_result_1 = |secs: i64, nsecs: u32| match Utc.timestamp_opt(secs, nsecs) {
|
||||||
|
LocalResult::Single(dt) => Some(Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
_ => panic!("datetime: help example is invalid"),
|
||||||
|
};
|
||||||
|
let example_result_2 = |millis: i64| match Utc.timestamp_millis_opt(millis) {
|
||||||
|
LocalResult::Single(dt) => Some(Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
_ => panic!("datetime: help example is invalid"),
|
||||||
|
};
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434100,0)
|
||||||
val: Utc.timestamp(1614434100, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'2021-02-27T13:55:40+00:00' | into datetime",
|
example: "'2021-02-27T13:55:40+00:00' | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime using a custom format",
|
description: "Convert to datetime using a custom format",
|
||||||
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
|
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
|
||||||
example: "1614434140 | into datetime",
|
example: "1614434140 | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_1(1614434140, 0)
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
@ -184,12 +191,9 @@ impl Command for SubCommand {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Convert timestamps like the sqlite history t",
|
"Convert a millisecond-precise timestamp",
|
||||||
example: "1656165681720 | into datetime",
|
example: "1656165681720 | into datetime",
|
||||||
result: Some(Value::Date {
|
result: example_result_2(1656165681720)
|
||||||
val: Utc.timestamp_millis(1656165681720).into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -205,11 +209,16 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
Value::Int { val, .. } => Ok(*val),
|
Value::Int { val, .. } => Ok(*val),
|
||||||
Value::String { val, .. } => val.parse::<i64>(),
|
Value::String { val, .. } => val.parse::<i64>(),
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => return input.clone(),
|
||||||
other => {
|
other => {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!("Expected string or int, got {} instead", other.get_type()),
|
"string and integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
head,
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -222,54 +231,68 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
if ts.abs() > TIMESTAMP_BOUND {
|
if ts.abs() > TIMESTAMP_BOUND {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"Given timestamp is out of range, it should between -8e+12 and 8e+12"
|
"timestamp is out of range; it should between -8e+12 and 8e+12".to_string(),
|
||||||
.to_string(),
|
format!("timestamp is {ts:?}"),
|
||||||
head,
|
head,
|
||||||
|
// Again, can safely unwrap this from here on
|
||||||
|
input.expect_span(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! match_datetime {
|
||||||
|
($expr:expr) => {
|
||||||
|
match $expr {
|
||||||
|
LocalResult::Single(dt) => Value::Date {
|
||||||
|
val: dt.into(),
|
||||||
|
span: head,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"The given local datetime representation is invalid.".into(),
|
||||||
|
format!("timestamp is {:?}", ts),
|
||||||
|
head,
|
||||||
|
head,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return match timezone {
|
return match timezone {
|
||||||
// default to UTC
|
// default to UTC
|
||||||
None => {
|
None => {
|
||||||
// be able to convert chrono::Utc::now()
|
// be able to convert chrono::Utc::now()
|
||||||
let dt = match ts.to_string().len() {
|
match ts.to_string().len() {
|
||||||
x if x > 13 => Utc.timestamp_nanos(ts).into(),
|
x if x > 13 => Value::Date {
|
||||||
x if x > 10 => Utc.timestamp_millis(ts).into(),
|
val: Utc.timestamp_nanos(ts).into(),
|
||||||
_ => Utc.timestamp(ts, 0).into(),
|
span: head,
|
||||||
};
|
},
|
||||||
|
x if x > 10 => match_datetime!(Utc.timestamp_millis_opt(ts)),
|
||||||
Value::Date {
|
_ => match_datetime!(Utc.timestamp_opt(ts, 0)),
|
||||||
val: dt,
|
|
||||||
span: head,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Spanned { item, span }) => match item {
|
Some(Spanned { item, span }) => match item {
|
||||||
Zone::Utc => Value::Date {
|
Zone::Utc => match_datetime!(Utc.timestamp_opt(ts, 0)),
|
||||||
val: Utc.timestamp(ts, 0).into(),
|
Zone::Local => match_datetime!(Local.timestamp_opt(ts, 0)),
|
||||||
span: head,
|
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||||
|
Some(eastoffset) => match_datetime!(eastoffset.timestamp_opt(ts, 0)),
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(*span),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Zone::Local => Value::Date {
|
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||||
val: Local.timestamp(ts, 0).into(),
|
Some(westoffset) => match_datetime!(westoffset.timestamp_opt(ts, 0)),
|
||||||
span: head,
|
None => Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(*span),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Zone::East(i) => {
|
|
||||||
let eastoffset = FixedOffset::east((*i as i32) * HOUR);
|
|
||||||
Value::Date {
|
|
||||||
val: eastoffset.timestamp(ts, 0),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Zone::West(i) => {
|
|
||||||
let westoffset = FixedOffset::west((*i as i32) * HOUR);
|
|
||||||
Value::Date {
|
|
||||||
val: westoffset.timestamp(ts, 0),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Zone::Error => Value::Error {
|
Zone::Error => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
// This is an argument error, not an input error
|
||||||
"Cannot convert given timezone or offset to timestamp".to_string(),
|
error: ShellError::TypeMismatch(
|
||||||
|
"Invalid timezone or offset".to_string(),
|
||||||
*span,
|
*span,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -306,10 +329,15 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
format!("Expected string, got {} instead", other.get_type()),
|
"string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
head,
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -421,7 +449,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Local.timestamp(1614434140, 0).into(),
|
val: Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -439,7 +467,7 @@ mod tests {
|
|||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
val: Utc.timestamp_opt(1614434140, 0).unwrap().into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,11 +15,16 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into decimal").rest(
|
Signature::build("into decimal")
|
||||||
"rest",
|
.input_output_types(vec![
|
||||||
SyntaxShape::CellPath,
|
(Type::String, Type::Number),
|
||||||
"optionally convert text into decimal by column paths",
|
(Type::Bool, Type::Number),
|
||||||
)
|
])
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"for a data structure input, convert data at the given cell paths",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -81,7 +86,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
|||||||
let other = s.trim();
|
let other = s.trim();
|
||||||
|
|
||||||
match other.parse::<f64>() {
|
match other.parse::<f64>() {
|
||||||
Ok(x) => Value::Float { val: x, span: head },
|
Ok(x) => Value::float(x, head),
|
||||||
Err(reason) => Value::Error {
|
Err(reason) => Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
"float".to_string(),
|
"float".to_string(),
|
||||||
@ -92,10 +97,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Int { val: v, span } => Value::Float {
|
Value::Int { val: v, span } => Value::float(*v as f64, *span),
|
||||||
val: *v as f64,
|
|
||||||
span: *span,
|
|
||||||
},
|
|
||||||
Value::Bool { val: b, span } => Value::Float {
|
Value::Bool { val: b, span } => Value::Float {
|
||||||
val: match b {
|
val: match b {
|
||||||
true => 1.0,
|
true => 1.0,
|
||||||
@ -103,18 +105,17 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
|||||||
},
|
},
|
||||||
span: *span,
|
span: *span,
|
||||||
},
|
},
|
||||||
other => {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
let span = other.span();
|
Value::Error { .. } => input.clone(),
|
||||||
match span {
|
other => Value::Error {
|
||||||
Ok(s) => {
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
let got = format!("Expected a string, got {} instead", other.get_type());
|
"string, integer or bool".into(),
|
||||||
Value::Error {
|
other.get_type().to_string(),
|
||||||
error: ShellError::UnsupportedInput(got, s),
|
head,
|
||||||
}
|
// This line requires the Value::Error match above.
|
||||||
}
|
other.expect_span(),
|
||||||
Err(e) => Value::Error { error: e },
|
),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use nu_parser::parse_duration_bytes;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath, Expr},
|
ast::{Call, CellPath, Expr},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Unit,
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,6 +17,13 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into duration")
|
Signature::build("into duration")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::String, Type::Duration),
|
||||||
|
(Type::Duration, Type::Duration),
|
||||||
|
// TODO: --convert option should be implemented as `format duration`
|
||||||
|
(Type::String, Type::String),
|
||||||
|
(Type::Duration, Type::String),
|
||||||
|
])
|
||||||
.named(
|
.named(
|
||||||
"convert",
|
"convert",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -26,7 +33,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to duration (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -36,7 +43,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
"into duration does not take leap years into account and every month is calculated with 30 days"
|
"This command does not take leap years into account, and every month is assumed to have 30 days."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -58,7 +65,8 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to duration in table",
|
description: "Convert string to duration in table",
|
||||||
example: "echo [[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
|
example:
|
||||||
|
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::Record {
|
Value::Record {
|
||||||
@ -121,11 +129,19 @@ impl Command for SubCommand {
|
|||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert duration to duration",
|
||||||
|
example: "420sec | into duration",
|
||||||
|
result: Some(Value::Duration {
|
||||||
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert duration to the requested duration as a string",
|
description: "Convert duration to the requested duration as a string",
|
||||||
example: "420sec | into duration --convert min",
|
example: "420sec | into duration --convert ms",
|
||||||
result: Some(Value::String {
|
result: Some(Value::String {
|
||||||
val: "7 min".to_string(),
|
val: "420000 ms".to_string(),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -452,9 +468,11 @@ fn action(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Value::Error {
|
Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::CantConvert(
|
||||||
"'into duration' does not support this string input".into(),
|
"string".into(),
|
||||||
|
"duration".into(),
|
||||||
span,
|
span,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -465,10 +483,15 @@ fn action(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
"'into duration' does not support this input".into(),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"string or duration".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -487,7 +510,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_ns_to_duration() {
|
fn turns_ns_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("3ns");
|
let word = Value::test_string("3ns");
|
||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
@ -498,7 +521,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_us_to_duration() {
|
fn turns_us_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("4us");
|
let word = Value::test_string("4us");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 4 * 1000,
|
val: 4 * 1000,
|
||||||
@ -512,7 +535,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_ms_to_duration() {
|
fn turns_ms_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("5ms");
|
let word = Value::test_string("5ms");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 5 * 1000 * 1000,
|
val: 5 * 1000 * 1000,
|
||||||
@ -526,7 +549,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_sec_to_duration() {
|
fn turns_sec_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("1sec");
|
let word = Value::test_string("1sec");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 1000 * 1000 * 1000,
|
val: 1000 * 1000 * 1000,
|
||||||
@ -540,7 +563,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_min_to_duration() {
|
fn turns_min_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("7min");
|
let word = Value::test_string("7min");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||||
@ -554,7 +577,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_hr_to_duration() {
|
fn turns_hr_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 3);
|
||||||
let word = Value::test_string("42hr");
|
let word = Value::test_string("42hr");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
@ -568,7 +591,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_day_to_duration() {
|
fn turns_day_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 5);
|
||||||
let word = Value::test_string("123day");
|
let word = Value::test_string("123day");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
@ -582,7 +605,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_wk_to_duration() {
|
fn turns_wk_to_duration() {
|
||||||
let span = Span::test_data();
|
let span = Span::new(0, 2);
|
||||||
let word = Value::test_string("3wk");
|
let word = Value::test_string("3wk");
|
||||||
let expected = Value::Duration {
|
let expected = Value::Duration {
|
||||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,10 +16,16 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into filesize")
|
Signature::build("into filesize")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Int, Type::Filesize),
|
||||||
|
(Type::Number, Type::Filesize),
|
||||||
|
(Type::String, Type::Filesize),
|
||||||
|
(Type::Filesize, Type::Filesize),
|
||||||
|
])
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to filesize (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -110,20 +116,18 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
val: 0,
|
val: 0,
|
||||||
span: value_span,
|
span: value_span,
|
||||||
},
|
},
|
||||||
_ => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
"'into filesize' for unsupported type".into(),
|
"string and integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
span,
|
||||||
value_span,
|
value_span,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Value::Error {
|
// Propagate existing errors
|
||||||
error: ShellError::UnsupportedInput(
|
input.clone()
|
||||||
"'into filesize' for unsupported type".into(),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@ -28,12 +28,22 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into int")
|
Signature::build("into int")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::String, Type::Int),
|
||||||
|
(Type::Number, Type::Int),
|
||||||
|
(Type::Bool, Type::Int),
|
||||||
|
// Unix timestamp in seconds
|
||||||
|
(Type::Date, Type::Int),
|
||||||
|
// TODO: Users should do this by dividing a Filesize by a Filesize explicitly
|
||||||
|
(Type::Filesize, Type::Int),
|
||||||
|
])
|
||||||
|
.vectorizes_over_list(true)
|
||||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||||
.switch("little-endian", "use little-endian byte decoding", None)
|
.switch("little-endian", "use little-endian byte decoding", None)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to int (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
@ -60,7 +70,7 @@ impl Command for SubCommand {
|
|||||||
let radix: u32 = match radix {
|
let radix: u32 = match radix {
|
||||||
Some(Value::Int { val, span }) => {
|
Some(Value::Int { val, span }) => {
|
||||||
if !(2..=36).contains(&val) {
|
if !(2..=36).contains(&val) {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"Radix must lie in the range [2, 36]".to_string(),
|
"Radix must lie in the range [2, 36]".to_string(),
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
@ -82,7 +92,7 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to integer in table",
|
description: "Convert string to integer in table",
|
||||||
example: "echo [[num]; ['-5'] [4] [1.5]] | into int num",
|
example: "[[num]; ['-5'] [4] [1.5]] | into int num",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
@ -103,10 +113,7 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "Convert file size to integer",
|
description: "Convert file size to integer",
|
||||||
example: "4KB | into int",
|
example: "4KB | into int",
|
||||||
result: Some(Value::Int {
|
result: Some(Value::test_int(4000)),
|
||||||
val: 4000,
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert bool to integer",
|
description: "Convert bool to integer",
|
||||||
@ -180,9 +187,11 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
_ => {
|
_ => {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::CantConvert(
|
||||||
"Could not convert float to integer".to_string(),
|
"float".to_string(),
|
||||||
|
"integer".to_string(),
|
||||||
span,
|
span,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,6 +221,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
val: val.timestamp(),
|
val: val.timestamp(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Value::Duration { val, .. } => Value::Int { val: *val, span },
|
||||||
Value::Binary { val, span } => {
|
Value::Binary { val, span } => {
|
||||||
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
|
||||||
@ -223,26 +233,25 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
val.resize(8, 0);
|
val.resize(8, 0);
|
||||||
|
|
||||||
Value::Int {
|
Value::int(LittleEndian::read_i64(&val), *span)
|
||||||
val: LittleEndian::read_i64(&val),
|
|
||||||
span: *span,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
while val.len() < 8 {
|
while val.len() < 8 {
|
||||||
val.insert(0, 0);
|
val.insert(0, 0);
|
||||||
}
|
}
|
||||||
val.resize(8, 0);
|
val.resize(8, 0);
|
||||||
|
|
||||||
Value::Int {
|
Value::int(BigEndian::read_i64(&val), *span)
|
||||||
val: BigEndian::read_i64(&val),
|
|
||||||
span: *span,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Value::Error {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
error: ShellError::UnsupportedInput(
|
Value::Error { .. } => input.clone(),
|
||||||
format!("'into int' for unsupported type '{}'", input.get_type()),
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"integer, float, filesize, date, string, binary, duration or bool".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
span,
|
span,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -259,13 +268,13 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
|||||||
// octal
|
// octal
|
||||||
{
|
{
|
||||||
match int_from_string(val, head) {
|
match int_from_string(val, head) {
|
||||||
Ok(x) => return Value::Int { val: x, span: head },
|
Ok(x) => return Value::int(x, head),
|
||||||
Err(e) => return Value::Error { error: e },
|
Err(e) => return Value::Error { error: e },
|
||||||
}
|
}
|
||||||
} else if val.starts_with("00") {
|
} else if val.starts_with("00") {
|
||||||
// It's a padded string
|
// It's a padded string
|
||||||
match i64::from_str_radix(val, radix) {
|
match i64::from_str_radix(val, radix) {
|
||||||
Ok(n) => return Value::Int { val: n, span: head },
|
Ok(n) => return Value::int(n, head),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::CantConvert(
|
error: ShellError::CantConvert(
|
||||||
@ -280,17 +289,22 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
|||||||
}
|
}
|
||||||
val.to_string()
|
val.to_string()
|
||||||
}
|
}
|
||||||
_ => {
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
Value::Error { .. } => return input.clone(),
|
||||||
|
other => {
|
||||||
return Value::Error {
|
return Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
"only strings or integers are supported".to_string(),
|
"string and integer".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
head,
|
head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match i64::from_str_radix(i.trim(), radix) {
|
match i64::from_str_radix(i.trim(), radix) {
|
||||||
Ok(n) => Value::Int { val: n, span: head },
|
Ok(n) => Value::int(n, head),
|
||||||
Err(_reason) => Value::Error {
|
Err(_reason) => Value::Error {
|
||||||
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
|
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
|
||||||
},
|
},
|
||||||
@ -353,8 +367,7 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
span,
|
span,
|
||||||
Some(format!(
|
Some(format!(
|
||||||
r#"string "{}" does not represent a valid integer"#,
|
r#"string "{trimmed}" does not represent a valid integer"#
|
||||||
trimmed
|
|
||||||
)),
|
)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ mod decimal;
|
|||||||
mod duration;
|
mod duration;
|
||||||
mod filesize;
|
mod filesize;
|
||||||
mod int;
|
mod int;
|
||||||
|
mod record;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
||||||
pub use self::bool::SubCommand as IntoBool;
|
pub use self::bool::SubCommand as IntoBool;
|
||||||
@ -16,4 +17,5 @@ pub use datetime::SubCommand as IntoDatetime;
|
|||||||
pub use decimal::SubCommand as IntoDecimal;
|
pub use decimal::SubCommand as IntoDecimal;
|
||||||
pub use duration::SubCommand as IntoDuration;
|
pub use duration::SubCommand as IntoDuration;
|
||||||
pub use int::SubCommand as IntoInt;
|
pub use int::SubCommand as IntoInt;
|
||||||
|
pub use record::SubCommand as IntoRecord;
|
||||||
pub use string::SubCommand as IntoString;
|
pub use string::SubCommand as IntoString;
|
||||||
|
295
crates/nu-command/src/conversions/into/record.rs
Normal file
295
crates/nu-command/src/conversions/into/record.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
use chrono::{DateTime, Datelike, FixedOffset, Timelike};
|
||||||
|
use nu_protocol::format_duration_as_timeperiod;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
|
};
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"into record"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("into record")
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Date, Type::Record(vec![])),
|
||||||
|
(Type::Duration, Type::Record(vec![])),
|
||||||
|
(Type::List(Box::new(Type::Any)), Type::Record(vec![])),
|
||||||
|
(Type::Range, Type::Record(vec![])),
|
||||||
|
(Type::Record(vec![]), Type::Record(vec![])),
|
||||||
|
(Type::Table(vec![]), Type::Record(vec![])),
|
||||||
|
])
|
||||||
|
.category(Category::Conversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert value to record"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["convert"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
into_record(engine_state, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
let span = Span::test_data();
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Convert from one row table to record",
|
||||||
|
example: "[[value]; [false]] | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["value".to_string()],
|
||||||
|
vals: vec![Value::boolean(false, span)],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert from list to record",
|
||||||
|
example: "[1 2 3] | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int { val: 1, span },
|
||||||
|
Value::Int { val: 2, span },
|
||||||
|
Value::Int { val: 3, span },
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert from range to record",
|
||||||
|
example: "0..2 | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int { val: 0, span },
|
||||||
|
Value::Int { val: 1, span },
|
||||||
|
Value::Int { val: 2, span },
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert duration to record",
|
||||||
|
example: "-500day | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec![
|
||||||
|
"year".into(),
|
||||||
|
"month".into(),
|
||||||
|
"week".into(),
|
||||||
|
"day".into(),
|
||||||
|
"sign".into(),
|
||||||
|
],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int { val: 1, span },
|
||||||
|
Value::Int { val: 4, span },
|
||||||
|
Value::Int { val: 2, span },
|
||||||
|
Value::Int { val: 1, span },
|
||||||
|
Value::String {
|
||||||
|
val: "-".into(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert record to record",
|
||||||
|
example: "{a: 1, b: 2} | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["a".to_string(), "b".to_string()],
|
||||||
|
vals: vec![Value::Int { val: 1, span }, Value::Int { val: 2, span }],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "convert date to record",
|
||||||
|
example: "2020-04-12T22:10:57+02:00 | into record",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec![
|
||||||
|
"year".into(),
|
||||||
|
"month".into(),
|
||||||
|
"day".into(),
|
||||||
|
"hour".into(),
|
||||||
|
"minute".into(),
|
||||||
|
"second".into(),
|
||||||
|
"timezone".into(),
|
||||||
|
],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int { val: 2020, span },
|
||||||
|
Value::Int { val: 4, span },
|
||||||
|
Value::Int { val: 12, span },
|
||||||
|
Value::Int { val: 22, span },
|
||||||
|
Value::Int { val: 10, span },
|
||||||
|
Value::Int { val: 57, span },
|
||||||
|
Value::String {
|
||||||
|
val: "+02:00".to_string(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_record(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let input = input.into_value(call.head);
|
||||||
|
let input_type = input.get_type();
|
||||||
|
let res = match input {
|
||||||
|
Value::Date { val, span } => parse_date_into_record(Ok(val), span),
|
||||||
|
Value::Duration { val, span } => parse_duration_into_record(val, span),
|
||||||
|
Value::List { mut vals, span } => match input_type {
|
||||||
|
Type::Table(..) if vals.len() == 1 => vals.pop().expect("already checked 1 item"),
|
||||||
|
_ => {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut values = vec![];
|
||||||
|
for (idx, val) in vals.into_iter().enumerate() {
|
||||||
|
cols.push(format!("{idx}"));
|
||||||
|
values.push(val);
|
||||||
|
}
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals: values,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Range { val, span } => {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for (idx, val) in val.into_range_iter(engine_state.ctrlc.clone())?.enumerate() {
|
||||||
|
cols.push(format!("{idx}"));
|
||||||
|
vals.push(val);
|
||||||
|
}
|
||||||
|
Value::Record { cols, vals, span }
|
||||||
|
}
|
||||||
|
Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
|
||||||
|
Value::Error { .. } => input,
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::OnlySupportsThisInputType(
|
||||||
|
"string".into(),
|
||||||
|
other.get_type().to_string(),
|
||||||
|
call.head,
|
||||||
|
// This line requires the Value::Error match above.
|
||||||
|
other.expect_span(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(res.into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_date_into_record(date: Result<DateTime<FixedOffset>, Value>, span: Span) -> Value {
|
||||||
|
let cols = vec![
|
||||||
|
"year".into(),
|
||||||
|
"month".into(),
|
||||||
|
"day".into(),
|
||||||
|
"hour".into(),
|
||||||
|
"minute".into(),
|
||||||
|
"second".into(),
|
||||||
|
"timezone".into(),
|
||||||
|
];
|
||||||
|
match date {
|
||||||
|
Ok(x) => {
|
||||||
|
let vals = vec![
|
||||||
|
Value::Int {
|
||||||
|
val: x.year() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: x.month() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: x.day() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: x.hour() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: x.minute() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: x.second() as i64,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::String {
|
||||||
|
val: x.offset().to_string(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
Value::Record { cols, vals, span }
|
||||||
|
}
|
||||||
|
Err(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_duration_into_record(duration: i64, span: Span) -> Value {
|
||||||
|
let (sign, periods) = format_duration_as_timeperiod(duration);
|
||||||
|
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for p in periods {
|
||||||
|
let num_with_unit = p.to_text().to_string();
|
||||||
|
let split = num_with_unit.split(' ').collect::<Vec<&str>>();
|
||||||
|
cols.push(match split[1] {
|
||||||
|
"ns" => "nanosecond".into(),
|
||||||
|
"µs" => "microsecond".into(),
|
||||||
|
"ms" => "millisecond".into(),
|
||||||
|
"sec" => "second".into(),
|
||||||
|
"min" => "minute".into(),
|
||||||
|
"hr" => "hour".into(),
|
||||||
|
"day" => "day".into(),
|
||||||
|
"wk" => "week".into(),
|
||||||
|
"month" => "month".into(),
|
||||||
|
"yr" => "year".into(),
|
||||||
|
_ => "unknown".into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: split[0].parse::<i64>().unwrap_or(0),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cols.push("sign".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: if sign == -1 { "-".into() } else { "+".into() },
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
Value::Record { cols, vals, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
|||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||||
Span, SyntaxShape, Value,
|
Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::get_system_locale;
|
use nu_utils::get_system_locale;
|
||||||
use num_format::ToFormattedString;
|
use num_format::ToFormattedString;
|
||||||
@ -32,11 +32,20 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into string")
|
Signature::build("into string")
|
||||||
// FIXME - need to support column paths
|
.input_output_types(vec![
|
||||||
|
(Type::Binary, Type::String),
|
||||||
|
(Type::Int, Type::String),
|
||||||
|
(Type::Number, Type::String),
|
||||||
|
(Type::String, Type::String),
|
||||||
|
(Type::Bool, Type::String),
|
||||||
|
(Type::Filesize, Type::String),
|
||||||
|
(Type::Date, Type::String),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"column paths to convert to string (for table input)",
|
"for a data structure input, convert data at the given cell paths",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"decimals",
|
"decimals",
|
||||||
@ -70,34 +79,22 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "convert integer to string and append three decimal places",
|
description: "convert integer to string and append three decimal places",
|
||||||
example: "5 | into string -d 3",
|
example: "5 | into string -d 3",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("5.000")),
|
||||||
val: "5.000".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string and round to nearest integer",
|
description: "convert decimal to string and round to nearest integer",
|
||||||
example: "1.7 | into string -d 0",
|
example: "1.7 | into string -d 0",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("2")),
|
||||||
val: "2".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string",
|
description: "convert decimal to string",
|
||||||
example: "1.7 | into string -d 1",
|
example: "1.7 | into string -d 1",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("1.7")),
|
||||||
val: "1.7".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string and limit to 2 decimals",
|
description: "convert decimal to string and limit to 2 decimals",
|
||||||
example: "1.734 | into string -d 2",
|
example: "1.734 | into string -d 2",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("1.73")),
|
||||||
val: "1.73".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "try to convert decimal to string and provide negative decimal points",
|
description: "try to convert decimal to string and provide negative decimal points",
|
||||||
@ -114,32 +111,24 @@ impl Command for SubCommand {
|
|||||||
Example {
|
Example {
|
||||||
description: "convert decimal to string",
|
description: "convert decimal to string",
|
||||||
example: "4.3 | into string",
|
example: "4.3 | into string",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("4.3")),
|
||||||
val: "4.3".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert string to string",
|
description: "convert string to string",
|
||||||
example: "'1234' | into string",
|
example: "'1234' | into string",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("1234")),
|
||||||
val: "1234".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert boolean to string",
|
description: "convert boolean to string",
|
||||||
example: "true | into string",
|
example: "true | into string",
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("true")),
|
||||||
val: "true".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "convert date to string",
|
|
||||||
example: "date now | into string",
|
|
||||||
result: None,
|
|
||||||
},
|
},
|
||||||
|
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||||
|
// Example {
|
||||||
|
// description: "convert date to string",
|
||||||
|
// example: "'2020-10-10 10:00:00 +02:00' | into datetime | into string",
|
||||||
|
// result: Some(Value::test_string("Sat Oct 10 10:00:00 2020")),
|
||||||
|
// },
|
||||||
Example {
|
Example {
|
||||||
description: "convert filepath to string",
|
description: "convert filepath to string",
|
||||||
example: "ls Cargo.toml | get name | into string",
|
example: "ls Cargo.toml | get name | into string",
|
||||||
@ -147,8 +136,8 @@ impl Command for SubCommand {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert filesize to string",
|
description: "convert filesize to string",
|
||||||
example: "ls Cargo.toml | get size | into string",
|
example: "1KiB | into string",
|
||||||
result: None,
|
result: Some(Value::test_string("1,024 B")),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -165,7 +154,7 @@ fn string_helper(
|
|||||||
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
||||||
if let Some(decimal_val) = decimals_value {
|
if let Some(decimal_val) = decimals_value {
|
||||||
if decimals && decimal_val.is_negative() {
|
if decimals && decimal_val.is_negative() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::TypeMismatch(
|
||||||
"Cannot accept negative integers for decimals arguments".to_string(),
|
"Cannot accept negative integers for decimals arguments".to_string(),
|
||||||
head,
|
head,
|
||||||
));
|
));
|
||||||
@ -217,7 +206,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
if decimals {
|
if decimals {
|
||||||
let decimal_value = digits.unwrap_or(2) as usize;
|
let decimal_value = digits.unwrap_or(2) as usize;
|
||||||
Value::String {
|
Value::String {
|
||||||
val: format!("{:.*}", decimal_value, val),
|
val: format!("{val:.decimal_value$}"),
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -245,12 +234,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Error { error } => Value::String {
|
Value::Error { error } => Value::String {
|
||||||
val: {
|
val: into_code(error).unwrap_or_default(),
|
||||||
match into_code(error) {
|
|
||||||
Some(code) => code,
|
|
||||||
None => "".to_string(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Nothing { .. } => Value::String {
|
Value::Nothing { .. } => Value::String {
|
||||||
@ -262,9 +246,11 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
vals: _,
|
vals: _,
|
||||||
span: _,
|
span: _,
|
||||||
} => Value::Error {
|
} => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::CantConvert(
|
||||||
"Cannot convert Record into string".to_string(),
|
"record".into(),
|
||||||
|
"string".into(),
|
||||||
span,
|
span,
|
||||||
|
Some("try using the `to nuon` command".into()),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Value::Binary { .. } => Value::Error {
|
Value::Binary { .. } => Value::Error {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
|
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Alias;
|
pub struct Alias;
|
||||||
@ -16,6 +16,7 @@ impl Command for Alias {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("alias")
|
Signature::build("alias")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("name", SyntaxShape::String, "name of the alias")
|
.required("name", SyntaxShape::String, "name of the alias")
|
||||||
.required(
|
.required(
|
||||||
"initial_value",
|
"initial_value",
|
||||||
@ -42,10 +43,10 @@ impl Command for Alias {
|
|||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -53,12 +54,12 @@ impl Command for Alias {
|
|||||||
Example {
|
Example {
|
||||||
description: "Alias ll to ls -l",
|
description: "Alias ll to ls -l",
|
||||||
example: "alias ll = ls -l",
|
example: "alias ll = ls -l",
|
||||||
result: None,
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Make an alias that makes a list of all custom commands",
|
description: "Make an alias that makes a list of all custom commands",
|
||||||
example: "alias customs = ($nu.scope.commands | where is_custom | get command)",
|
example: "alias customs = ($nu.scope.commands | where is_custom | get command)",
|
||||||
result: None,
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use nu_parser::parse;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -20,6 +21,7 @@ impl Command for Ast {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("ast")
|
Signature::build("ast")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required(
|
.required(
|
||||||
"pipeline",
|
"pipeline",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -35,14 +37,13 @@ impl Command for Ast {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
|
||||||
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
|
let (output, err) = parse(&mut working_set, None, pipeline.item.as_bytes(), false, &[]);
|
||||||
eprintln!("output: {:#?}\nerror: {:#?}", output, err);
|
eprintln!("output: {output:#?}\nerror: {err:#?}");
|
||||||
|
|
||||||
Ok(PipelineData::new(head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -50,17 +51,17 @@ impl Command for Ast {
|
|||||||
Example {
|
Example {
|
||||||
description: "Print the ast of a string",
|
description: "Print the ast of a string",
|
||||||
example: "ast 'hello'",
|
example: "ast 'hello'",
|
||||||
result: None,
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Print the ast of a pipeline",
|
description: "Print the ast of a pipeline",
|
||||||
example: "ast 'ls | where name =~ README'",
|
example: "ast 'ls | where name =~ README'",
|
||||||
result: None,
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Print the ast of a pipeline with an error",
|
description: "Print the ast of a pipeline with an error",
|
||||||
example: "ast 'for x in 1..10 { echo $x '",
|
example: "ast 'for x in 1..10 { echo $x '",
|
||||||
result: None,
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
49
crates/nu-command/src/core_commands/break_.rs
Normal file
49
crates/nu-command/src/core_commands/break_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Break;
|
||||||
|
|
||||||
|
impl Command for Break {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"break"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Break a loop"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("break")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Err(ShellError::Break(call.head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Break out of a loop",
|
||||||
|
example: r#"loop { break }"#,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use nu_protocol::engine::ReplOperation;
|
|||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::IntoPipelineData;
|
use nu_protocol::IntoPipelineData;
|
||||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Commandline;
|
pub struct Commandline;
|
||||||
@ -16,6 +16,7 @@ impl Command for Commandline {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("commandline")
|
Signature::build("commandline")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.switch(
|
.switch(
|
||||||
"append",
|
"append",
|
||||||
"appends the string to the end of the buffer",
|
"appends the string to the end of the buffer",
|
||||||
|
104
crates/nu-command/src/core_commands/const_.rs
Normal file
104
crates/nu-command/src/core_commands/const_.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Const;
|
||||||
|
|
||||||
|
impl Command for Const {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"const"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create a parse-time constant."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("const")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||||
|
"equals sign followed by constant value",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["set", "let"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let var_id = call
|
||||||
|
.positional_nth(0)
|
||||||
|
.expect("checked through parser")
|
||||||
|
.as_var()
|
||||||
|
.expect("internal error: missing variable");
|
||||||
|
|
||||||
|
if let Some(constval) = engine_state.find_constant(var_id, &[]) {
|
||||||
|
// Instead of creating a second copy of the value in the stack, we could change
|
||||||
|
// stack.get_var() to check engine_state.find_constant().
|
||||||
|
stack.add_var(var_id, constval.clone());
|
||||||
|
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::NushellFailedSpanned(
|
||||||
|
"Missing Constant".to_string(),
|
||||||
|
"constant not added by the parser".to_string(),
|
||||||
|
call.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Create a new parse-time constant.",
|
||||||
|
example: "const x = 10",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Create a composite constant value",
|
||||||
|
example: "const x = { a: 10, b: 20 }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Const {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_type() {
|
||||||
|
assert!(matches!(Const.command_type(), CommandType::Keyword));
|
||||||
|
}
|
||||||
|
}
|
49
crates/nu-command/src/core_commands/continue_.rs
Normal file
49
crates/nu-command/src/core_commands/continue_.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Continue;
|
||||||
|
|
||||||
|
impl Command for Continue {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Continue a loop from the next iteration"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("continue")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Err(ShellError::Continue(call.head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Continue a loop from the next iteration",
|
||||||
|
example: r#"for i in 1..10 { if $i == 5 { continue }; print $i }"#,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Debug;
|
pub struct Debug;
|
||||||
@ -15,11 +15,17 @@ impl Command for Debug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("debug").category(Category::Core).switch(
|
Signature::build("debug")
|
||||||
"raw",
|
.input_output_types(vec![
|
||||||
"Prints the raw value representation",
|
(
|
||||||
Some('r'),
|
Type::List(Box::new(Type::Any)),
|
||||||
)
|
Type::List(Box::new(Type::String)),
|
||||||
|
),
|
||||||
|
(Type::Table(vec![]), Type::List(Box::new(Type::String))),
|
||||||
|
(Type::Any, Type::String),
|
||||||
|
])
|
||||||
|
.category(Category::Core)
|
||||||
|
.switch("raw", "Prints the raw value representation", Some('r'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -33,6 +39,8 @@ impl Command for Debug {
|
|||||||
let config = engine_state.get_config().clone();
|
let config = engine_state.get_config().clone();
|
||||||
let raw = call.has_flag("raw");
|
let raw = call.has_flag("raw");
|
||||||
|
|
||||||
|
// Should PipelineData::Empty result in an error here?
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |x| {
|
move |x| {
|
||||||
if raw {
|
if raw {
|
||||||
@ -54,13 +62,21 @@ impl Command for Debug {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Print the value of a string",
|
description: "Debug print a string",
|
||||||
example: "'hello' | debug",
|
example: "'hello' | debug",
|
||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::test_string("hello")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Print the value of a table",
|
description: "Debug print a list",
|
||||||
example: "echo [[version patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | debug",
|
example: "['hello'] | debug",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_string("hello")],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Debug print a table",
|
||||||
|
example: "[[version patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | debug",
|
||||||
result: Some(Value::List {
|
result: Some(Value::List {
|
||||||
vals: vec![
|
vals: vec![
|
||||||
Value::test_string("{version: 0.1.0, patch: false}"),
|
Value::test_string("{version: 0.1.0, patch: false}"),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Def;
|
pub struct Def;
|
||||||
@ -16,13 +16,10 @@ impl Command for Def {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("def")
|
Signature::build("def")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("def_name", SyntaxShape::String, "definition name")
|
.required("def_name", SyntaxShape::String, "definition name")
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
.required("params", SyntaxShape::Signature, "parameters")
|
||||||
.required(
|
.required("body", SyntaxShape::Closure(None), "body of the definition")
|
||||||
"block",
|
|
||||||
SyntaxShape::Block(Some(vec![])),
|
|
||||||
"body of the definition",
|
|
||||||
)
|
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +36,10 @@ impl Command for Def {
|
|||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DefEnv;
|
pub struct DefEnv;
|
||||||
@ -16,13 +16,10 @@ impl Command for DefEnv {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("def-env")
|
Signature::build("def-env")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("def_name", SyntaxShape::String, "definition name")
|
.required("def_name", SyntaxShape::String, "definition name")
|
||||||
.required("params", SyntaxShape::Signature, "parameters")
|
.required("params", SyntaxShape::Signature, "parameters")
|
||||||
.required(
|
.required("block", SyntaxShape::Block, "body of the definition")
|
||||||
"block",
|
|
||||||
SyntaxShape::Block(Some(vec![])),
|
|
||||||
"body of the definition",
|
|
||||||
)
|
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,20 +62,17 @@ def-env cd_with_fallback [arg = ""] {
|
|||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Set environment variable by call a custom command",
|
description: "Set environment variable by call a custom command",
|
||||||
example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#,
|
example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#,
|
||||||
result: Some(Value::String {
|
result: Some(Value::test_string("BAZ")),
|
||||||
val: "BAZ".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +17,14 @@ impl Command for Describe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("describe").category(Category::Core)
|
Signature::build("describe")
|
||||||
|
.input_output_types(vec![(Type::Any, Type::String)])
|
||||||
|
.switch(
|
||||||
|
"no-collect",
|
||||||
|
"do not collect streams of structured data",
|
||||||
|
Some('n'),
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -28,32 +35,58 @@ impl Command for Describe {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
if matches!(input, PipelineData::ExternalStream { .. }) {
|
|
||||||
Ok(PipelineData::Value(
|
|
||||||
Value::string("raw input", call.head),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let value = input.into_value(call.head);
|
|
||||||
let description = match value {
|
|
||||||
Value::CustomValue { val, .. } => val.value_string(),
|
|
||||||
_ => value.get_type().to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::String {
|
let no_collect: bool = call.has_flag("no-collect");
|
||||||
val: description,
|
|
||||||
span: head,
|
let description = match input {
|
||||||
|
PipelineData::ExternalStream { .. } => "raw input".into(),
|
||||||
|
PipelineData::ListStream(_, _) => {
|
||||||
|
if no_collect {
|
||||||
|
"stream".into()
|
||||||
|
} else {
|
||||||
|
let value = input.into_value(head);
|
||||||
|
let base_description = match value {
|
||||||
|
Value::CustomValue { val, .. } => val.value_string(),
|
||||||
|
_ => value.get_type().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{base_description} (stream)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
_ => {
|
||||||
|
let value = input.into_value(head);
|
||||||
|
match value {
|
||||||
|
Value::CustomValue { val, .. } => val.value_string(),
|
||||||
|
_ => value.get_type().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: description,
|
||||||
|
span: head,
|
||||||
}
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Describe the type of a string",
|
Example {
|
||||||
example: "'hello' | describe",
|
description: "Describe the type of a string",
|
||||||
result: Some(Value::test_string("string")),
|
example: "'hello' | describe",
|
||||||
}]
|
result: Some(Value::test_string("string")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Describe a stream of data, collecting it first",
|
||||||
|
example: "[1 2 3] | each {|i| $i} | describe",
|
||||||
|
result: Some(Value::test_string("list<int> (stream)")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Describe the input but do not collect streams",
|
||||||
|
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
|
||||||
|
result: Some(Value::test_string("stream")),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use nu_engine::{eval_block, CallExt};
|
use std::thread;
|
||||||
|
|
||||||
|
use nu_engine::{eval_block_with_early_return, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,23 +17,34 @@ impl Command for Do {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Run a block"
|
"Run a closure, providing it with the pipeline input"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("do")
|
Signature::build("do")
|
||||||
.required("block", SyntaxShape::Any, "the block to run")
|
.required("closure", SyntaxShape::Any, "the closure to run")
|
||||||
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.switch(
|
.switch(
|
||||||
"ignore-errors",
|
"ignore-errors",
|
||||||
"ignore shell errors as the block runs",
|
"ignore errors as the closure runs",
|
||||||
Some('i'),
|
Some('i'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"ignore-shell-errors",
|
||||||
|
"ignore shell errors as the closure runs",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"ignore-program-errors",
|
||||||
|
"ignore external program errors as the closure runs",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"capture-errors",
|
"capture-errors",
|
||||||
"capture errors as the block runs and return it",
|
"catch errors as the closure runs, and return them",
|
||||||
Some('c'),
|
Some('c'),
|
||||||
)
|
)
|
||||||
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
.rest("rest", SyntaxShape::Any, "the parameter(s) for the closure")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +54,12 @@ impl Command for Do {
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
let block: Closure = call.req(engine_state, stack, 0)?;
|
||||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||||
let ignore_errors = call.has_flag("ignore-errors");
|
let ignore_all_errors = call.has_flag("ignore-errors");
|
||||||
|
let ignore_shell_errors = ignore_all_errors || call.has_flag("ignore-shell-errors");
|
||||||
|
let ignore_program_errors = ignore_all_errors || call.has_flag("ignore-program-errors");
|
||||||
let capture_errors = call.has_flag("capture-errors");
|
let capture_errors = call.has_flag("capture-errors");
|
||||||
|
|
||||||
let mut stack = stack.captures_to_stack(&block.captures);
|
let mut stack = stack.captures_to_stack(&block.captures);
|
||||||
@ -88,117 +103,174 @@ impl Command for Do {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result = eval_block(
|
let result = eval_block_with_early_return(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
block,
|
block,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
ignore_errors || capture_errors,
|
call.redirect_stdout,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ignore_errors {
|
match result {
|
||||||
match result {
|
Ok(PipelineData::ExternalStream {
|
||||||
Ok(x) => Ok(x),
|
stdout,
|
||||||
Err(_) => Ok(PipelineData::new(call.head)),
|
stderr,
|
||||||
}
|
exit_code,
|
||||||
} else if capture_errors {
|
span,
|
||||||
// collect stdout and stderr and check exit code.
|
metadata,
|
||||||
// if exit code is not 0, return back ShellError.
|
trim_end_newline,
|
||||||
match result {
|
}) if capture_errors => {
|
||||||
Ok(PipelineData::ExternalStream {
|
// Use a thread to receive stdout message.
|
||||||
stdout,
|
// Or we may get a deadlock if child process sends out too much bytes to stderr.
|
||||||
stderr,
|
//
|
||||||
exit_code,
|
// For example: in normal linux system, stderr pipe's limit is 65535 bytes.
|
||||||
span,
|
// if child process sends out 65536 bytes, the process will be hanged because no consumer
|
||||||
metadata,
|
// consumes the first 65535 bytes
|
||||||
}) => {
|
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
||||||
// collect all output first.
|
// stderr messages.
|
||||||
let mut stderr_ctrlc = None;
|
let stdout_handler = stdout.map(|stdout_stream| {
|
||||||
let stderr_msg = match stderr {
|
thread::Builder::new()
|
||||||
None => "".to_string(),
|
.name("stderr redirector".to_string())
|
||||||
Some(stderr_stream) => {
|
.spawn(move || {
|
||||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
let ctrlc = stdout_stream.ctrlc.clone();
|
||||||
stderr_stream.into_string().map(|s| s.item)?
|
let span = stdout_stream.span;
|
||||||
}
|
RawStream::new(
|
||||||
};
|
Box::new(
|
||||||
|
vec![stdout_stream.into_bytes().map(|s| s.item)].into_iter(),
|
||||||
|
),
|
||||||
|
ctrlc,
|
||||||
|
span,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.expect("Failed to create thread")
|
||||||
|
});
|
||||||
|
|
||||||
let mut stdout_ctrlc = None;
|
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
||||||
let stdout_msg = match stdout {
|
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
||||||
None => "".to_string(),
|
let mut stderr_ctrlc = None;
|
||||||
Some(stdout_stream) => {
|
let stderr_msg = match stderr {
|
||||||
stdout_ctrlc = stdout_stream.ctrlc.clone();
|
None => "".to_string(),
|
||||||
stdout_stream.into_string().map(|s| s.item)?
|
Some(stderr_stream) => {
|
||||||
}
|
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||||
};
|
stderr_stream.into_string().map(|s| s.item)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut exit_code_ctrlc = None;
|
let stdout = if let Some(handle) = stdout_handler {
|
||||||
let exit_code: Vec<Value> = match exit_code {
|
match handle.join() {
|
||||||
None => vec![],
|
Err(err) => {
|
||||||
Some(exit_code_stream) => {
|
|
||||||
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
|
||||||
exit_code_stream.into_iter().collect()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
|
||||||
// if exit_code is not 0, it indicates error occured, return back Err.
|
|
||||||
if *code != 0 {
|
|
||||||
return Err(ShellError::ExternalCommand(
|
return Err(ShellError::ExternalCommand(
|
||||||
"External command runs to failed".to_string(),
|
"Fail to receive external commands stdout message".to_string(),
|
||||||
stderr_msg,
|
format!("{err:?}"),
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
Ok(res) => Some(res),
|
||||||
}
|
}
|
||||||
// construct pipeline data to our caller
|
} else {
|
||||||
Ok(PipelineData::ExternalStream {
|
None
|
||||||
stdout: Some(RawStream::new(
|
};
|
||||||
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
|
|
||||||
stdout_ctrlc,
|
let mut exit_code_ctrlc = None;
|
||||||
|
let exit_code: Vec<Value> = match exit_code {
|
||||||
|
None => vec![],
|
||||||
|
Some(exit_code_stream) => {
|
||||||
|
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||||
|
exit_code_stream.into_iter().collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||||
|
if *code != 0 {
|
||||||
|
return Err(ShellError::ExternalCommand(
|
||||||
|
"External command failed".to_string(),
|
||||||
|
stderr_msg,
|
||||||
span,
|
span,
|
||||||
)),
|
));
|
||||||
stderr: Some(RawStream::new(
|
}
|
||||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
|
||||||
stderr_ctrlc,
|
|
||||||
span,
|
|
||||||
)),
|
|
||||||
exit_code: Some(ListStream::from_stream(
|
|
||||||
exit_code.into_iter(),
|
|
||||||
exit_code_ctrlc,
|
|
||||||
)),
|
|
||||||
span,
|
|
||||||
metadata,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Ok(other) => Ok(other),
|
|
||||||
Err(e) => Err(e),
|
Ok(PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr: Some(RawStream::new(
|
||||||
|
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||||
|
stderr_ctrlc,
|
||||||
|
span,
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
exit_code: Some(ListStream::from_stream(
|
||||||
|
exit_code.into_iter(),
|
||||||
|
exit_code_ctrlc,
|
||||||
|
)),
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
Ok(PipelineData::ExternalStream {
|
||||||
result
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code: _,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
}) if ignore_program_errors => Ok(PipelineData::ExternalStream {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code: None,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
trim_end_newline,
|
||||||
|
}),
|
||||||
|
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
r => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block",
|
description: "Run the closure",
|
||||||
example: r#"do { echo hello }"#,
|
example: r#"do { echo hello }"#,
|
||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::test_string("hello")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block and ignore shell errors",
|
description: "Run a stored first-class closure",
|
||||||
|
example: r#"let text = "I am enclosed"; let hello = {|| echo $text}; do $hello"#,
|
||||||
|
result: Some(Value::test_string("I am enclosed")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure and ignore both shell and external program errors",
|
||||||
example: r#"do -i { thisisnotarealcommand }"#,
|
example: r#"do -i { thisisnotarealcommand }"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure and ignore shell errors",
|
||||||
|
example: r#"do -s { thisisnotarealcommand }"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure and ignore external program errors",
|
||||||
|
example: r#"do -p { nu -c 'exit 1' }; echo "I'll still run""#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Abort the pipeline if a program returns a non-zero exit code",
|
description: "Abort the pipeline if a program returns a non-zero exit code",
|
||||||
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
|
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block, with a positional parameter",
|
description: "Run the closure, with a positional parameter",
|
||||||
example: r#"do {|x| 100 + $x } 50"#,
|
example: r#"do {|x| 100 + $x } 77"#,
|
||||||
result: Some(Value::test_int(150)),
|
result: Some(Value::test_int(177)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Run the closure, with input",
|
||||||
|
example: r#"77 | do {|x| 100 + $in }"#,
|
||||||
|
result: None, // TODO: returns 177
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -19,6 +20,7 @@ impl Command for Echo {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("echo")
|
Signature::build("echo")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||||
.rest("rest", SyntaxShape::Any, "the values to echo")
|
.rest("rest", SyntaxShape::Any, "the values to echo")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
@ -49,13 +51,7 @@ little reason to use this over just writing the values as-is."#
|
|||||||
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
|
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
|
||||||
|
|
||||||
// When there are no elements, we echo the empty string
|
// When there are no elements, we echo the empty string
|
||||||
std::cmp::Ordering::Less => PipelineData::Value(
|
std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None),
|
||||||
Value::String {
|
|
||||||
val: "".to_string(),
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +15,7 @@ impl Command for ErrorMake {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("error make")
|
Signature::build("error make")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Error)])
|
||||||
.required("error_struct", SyntaxShape::Record, "the error to create")
|
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||||
.switch(
|
.switch(
|
||||||
"unspanned",
|
"unspanned",
|
||||||
@ -108,10 +109,7 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
|||||||
) => Some(ShellError::GenericError(
|
) => Some(ShellError::GenericError(
|
||||||
message,
|
message,
|
||||||
label_text,
|
label_text,
|
||||||
Some(Span {
|
Some(Span::new(start as usize, end as usize)),
|
||||||
start: start as usize,
|
|
||||||
end: end as usize,
|
|
||||||
}),
|
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user