mirror of
https://github.com/nushell/nushell.git
synced 2025-07-04 08:31:05 +02:00
Compare commits
438 Commits
Author | SHA1 | Date | |
---|---|---|---|
fd09609b44 | |||
c7583ecdb7 | |||
4eec4a27c7 | |||
86faf753bd | |||
79d0735864 | |||
808e523adc | |||
a52386e837 | |||
c26d91fb61 | |||
0a5f8f05da | |||
a13946e3ef | |||
2e01bf9cba | |||
7e82f8d9b5 | |||
2bef85a913 | |||
e435196956 | |||
af1ab39851 | |||
baddc86d9d | |||
de6bab59bf | |||
0ff1cb1ea6 | |||
3e9bb4028a | |||
878e08cfa4 | |||
4e78f3649b | |||
ccd72fa64a | |||
03e688ea7b | |||
7e949595bd | |||
d31a51e3bc | |||
0df847da15 | |||
6af59cb0ea | |||
2ad0fcb377 | |||
f34034ae58 | |||
0e2167884d | |||
e445c41454 | |||
454d1a995c | |||
62575c9a4f | |||
4898750fc1 | |||
48b4471382 | |||
f7b8f97873 | |||
4ae1b1cc26 | |||
b7a34498e3 | |||
10fd3115c2 | |||
df60793e3b | |||
a4952bc029 | |||
f6ca62384e | |||
d6141881f2 | |||
f93033c20b | |||
33fb17776a | |||
b864a455f2 | |||
b9c78a05aa | |||
26c36e932e | |||
bd096430cb | |||
6148314dcd | |||
a7b5bd18ba | |||
e01eb42e74 | |||
c1d76bfac7 | |||
12483fac92 | |||
1a62d87a42 | |||
2ccbefe01e | |||
438062d7fc | |||
0a1af85200 | |||
5bf077d64f | |||
dec0a2517f | |||
324d625324 | |||
e22b70acff | |||
a5c604c283 | |||
644164fab3 | |||
592e677caf | |||
cc7bdebc1c | |||
27d798270b | |||
50f1e33965 | |||
ffc3727a1e | |||
b093d5d10d | |||
9e589a9d93 | |||
f8d2bff283 | |||
0a2e711351 | |||
ba5258d716 | |||
c358400351 | |||
a3f817d71b | |||
c6e2607868 | |||
a29da8c95b | |||
a09aaf3495 | |||
ffc8e752a5 | |||
6ca07b87b9 | |||
49e45915f0 | |||
96e3a3de68 | |||
2aa5c2c41f | |||
4b3e3a37a3 | |||
2492165fcb | |||
c602b5a1e8 | |||
9bbb9711e4 | |||
680405e527 | |||
44595b44c5 | |||
b27c7702f9 | |||
378a3ae05f | |||
836a56b347 | |||
b36ac8f2f8 | |||
b1e7bb899a | |||
7c285750c7 | |||
e93a8b1d32 | |||
42f0b55de0 | |||
253b223e65 | |||
85bfdca578 | |||
4dd9d0d46b | |||
fd1ac5106d | |||
b572b4ecbd | |||
585e104608 | |||
d0aefa99eb | |||
728e95c52b | |||
83087e0f9d | |||
3fb1e37473 | |||
9890966fa4 | |||
aba0fb0000 | |||
0e86ba4b63 | |||
fc23c6721a | |||
f4a129a792 | |||
8deecc0137 | |||
c7966e81c2 | |||
2659c359e9 | |||
e389e51b2b | |||
0ab6b66d8f | |||
8608d8d873 | |||
d34a2c353f | |||
150b0b6b86 | |||
d0e0701a88 | |||
28b20c5ec3 | |||
9088ef182e | |||
c4d1aa452d | |||
66e52b7cfc | |||
e761954cf0 | |||
58829e3560 | |||
62652cf8c1 | |||
4482862a40 | |||
d80ba00590 | |||
81dd4a8450 | |||
101ed629a4 | |||
95ec2fcce7 | |||
73bc3389e5 | |||
bc38a6a795 | |||
e89866bedb | |||
ca09dbbbee | |||
fa4531fd17 | |||
d17c970f8c | |||
6ca62ef131 | |||
8e84e33638 | |||
b9be416937 | |||
0a8c9b22b0 | |||
527c44ed84 | |||
8ee015a847 | |||
e8cabd16d5 | |||
88e07b5ea4 | |||
f3ee8b50e3 | |||
68ad854b0d | |||
789b2e603a | |||
66398fbf77 | |||
daeb3e5187 | |||
1fd1a3a456 | |||
30ac2d220c | |||
ade7bde813 | |||
cba3e100a0 | |||
664d8d3573 | |||
d5ce509e3a | |||
2f10d19c98 | |||
4c787af26d | |||
8136170431 | |||
007916c2c1 | |||
208ffdc1da | |||
4468dc835c | |||
90a2352337 | |||
0a9d14fcb3 | |||
7863fb1087 | |||
072d2a919d | |||
ccbdc9f6d8 | |||
0f5ea16605 | |||
1cd70d7505 | |||
b0775b3f1e | |||
2894668b3e | |||
1096e653b0 | |||
9777d755d5 | |||
58529aa0b2 | |||
64b6c02a22 | |||
0780300fb3 | |||
b9106b633b | |||
23dfaa2933 | |||
cfd2cc4970 | |||
710349768f | |||
f4bd78b86d | |||
ddb7e4e179 | |||
00601f1835 | |||
c31225fdcf | |||
16b99ed0ba | |||
3b6d340603 | |||
99aea0c71c | |||
023e244958 | |||
659d890ecf | |||
8e9ed14b89 | |||
f4bf7316fe | |||
0527f9bf0d | |||
055edd886d | |||
5e70d4121a | |||
f9b5d8bc5e | |||
2917c045fb | |||
6e6ef862c5 | |||
a7fdca05c6 | |||
ddc33dc74a | |||
a562f492e3 | |||
58f0d0b945 | |||
67d1249b2b | |||
66e5e42fb1 | |||
1f01b6438f | |||
bea7ec33c1 | |||
b5561f35b9 | |||
b796cda060 | |||
4c308b7f2f | |||
d50eb9b41b | |||
9168301369 | |||
e8d930f659 | |||
aef88aa03e | |||
ec4370069a | |||
c79ece2b21 | |||
99076af18b | |||
a0e3ad2b70 | |||
e89e734ca2 | |||
9945241b77 | |||
215ed141e7 | |||
f189ee67a1 | |||
babc7d3baf | |||
8f4807020f | |||
31e1410191 | |||
24d7227e27 | |||
c130ca1bc6 | |||
4db960c0a6 | |||
d13ce2aec9 | |||
5e957ecda6 | |||
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 |
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"
|
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@ -19,6 +19,13 @@ Make sure you've run and fixed any issues with these commands:
|
|||||||
- `cargo clippy --workspace -- -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` to check that all tests pass
|
- `cargo test --workspace` to check that all tests pass
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> from `nushell` you can also use the `toolkit` as follows
|
||||||
|
> ```bash
|
||||||
|
> use toolkit.nu # or use an `env_change` hook to activate it automatically
|
||||||
|
> toolkit check pr
|
||||||
|
> ```
|
||||||
|
|
||||||
# After Submitting
|
# After Submitting
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
73
.github/workflows/ci.yml
vendored
73
.github/workflows/ci.yml
vendored
@ -15,8 +15,22 @@ jobs:
|
|||||||
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
# 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)
|
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
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:
|
||||||
@ -26,13 +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.4.3
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
|
|
||||||
nu-tests:
|
nu-tests:
|
||||||
env:
|
env:
|
||||||
@ -63,7 +77,7 @@ 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.4.3
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||||
@ -87,7 +101,7 @@ 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.4.3
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
run: cargo install --locked --path=. --profile ci --no-default-features
|
run: cargo install --locked --path=. --profile ci --no-default-features
|
||||||
@ -99,12 +113,20 @@ 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
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
|
run: |
|
||||||
|
cd virtualenv
|
||||||
|
# if we encounter problems with bleeding edge tests pin to the latest tag
|
||||||
|
# git checkout $(git describe --tags | cut -d - -f 1)
|
||||||
|
# We need to disable failing on coverage levels.
|
||||||
|
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
|
||||||
|
mv patchproject.toml pyproject.toml
|
||||||
|
tox -e ${{ matrix.py }} -- -k nushell
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
|
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
|
||||||
@ -126,10 +148,49 @@ 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.4.3
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --profile ci --package nu_plugin_*
|
run: cargo test --profile ci --package nu_plugin_*
|
||||||
|
|
||||||
|
|
||||||
|
nu-coverage:
|
||||||
|
needs: nu-tests
|
||||||
|
env:
|
||||||
|
NUSHELL_CARGO_TARGET: ci
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
# disabled mac due to problems with merging coverage and similarity to linux
|
||||||
|
# disabled windows due to running out of disk space when having too many crates or tests
|
||||||
|
platform: [ubuntu-20.04] # windows-latest
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Rust toolchain and cache
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||||
|
- name: Install cargo-llvm-cov
|
||||||
|
uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
|
||||||
|
cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
|
||||||
|
cargo build --workspace --profile ci
|
||||||
|
cargo test --workspace --profile ci
|
||||||
|
cargo llvm-cov report --profile ci --lcov --output-path lcov.info
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov with GitHub Action
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
files: lcov.info
|
||||||
|
41
.github/workflows/manual.yml
vendored
41
.github/workflows/manual.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
# This is a basic workflow that is manually triggered
|
|
||||||
# Don't run it unless you know what you are doing
|
|
||||||
|
|
||||||
name: Manual Workflow for Winget Submission
|
|
||||||
|
|
||||||
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
|
||||||
# or API.
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
# Inputs the workflow accepts.
|
|
||||||
inputs:
|
|
||||||
ver:
|
|
||||||
# Friendly description to be shown in the UI instead of 'ver'
|
|
||||||
description: 'The nushell version to release'
|
|
||||||
# Default value if no value is explicitly provided
|
|
||||||
default: '0.66.0'
|
|
||||||
# Input has to be provided for the workflow to run
|
|
||||||
required: true
|
|
||||||
uri:
|
|
||||||
# Friendly description to be shown in the UI instead of 'uri'
|
|
||||||
description: 'The nushell windows .msi package URI to publish'
|
|
||||||
# Default value if no value is explicitly provided
|
|
||||||
default: 'https://github.com/nushell/nushell/releases/download/0.66.0/nu-0.66.0-x86_64-pc-windows-msvc.msi'
|
|
||||||
# Input has to be provided for the workflow to run
|
|
||||||
required: true
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
|
||||||
# This workflow contains a single job
|
|
||||||
rls-winget-pkg:
|
|
||||||
name: Publish winget package manually
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
|
||||||
# Runs commands using the runners shell
|
|
||||||
- name: Submit package to Windows Package Manager Community Repository Manually
|
|
||||||
run: |
|
|
||||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
|
||||||
.\wingetcreate.exe update Nushell.Nushell -s -v ${{ github.event.inputs.ver }} -u ${{ github.event.inputs.uri }} -t ${{ secrets.NUSHELL_PAT }}
|
|
39
.github/workflows/release-pkg.nu
vendored
39
.github/workflows/release-pkg.nu
vendored
@ -6,19 +6,38 @@
|
|||||||
# REF:
|
# REF:
|
||||||
# 1. https://github.com/volks73/cargo-wix
|
# 1. https://github.com/volks73/cargo-wix
|
||||||
|
|
||||||
|
# Instructions for manually creating an MSI for Winget Releases when they fail
|
||||||
# Added 2022-11-29 when Windows packaging wouldn't work
|
# Added 2022-11-29 when Windows packaging wouldn't work
|
||||||
# because softprops/action-gh-release was broken
|
# Updated again on 2023-02-23 because msis are still failing validation
|
||||||
# To run this manual for windows
|
# To run this manual for windows here are the steps I take
|
||||||
# let-env TARGET = 'x86_64-pc-windows-msvc'
|
# checkout the release you want to publish
|
||||||
# let-env TARGET_RUSTFLAGS = ''
|
# 1. git checkout 0.76.0
|
||||||
# let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
|
||||||
# Pass 1 let-env _EXTRA_ = 'bin'
|
# 2. $env:CARGO_TARGET_DIR = ""
|
||||||
# Pass 2 let-env _EXTRA_ = 'msi'
|
# 2. hide-env CARGO_TARGET_DIR
|
||||||
|
# 3. let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||||
|
# 4. let-env TARGET_RUSTFLAGS = ''
|
||||||
|
# 5. let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||||
|
# 6. let-env GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
|
||||||
|
# 7. let-env OS = 'windows-latest'
|
||||||
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
||||||
|
# 8. let-env Path = ($env.Path | append 'c:\apps\7-zip')
|
||||||
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
||||||
|
# 9. let-env Path = ($env.Path | append 'c:\path\to\aria2c')
|
||||||
# make sure you have the wixtools installed https://wixtoolset.org/
|
# make sure you have the wixtools installed https://wixtoolset.org/
|
||||||
# set os below like this because it's what github's runner is named
|
# 10. let-env Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
||||||
# let os = 'windows-latest'
|
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
|
||||||
|
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
|
||||||
|
# 11. let-env _EXTRA_ = 'bin'
|
||||||
|
# 12. source .github\workflows\release-pkg.nu
|
||||||
|
# 13. cd ..
|
||||||
|
# 14. let-env _EXTRA_ = 'msi'
|
||||||
|
# 15. source .github\workflows\release-pkg.nu
|
||||||
|
# After msi is generated, 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
|
||||||
|
# 16. 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
|
||||||
@ -145,7 +164,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
cd $src; hr-line
|
cd $src; hr-line
|
||||||
# Wix need the binaries be stored in target/release/
|
# Wix need the binaries be stored in target/release/
|
||||||
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.4
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
print $'archive: ---> ($wixRelease)';
|
print $'archive: ---> ($wixRelease)';
|
||||||
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -63,14 +63,14 @@ jobs:
|
|||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.1.0
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Update Rust Toolchain Target
|
- name: Update Rust Toolchain Target
|
||||||
run: |
|
run: |
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- 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.4.3
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
|
28
.github/workflows/stale.yml
vendored
28
.github/workflows/stale.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: 'Close stale issues and PRs'
|
|
||||||
#on: [workflow_dispatch]
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '30 1 * * *'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v3
|
|
||||||
with:
|
|
||||||
#debug-only: true
|
|
||||||
ascending: true
|
|
||||||
operations-per-run: 520
|
|
||||||
enable-statistics: true
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
|
|
||||||
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
|
|
||||||
days-before-issue-stale: 90
|
|
||||||
days-before-pr-stale: 45
|
|
||||||
days-before-issue-close: 100000
|
|
||||||
days-before-pr-close: 100
|
|
||||||
exempt-issue-labels: 'exempt,keep'
|
|
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@v3
|
||||||
|
|
||||||
|
- name: Check spelling
|
||||||
|
uses: crate-ci/typos@master
|
21
.github/workflows/winget-submission.yml
vendored
21
.github/workflows/winget-submission.yml
vendored
@ -1,9 +1,14 @@
|
|||||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [released]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag_name:
|
||||||
|
description: 'Specific tag name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -12,8 +17,10 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
run: |
|
uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
iwr https://github.com/microsoft/winget-create/releases/download/v1.0.4.0/wingetcreate.exe -OutFile wingetcreate.exe
|
with:
|
||||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
identifier: Nushell.Nushell
|
||||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
|
token: ${{ secrets.NUSHELL_PAT }}
|
||||||
|
fork-user: fdncred
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -39,3 +39,10 @@ tarpaulin-report.html
|
|||||||
*.rsproj
|
*.rsproj
|
||||||
*.rsproj.user
|
*.rsproj.user
|
||||||
*.sln
|
*.sln
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.direnv/
|
||||||
|
.envrc
|
||||||
|
|
||||||
|
# pre-commit-hooks
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
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"
|
2182
Cargo.lock
generated
2182
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
78
Cargo.toml
78
Cargo.toml
@ -3,14 +3,14 @@ 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.73.0"
|
version = "0.77.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
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ members = [
|
|||||||
"crates/nu-engine",
|
"crates/nu-engine",
|
||||||
"crates/nu-parser",
|
"crates/nu-parser",
|
||||||
"crates/nu-system",
|
"crates/nu-system",
|
||||||
|
"crates/nu-cmd-lang",
|
||||||
"crates/nu-command",
|
"crates/nu-command",
|
||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
@ -35,6 +36,7 @@ members = [
|
|||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu_plugin_query",
|
||||||
"crates/nu_plugin_custom_values",
|
"crates/nu_plugin_custom_values",
|
||||||
|
"crates/nu_plugin_formats",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -43,25 +45,27 @@ 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-cli = { path = "./crates/nu-cli", version = "0.77.0" }
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.73.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.77.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.73.0" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.77.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.73.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.77.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.73.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.77.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.73.0" }
|
nu-json = { path = "./crates/nu-json", version = "0.77.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.73.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.77.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.73.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.77.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.73.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.77.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.73.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.77.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.73.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.77.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.73.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.77.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.73.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.77.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.73.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.77.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.73.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.77.0" }
|
||||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
|
||||||
|
|
||||||
rayon = "1.5.1"
|
nu-ansi-term = "0.47.0"
|
||||||
|
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
|
rayon = "1.7.0"
|
||||||
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"
|
||||||
@ -76,21 +80,34 @@ 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.26", 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.73.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.77.0" }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.4.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.16.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 = [
|
||||||
|
"nu-plugin",
|
||||||
|
"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 used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||||
extra = ["default"]
|
extra = ["default"]
|
||||||
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||||
@ -135,8 +152,17 @@ debug = false
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
bench = false
|
||||||
|
|
||||||
# To use a development version of a dependency please use a global override here
|
# To use a development version of a dependency please use a global override here
|
||||||
# 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" }
|
||||||
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.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"
|
13
README.md
13
README.md
@ -1,11 +1,12 @@
|
|||||||
# Nushell <!-- omit in toc -->
|
# Nushell <!-- omit in toc -->
|
||||||
[](https://crates.io/crates/nu)
|
[](https://crates.io/crates/nu)
|
||||||

|
[](https://github.com/nushell/nushell/actions)
|
||||||
[](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)
|
||||||

|
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
||||||

|
[](https://github.com/nushell/nushell/graphs/contributors)
|
||||||
|
[](https://codecov.io/gh/nushell/nushell)
|
||||||
|
|
||||||
A new type of shell.
|
A new type of shell.
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ This project has reached a minimum-viable-product level of quality. Many people
|
|||||||
|
|
||||||
## Learning About Nu
|
## Learning About Nu
|
||||||
|
|
||||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||||
|
|
||||||
@ -47,7 +48,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:
|
||||||
|
|
||||||
@ -174,6 +175,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.
|
||||||
|
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`
|
191
benches/benchmarks.rs
Normal file
191
benches/benchmarks.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
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(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
@ -5,7 +5,7 @@
|
|||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
echo Building nushell.exe
|
echo Building nushell.exe
|
||||||
cargo build cargo build --features=dataframe
|
cargo build --features=dataframe
|
||||||
@echo.
|
@echo.
|
||||||
|
|
||||||
@cd crates\nu_plugin_example
|
@cd crates\nu_plugin_example
|
||||||
|
@ -12,6 +12,7 @@ let plugins = [
|
|||||||
nu_plugin_query,
|
nu_plugin_query,
|
||||||
nu_plugin_example,
|
nu_plugin_example,
|
||||||
nu_plugin_custom_values,
|
nu_plugin_custom_values,
|
||||||
|
nu_plugin_formats,
|
||||||
]
|
]
|
||||||
|
|
||||||
for plugin in $plugins {
|
for plugin in $plugins {
|
||||||
|
17
codecov.yml
Normal file
17
codecov.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
target: 55%
|
||||||
|
threshold: 2%
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
informational: true
|
||||||
|
|
||||||
|
comment:
|
||||||
|
layout: reach, diff, files
|
||||||
|
behavior: default
|
||||||
|
require_base: yes
|
||||||
|
require_head: yes
|
||||||
|
after_n_builds: 1 # Disabled windows else: 2
|
||||||
|
|
54
coverage-local.nu
Executable file
54
coverage-local.nu
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env nu
|
||||||
|
|
||||||
|
let start = (date now)
|
||||||
|
# Script to generate coverage locally
|
||||||
|
#
|
||||||
|
# Output: `lcov.info` file
|
||||||
|
#
|
||||||
|
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||||
|
# https://github.com/taiki-e/cargo-llvm-cov
|
||||||
|
|
||||||
|
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||||
|
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||||
|
# Else the script might stall waiting for your `y<ENTER>`
|
||||||
|
|
||||||
|
# Some of the internal tests rely on the exact cargo profile
|
||||||
|
# (This is somewhat criminal itself)
|
||||||
|
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||||
|
let-env NUSHELL_CARGO_TARGET = "ci"
|
||||||
|
|
||||||
|
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||||
|
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||||
|
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||||
|
|
||||||
|
print "Setting up environment variables for coverage"
|
||||||
|
# Enable LLVM coverage tracking through environment variables
|
||||||
|
# show env outputs .ini/.toml style description of the variables
|
||||||
|
# In order to use from toml, we need to make sure our string literals are single quoted
|
||||||
|
# This is especially important when running on Windows since "C:\blah" is treated as an escape
|
||||||
|
cargo llvm-cov show-env | str replace (char dq) (char sq) -a | from toml | load-env
|
||||||
|
|
||||||
|
print "Cleaning up coverage data"
|
||||||
|
cargo llvm-cov clean --workspace
|
||||||
|
|
||||||
|
print "Building with workspace and profile=ci"
|
||||||
|
# Apparently we need to explicitly build the necessary parts
|
||||||
|
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||||
|
# leads to smaller binaries and potential savings when compiling and running
|
||||||
|
cargo build --workspace --profile=ci
|
||||||
|
|
||||||
|
print "Running tests with --workspace and profile=ci"
|
||||||
|
cargo test --workspace --profile=ci
|
||||||
|
|
||||||
|
# You need to provide the used profile to find the raw data
|
||||||
|
print "Generating coverage report as lcov.info"
|
||||||
|
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||||
|
|
||||||
|
let end = (date now)
|
||||||
|
$"Coverage generation took ($end - $start)."
|
||||||
|
|
||||||
|
# To display the coverage in your editor see:
|
||||||
|
#
|
||||||
|
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||||
|
# - https://github.com/umaumax/vim-lcov
|
||||||
|
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
38
coverage-local.sh
Executable file
38
coverage-local.sh
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Script to generate coverage locally
|
||||||
|
#
|
||||||
|
# Output: `lcov.info` file
|
||||||
|
#
|
||||||
|
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||||
|
# https://github.com/taiki-e/cargo-llvm-cov
|
||||||
|
|
||||||
|
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||||
|
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||||
|
# Else the script might stall waiting for your `y<ENTER>`
|
||||||
|
|
||||||
|
# Some of the internal tests rely on the exact cargo profile
|
||||||
|
# (This is somewhat criminal itself)
|
||||||
|
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||||
|
export NUSHELL_CARGO_TARGET=ci
|
||||||
|
|
||||||
|
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||||
|
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||||
|
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||||
|
|
||||||
|
# Enable LLVM coverage tracking through environment variables
|
||||||
|
source <(cargo llvm-cov show-env --export-prefix)
|
||||||
|
cargo llvm-cov clean --workspace
|
||||||
|
# Apparently we need to explicitly build the necessary parts
|
||||||
|
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||||
|
# leads to smaller binaries and potential savings when compiling and running
|
||||||
|
cargo build --workspace --profile=ci
|
||||||
|
cargo test --workspace --profile=ci
|
||||||
|
# You need to provide the used profile to find the raw data
|
||||||
|
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||||
|
|
||||||
|
# To display the coverage in your editor see:
|
||||||
|
#
|
||||||
|
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||||
|
# - https://github.com/umaumax/vim-lcov
|
||||||
|
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
@ -5,34 +5,38 @@ 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.73.0"
|
version = "0.77.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.73.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.77.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.73.0" }
|
nu-command = { path = "../nu-command", version = "0.77.0" }
|
||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = { version = "0.16.0", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.73.0" }
|
nu-engine = { path = "../nu-engine", version = "0.77.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.73.0" }
|
nu-path = { path = "../nu-path", version = "0.77.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.73.0" }
|
nu-parser = { path = "../nu-parser", version = "0.77.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.73.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.77.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.73.0" }
|
nu-utils = { path = "../nu-utils", version = "0.77.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-color-config = { path = "../nu-color-config", version = "0.77.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.73.0" }
|
|
||||||
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
nu-ansi-term = "0.47.0"
|
||||||
|
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
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"
|
||||||
once_cell = "1.16.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.28.2"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -37,7 +37,8 @@ impl CommandCompletion {
|
|||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
let mut executables = vec![];
|
let mut executables = vec![];
|
||||||
|
|
||||||
let paths = self.engine_state.get_env_var("PATH");
|
// os agnostic way to get the PATH env var
|
||||||
|
let paths = self.engine_state.get_path_env_var();
|
||||||
|
|
||||||
if let Some(paths) = paths {
|
if let Some(paths) = paths {
|
||||||
if let Ok(paths) = paths.as_list() {
|
if let Ok(paths) = paths.as_list() {
|
||||||
@ -198,6 +199,7 @@ impl Completer for CommandCompletion {
|
|||||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||||
|| ((span.end - span.start) == 0)
|
|| ((span.end - span.start) == 0)
|
||||||
|
|| is_passthrough_command(working_set.delta.get_file_contents())
|
||||||
{
|
{
|
||||||
// we're in a gap or at a command
|
// we're in a gap or at a command
|
||||||
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||||
@ -225,3 +227,107 @@ impl Completer for CommandCompletion {
|
|||||||
SortBy::LevenshteinDistance
|
SortBy::LevenshteinDistance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
|
||||||
|
match contents.get(start..) {
|
||||||
|
Some(contents) => {
|
||||||
|
contents
|
||||||
|
.iter()
|
||||||
|
.take_while(|x| x.is_ascii_whitespace())
|
||||||
|
.count()
|
||||||
|
+ start
|
||||||
|
}
|
||||||
|
None => start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usize)]) -> bool {
|
||||||
|
for (contents, _, _) in working_set_file_contents {
|
||||||
|
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
||||||
|
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
||||||
|
|
||||||
|
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
||||||
|
|
||||||
|
let result = match contents.get(cur_pos..) {
|
||||||
|
Some(contents) => contents.starts_with(b"sudo "),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
if result {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod command_completions_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_non_whitespace_index() {
|
||||||
|
let commands = vec![
|
||||||
|
(" hello", 4),
|
||||||
|
("sudo ", 0),
|
||||||
|
(" sudo ", 2),
|
||||||
|
(" sudo ", 2),
|
||||||
|
(" hello ", 1),
|
||||||
|
(" hello ", 3),
|
||||||
|
(" hello | sudo ", 4),
|
||||||
|
(" sudo|sudo", 5),
|
||||||
|
("sudo | sudo ", 0),
|
||||||
|
(" hello sud", 1),
|
||||||
|
];
|
||||||
|
for (idx, ele) in commands.iter().enumerate() {
|
||||||
|
let index = find_non_whitespace_index(&Vec::from(ele.0.as_bytes()), 0);
|
||||||
|
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_last_command_passthrough() {
|
||||||
|
let commands = vec![
|
||||||
|
(" hello", false),
|
||||||
|
(" sudo ", true),
|
||||||
|
("sudo ", true),
|
||||||
|
(" hello", false),
|
||||||
|
(" sudo", false),
|
||||||
|
(" sudo ", true),
|
||||||
|
(" sudo ", true),
|
||||||
|
(" sudo ", true),
|
||||||
|
(" hello ", false),
|
||||||
|
(" hello | sudo ", true),
|
||||||
|
(" sudo|sudo", false),
|
||||||
|
("sudo | sudo ", true),
|
||||||
|
(" hello sud", false),
|
||||||
|
(" sudo | sud ", false),
|
||||||
|
(" sudo|sudo ", true),
|
||||||
|
(" sudo | sudo ls | sudo ", true),
|
||||||
|
];
|
||||||
|
for (idx, ele) in commands.iter().enumerate() {
|
||||||
|
let input = ele.0.as_bytes();
|
||||||
|
|
||||||
|
let mut engine_state = EngineState::new();
|
||||||
|
engine_state.add_file("test.nu".into(), vec![]);
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
working_set.add_file("child.nu".into(), input);
|
||||||
|
working_set.render()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = engine_state.merge_delta(delta);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Merge delta has failed: {}",
|
||||||
|
result.err().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
|
||||||
|
assert_eq!(
|
||||||
|
is_passthrough_command, ele.1,
|
||||||
|
"index for '{}': {}",
|
||||||
|
ele.0, idx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -104,7 +104,7 @@ impl NuCompleter {
|
|||||||
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
|
||||||
@ -128,12 +128,17 @@ impl NuCompleter {
|
|||||||
PipelineElement::Expression(_, expr)
|
PipelineElement::Expression(_, expr)
|
||||||
| PipelineElement::Redirection(_, _, expr)
|
| PipelineElement::Redirection(_, _, expr)
|
||||||
| PipelineElement::And(_, expr)
|
| PipelineElement::And(_, expr)
|
||||||
| PipelineElement::Or(_, expr) => {
|
| PipelineElement::Or(_, expr)
|
||||||
|
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
let span_offset: usize = alias_offset.iter().sum();
|
let span_offset: usize = alias_offset.iter().sum();
|
||||||
let mut spans: Vec<String> = vec![];
|
let mut spans: Vec<String> = vec![];
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
|
let is_passthrough_command = spans
|
||||||
|
.first()
|
||||||
|
.filter(|content| *content == &String::from("sudo"))
|
||||||
|
.is_some();
|
||||||
// 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);
|
||||||
@ -216,8 +221,9 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// specially check if it is currently empty - always complete commands
|
// specially check if it is currently empty - always complete commands
|
||||||
if flat_idx == 0
|
if (is_passthrough_command && flat_idx == 1)
|
||||||
&& working_set.get_span_contents(new_span).is_empty()
|
|| (flat_idx == 0
|
||||||
|
&& working_set.get_span_contents(new_span).is_empty())
|
||||||
{
|
{
|
||||||
let mut completer = CommandCompletion::new(
|
let mut completer = CommandCompletion::new(
|
||||||
self.engine_state.clone(),
|
self.engine_state.clone(),
|
||||||
@ -238,7 +244,7 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Completions that depends on the previous expression (e.g: use, source-env)
|
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||||
if flat_idx > 0 {
|
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 {
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||||
// Read the content for the previous expression
|
// Read the content for the previous expression
|
||||||
let prev_expr_str =
|
let prev_expr_str =
|
||||||
@ -575,3 +581,63 @@ pub fn map_value_completions<'a>(
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod completer_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_helper() {
|
||||||
|
let mut engine_state = nu_command::create_default_context();
|
||||||
|
|
||||||
|
// Custom additions
|
||||||
|
let delta = {
|
||||||
|
let working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
|
||||||
|
working_set.render()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = engine_state.merge_delta(delta);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Error merging delta: {:?}",
|
||||||
|
result.err().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
||||||
|
let dataset = vec![
|
||||||
|
("sudo", false, "", Vec::new()),
|
||||||
|
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||||
|
(" sudo", false, "", Vec::new()),
|
||||||
|
(" sudo le", true, "le", vec!["let", "length"]),
|
||||||
|
(
|
||||||
|
"ls | c",
|
||||||
|
true,
|
||||||
|
"c",
|
||||||
|
vec!["cd", "config", "const", "cp", "cal"],
|
||||||
|
),
|
||||||
|
("ls | sudo m", true, "m", vec!["mv", "mut", "move"]),
|
||||||
|
];
|
||||||
|
for (line, has_result, begins_with, expected_values) in dataset {
|
||||||
|
let result = completer.completion_helper(line, line.len());
|
||||||
|
// Test whether the result is empty or not
|
||||||
|
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
|
||||||
|
|
||||||
|
// Test whether the result begins with the expected value
|
||||||
|
result
|
||||||
|
.iter()
|
||||||
|
.for_each(|x| assert!(x.value.starts_with(begins_with)));
|
||||||
|
|
||||||
|
// Test whether the result contains all the expected values
|
||||||
|
assert_eq!(
|
||||||
|
result
|
||||||
|
.iter()
|
||||||
|
.map(|x| expected_values.contains(&x.value.as_str()))
|
||||||
|
.filter(|x| *x)
|
||||||
|
.count(),
|
||||||
|
expected_values.len(),
|
||||||
|
"line: {}",
|
||||||
|
line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -66,6 +66,7 @@ impl Completer for CustomCompletion {
|
|||||||
],
|
],
|
||||||
redirect_stdout: true,
|
redirect_stdout: true,
|
||||||
redirect_stderr: true,
|
redirect_stderr: true,
|
||||||
|
parser_info: vec![],
|
||||||
},
|
},
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
);
|
);
|
||||||
@ -73,8 +74,8 @@ impl Completer for CustomCompletion {
|
|||||||
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)
|
||||||
|
@ -8,7 +8,7 @@ use std::fs;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{partial_from, prepend_base_dir};
|
use super::{partial_from, prepend_base_dir, SortBy};
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
@ -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
|
||||||
@ -67,12 +60,20 @@ impl Completer for DirectoryCompletion {
|
|||||||
|
|
||||||
// Sort items
|
// Sort items
|
||||||
let mut sorted_items = items;
|
let mut sorted_items = items;
|
||||||
|
|
||||||
|
match self.get_sort_by() {
|
||||||
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||||
|
}
|
||||||
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
// Separate the results between hidden and non hidden
|
// Separate the results between hidden and non hidden
|
||||||
let mut hidden: Vec<Suggestion> = vec![];
|
let mut hidden: Vec<Suggestion> = vec![];
|
||||||
@ -126,7 +127,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()
|
||||||
};
|
};
|
||||||
@ -142,7 +143,7 @@ pub fn directory_completion(
|
|||||||
|| path.contains(' ')
|
|| path.contains(' ')
|
||||||
|| path.contains('#')
|
|| path.contains('#')
|
||||||
{
|
{
|
||||||
path = format!("`{}`", path);
|
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
|
||||||
|
@ -7,6 +7,8 @@ use reedline::Suggestion;
|
|||||||
use std::path::{is_separator, Path};
|
use std::path::{is_separator, Path};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::SortBy;
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -30,14 +32,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()
|
||||||
@ -62,12 +57,20 @@ impl Completer for FileCompletion {
|
|||||||
|
|
||||||
// Sort items
|
// Sort items
|
||||||
let mut sorted_items = items;
|
let mut sorted_items = items;
|
||||||
|
|
||||||
|
match self.get_sort_by() {
|
||||||
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||||
|
}
|
||||||
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
// Separate the results between hidden and non hidden
|
// Separate the results between hidden and non hidden
|
||||||
let mut hidden: Vec<Suggestion> = vec![];
|
let mut hidden: Vec<Suggestion> = vec![];
|
||||||
@ -131,7 +134,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()
|
||||||
};
|
};
|
||||||
@ -146,8 +149,20 @@ pub fn file_path_completion(
|
|||||||
|| path.contains('"')
|
|| path.contains('"')
|
||||||
|| path.contains(' ')
|
|| path.contains(' ')
|
||||||
|| path.contains('#')
|
|| path.contains('#')
|
||||||
|
|| path.contains('(')
|
||||||
|
|| path.contains(')')
|
||||||
|
|| path.starts_with('0')
|
||||||
|
|| path.starts_with('1')
|
||||||
|
|| path.starts_with('2')
|
||||||
|
|| path.starts_with('3')
|
||||||
|
|| path.starts_with('4')
|
||||||
|
|| path.starts_with('5')
|
||||||
|
|| path.starts_with('6')
|
||||||
|
|| path.starts_with('7')
|
||||||
|
|| path.starts_with('8')
|
||||||
|
|| path.starts_with('9')
|
||||||
{
|
{
|
||||||
path = format!("`{}`", path);
|
path = format!("`{path}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((span, path))
|
Some((span, path))
|
||||||
@ -175,7 +190,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,
|
||||||
@ -116,10 +120,11 @@ impl Completer for VariableCompletion {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,10 +143,11 @@ impl Completer for VariableCompletion {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,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,
|
||||||
@ -178,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,
|
||||||
@ -200,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,
|
||||||
@ -247,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,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,7 +1,5 @@
|
|||||||
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;
|
||||||
@ -9,6 +7,8 @@ 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};
|
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,7 +33,7 @@ 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,
|
||||||
@ -39,11 +41,19 @@ pub fn read_plugin_file(
|
|||||||
&contents,
|
&contents,
|
||||||
&plugin_filename,
|
&plugin_filename,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
perf(
|
||||||
|
&format!("read_plugin_file {}", &plug_path),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
engine_state.get_config().use_ansi_coloring,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
@ -56,13 +66,12 @@ 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
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
@ -86,6 +95,7 @@ pub fn eval_config_contents(
|
|||||||
&contents,
|
&contents,
|
||||||
&config_filename,
|
&config_filename,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge the environment in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
|
@ -29,10 +29,7 @@ pub fn evaluate_file(
|
|||||||
|
|
||||||
let cwd = current_dir(engine_state, stack)?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
|
|
||||||
let file_path = {
|
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
|
||||||
match canonicalize_with(&path, &cwd) {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -42,13 +39,9 @@ pub fn evaluate_file(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_path_str = match file_path.to_str() {
|
let file_path_str = file_path.to_str().unwrap_or_else(|| {
|
||||||
Some(s) => s,
|
|
||||||
None => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -61,12 +54,11 @@ pub fn evaluate_file(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
let file = match std::fs::read(&file_path).into_diagnostic() {
|
let file = std::fs::read(&file_path)
|
||||||
Ok(p) => p,
|
.into_diagnostic()
|
||||||
Err(e) => {
|
.unwrap_or_else(|e| {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -80,13 +72,21 @@ pub fn evaluate_file(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
engine_state.start_in_file(Some(file_path_str));
|
engine_state.start_in_file(Some(file_path_str));
|
||||||
|
|
||||||
let mut parent = file_path.clone();
|
let parent = file_path.parent().unwrap_or_else(|| {
|
||||||
parent.pop();
|
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(
|
stack.add_env_var(
|
||||||
"FILE_PWD".to_string(),
|
"FILE_PWD".to_string(),
|
||||||
@ -106,13 +106,21 @@ pub fn evaluate_file(
|
|||||||
&file,
|
&file,
|
||||||
file_path_str,
|
file_path_str,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
|
true,
|
||||||
) {
|
) {
|
||||||
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,
|
||||||
|
true,
|
||||||
|
) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
} else if !eval_source(engine_state, stack, &file, file_path_str, input) {
|
} else if !eval_source(engine_state, stack, &file, file_path_str, input, true) {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +129,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,
|
||||||
@ -137,24 +145,19 @@ pub fn print_table_or_error(
|
|||||||
|
|
||||||
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &**error);
|
||||||
report_error(&working_set, error);
|
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
|
||||||
Some(decl_id) => {
|
|
||||||
let command = engine_state.get_decl(decl_id);
|
let command = engine_state.get_decl(decl_id);
|
||||||
if command.get_block_id().is_some() {
|
if command.get_block_id().is_some() {
|
||||||
print_or_exit(pipeline_data, engine_state, config);
|
print_or_exit(pipeline_data, engine_state, config);
|
||||||
} else {
|
} else {
|
||||||
let table = command.run(
|
// The final call on table command, it's ok to set redirect_output to false.
|
||||||
engine_state,
|
let mut call = Call::new(Span::new(0, 0));
|
||||||
stack,
|
call.redirect_stdout = false;
|
||||||
&Call::new(Span::new(0, 0)),
|
let table = command.run(engine_state, stack, &call, pipeline_data);
|
||||||
pipeline_data,
|
|
||||||
);
|
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
@ -162,18 +165,14 @@ pub fn print_table_or_error(
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
report_error(&working_set, &error);
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
None => {
|
|
||||||
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 {
|
||||||
@ -194,14 +193,12 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
|
|||||||
if let Value::Error { error } = item {
|
if let Value::Error { error } = item {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &error);
|
report_error(&working_set, &*error);
|
||||||
|
|
||||||
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}",)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,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")
|
||||||
@ -157,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 {
|
||||||
@ -51,7 +53,9 @@ impl Command for NuHighlight {
|
|||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => Value::Error { error: err },
|
Err(err) => Value::Error {
|
||||||
|
error: Box::new(err),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ctrlc,
|
ctrlc,
|
||||||
)
|
)
|
||||||
|
@ -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",
|
||||||
@ -26,7 +28,7 @@ impl Command for Print {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Print the given values to stdout"
|
"Print the given values to stdout."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
@ -45,16 +47,21 @@ Since this command has no output, there is no point in piping it with other comm
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
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");
|
||||||
|
|
||||||
|
// This will allow for easy printing of pipelines as well
|
||||||
|
if !args.is_empty() {
|
||||||
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)?;
|
||||||
}
|
}
|
||||||
|
} else if !input.is_nothing() {
|
||||||
|
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
@ -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,6 +1,6 @@
|
|||||||
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},
|
||||||
@ -39,41 +39,37 @@ fn get_prompt_string(
|
|||||||
// 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 =
|
let ret_val =
|
||||||
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
||||||
info!(
|
trace!(
|
||||||
"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, .. } => {
|
Value::Block { val: block_id, .. } => {
|
||||||
let block = engine_state.get_block(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
|
// 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());
|
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
||||||
info!(
|
trace!(
|
||||||
"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::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -81,8 +77,7 @@ 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();
|
||||||
@ -91,10 +86,8 @@ fn get_prompt_string(
|
|||||||
if x.ends_with('\r') {
|
if x.ends_with('\r') {
|
||||||
x.pop();
|
x.pop();
|
||||||
}
|
}
|
||||||
Some(x)
|
x
|
||||||
}
|
})
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,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
|
||||||
@ -148,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
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ use nu_parser::parse;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
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 {
|
||||||
@ -652,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)
|
||||||
}
|
}
|
||||||
@ -680,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)
|
||||||
@ -990,7 +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("Enter", Span::test_data())];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
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();
|
||||||
@ -1010,7 +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("Clear", Span::test_data())];
|
let vals = vec![Value::test_string("Clear")];
|
||||||
|
|
||||||
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();
|
||||||
@ -1034,8 +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("Menu", Span::test_data()),
|
Value::test_string("Menu"),
|
||||||
Value::string("history_menu", Span::test_data()),
|
Value::test_string("history_menu"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
@ -1061,8 +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("Menu", Span::test_data()),
|
Value::test_string("Menu"),
|
||||||
Value::string("history_menu", Span::test_data()),
|
Value::test_string("history_menu"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1073,7 +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("Enter", Span::test_data())];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1114,8 +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("Menu", Span::test_data()),
|
Value::test_string("Menu"),
|
||||||
Value::string("history_menu", Span::test_data()),
|
Value::test_string("history_menu"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::Record {
|
||||||
@ -1126,7 +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("Enter", Span::test_data())];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::Record {
|
||||||
cols,
|
cols,
|
||||||
@ -1154,7 +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("Enter", Span::test_data())];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
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,18 +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 log::{info, trace, warn};
|
use crossterm::cursor::CursorShape;
|
||||||
|
use log::{trace, warn};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
|
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,
|
||||||
@ -39,8 +42,10 @@ 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};
|
||||||
|
let use_color = engine_state.get_config().use_ansi_coloring;
|
||||||
|
|
||||||
// Guard against invocation without a connected terminal.
|
// Guard against invocation without a connected terminal.
|
||||||
// reedline / crossterm event polling will fail without a connected tty
|
// reedline / crossterm event polling will fail without a connected tty
|
||||||
@ -56,18 +61,20 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
@ -77,33 +84,32 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||||
|
|
||||||
info!(
|
let mut start_time = std::time::Instant::now();
|
||||||
"load config initially {}:{}:{}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
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(
|
||||||
@ -118,7 +124,16 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
@ -126,61 +141,113 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
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::empty(),
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
|
||||||
|
|
||||||
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(),
|
||||||
@ -188,10 +255,20 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
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({
|
||||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||||
@ -201,16 +278,31 @@ pub fn evaluate_repl(
|
|||||||
} else {
|
} else {
|
||||||
line_editor.disable_hints()
|
line_editor.disable_hints()
|
||||||
};
|
};
|
||||||
|
perf(
|
||||||
|
"reedline coloring/style_computer",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -231,17 +323,31 @@ pub fn evaluate_repl(
|
|||||||
} else {
|
} else {
|
||||||
line_editor
|
line_editor
|
||||||
};
|
};
|
||||||
|
perf(
|
||||||
|
"reedline buffer_editor",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
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 {
|
||||||
@ -263,9 +369,16 @@ pub fn evaluate_repl(
|
|||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
perf(
|
||||||
|
"keybindings",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
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() {
|
||||||
@ -273,7 +386,16 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
@ -282,19 +404,40 @@ 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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
let config = engine_state.get_config();
|
start_time = std::time::Instant::now();
|
||||||
|
let config = &engine_state.get_config().clone();
|
||||||
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!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
info!(
|
if entry_num == 1 {
|
||||||
"finished setup, starting repl {}:{}:{}",
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||||
file!(),
|
if show_banner {
|
||||||
line!(),
|
println!(
|
||||||
column!()
|
"Startup Time: {}",
|
||||||
|
format_duration(engine_state.get_startup_time())
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
@ -421,8 +564,9 @@ 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::empty(),
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let cmd_duration = start_time.elapsed();
|
let cmd_duration = start_time.elapsed();
|
||||||
@ -479,7 +623,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)?;
|
||||||
}
|
}
|
||||||
@ -519,7 +663,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") {
|
||||||
eprintln!("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
|
||||||
@ -530,11 +674,36 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
"processing line editor input",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
perf(
|
||||||
|
"finished repl loop",
|
||||||
|
loop_start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -554,19 +723,12 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
|||||||
|
|
||||||
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
|
Please join our {}Discord{} community at {}https://discord.gg/NtAbbGn{}
|
||||||
Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
||||||
Our {}Documentation{} is located at {}http://nushell.sh{}
|
Our {}Documentation{} is located at {}https://nushell.sh{}
|
||||||
{}Tweet{} us at {}@nu_shell{}
|
{}Tweet{} us at {}@nu_shell{}
|
||||||
|
Learn how to remove this at: {}https://nushell.sh/book/configuration.html#remove-welcome-message{}
|
||||||
|
|
||||||
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
|
||||||
@ -595,13 +757,11 @@ let-env config = {{
|
|||||||
"\x1b[0m", //after Tweet
|
"\x1b[0m", //after Tweet
|
||||||
"\x1b[1;36m", //before @nu_shell cyan_bold
|
"\x1b[1;36m", //before @nu_shell cyan_bold
|
||||||
"\x1b[0m", //after @nu_shell
|
"\x1b[0m", //after @nu_shell
|
||||||
|
"\x1b[32m", //before Welcome Message
|
||||||
|
"\x1b[0m", //after Welcome Message
|
||||||
"\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[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
|
"\x1b[0m", //after banner disable
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -639,7 +799,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 {
|
||||||
@ -689,10 +849,10 @@ pub fn eval_env_change_hook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
x => {
|
x => {
|
||||||
return Err(ShellError::TypeMismatch(
|
return Err(ShellError::TypeMismatch {
|
||||||
"record for the 'env_change' hook".to_string(),
|
err_message: "record for the 'env_change' hook".to_string(),
|
||||||
x.span()?,
|
span: x.span()?,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -734,8 +894,11 @@ 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
|
||||||
|
.clone()
|
||||||
|
.follow_cell_path(&[condition_path], false, false)
|
||||||
|
{
|
||||||
match condition {
|
match condition {
|
||||||
Value::Block {
|
Value::Block {
|
||||||
val: block_id,
|
val: block_id,
|
||||||
@ -787,7 +950,7 @@ pub fn eval_hook(
|
|||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
@ -949,19 +1112,19 @@ fn run_hook_block(
|
|||||||
if let Some(arg) = arguments.get(idx) {
|
if let Some(arg) = arguments.get(idx) {
|
||||||
callee_stack.add_var(*var_id, arg.1.clone())
|
callee_stack.add_var(*var_id, arg.1.clone())
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::IncompatibleParametersSingle(
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
"This hook block has too many parameters".into(),
|
msg: "This hook block has too many parameters".into(),
|
||||||
span,
|
span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)
|
let pipeline_data =
|
||||||
{
|
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
||||||
Ok(pipeline_data) => {
|
|
||||||
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
||||||
return Err(error);
|
return Err(*error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all went fine, preserve the environment of the called block
|
// If all went fine, preserve the environment of the called block
|
||||||
@ -981,23 +1144,17 @@ fn run_hook_block(
|
|||||||
}
|
}
|
||||||
Ok(pipeline_data)
|
Ok(pipeline_data)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"Error writing ansi sequence".into(),
|
"Error writing ansi sequence".into(),
|
||||||
err.to_string(),
|
e.to_string(),
|
||||||
Some(Span::unknown()),
|
Some(Span::unknown()),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
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(),
|
||||||
|
@ -6,9 +6,10 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +234,8 @@ fn find_matching_block_end_in_block(
|
|||||||
PipelineElement::Expression(_, e)
|
PipelineElement::Expression(_, e)
|
||||||
| PipelineElement::Redirection(_, _, e)
|
| PipelineElement::Redirection(_, _, e)
|
||||||
| PipelineElement::And(_, e)
|
| PipelineElement::And(_, e)
|
||||||
| PipelineElement::Or(_, e) => {
|
| PipelineElement::Or(_, e)
|
||||||
|
| PipelineElement::SeparateRedirection { out: (_, e), .. } => {
|
||||||
if e.span.contains(global_cursor_offset) {
|
if e.span.contains(global_cursor_offset) {
|
||||||
if let Some(pos) = find_matching_block_end_in_expr(
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
line,
|
line,
|
||||||
@ -352,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 {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::repl::eval_hook;
|
use crate::repl::eval_hook;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::CliError;
|
use nu_protocol::CliError;
|
||||||
@ -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(),
|
||||||
),
|
),
|
||||||
@ -203,7 +203,10 @@ pub fn eval_source(
|
|||||||
source: &[u8],
|
source: &[u8],
|
||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
|
allow_return: bool,
|
||||||
) -> 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(
|
||||||
@ -228,7 +231,13 @@ pub fn eval_source(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
let b = if allow_return {
|
||||||
|
eval_block_with_early_return(engine_state, stack, &block, input, false, false)
|
||||||
|
} else {
|
||||||
|
eval_block(engine_state, stack, &block, input, false, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
match b {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let result;
|
let result;
|
||||||
@ -282,6 +291,14 @@ pub fn eval_source(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
perf(
|
||||||
|
&format!("eval_source {}", &fname),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
engine_state.get_config().use_ansi_coloring,
|
||||||
|
);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -315,27 +332,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,
|
|
||||||
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);
|
||||||
get_init_cwd()
|
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 {
|
||||||
|
@ -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,20 +169,20 @@ 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
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
folder(dir.join("another")),
|
||||||
|
file(dir.join("custom_completion.nu")),
|
||||||
file(dir.join("nushell")),
|
file(dir.join("nushell")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
folder(dir.join("another")),
|
|
||||||
file(dir.join("custom_completion.nu")),
|
|
||||||
file(dir.join(".hidden_file")),
|
file(dir.join(".hidden_file")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
];
|
];
|
||||||
@ -192,21 +212,21 @@ fn command_ls_with_filecompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -224,21 +244,21 @@ fn command_open_with_filecompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -257,21 +277,21 @@ fn command_rm_with_globcompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -290,21 +310,21 @@ fn command_cp_with_globcompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -323,21 +343,21 @@ fn command_save_with_filecompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -356,21 +376,21 @@ fn command_touch_with_filecompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -389,21 +409,21 @@ fn command_watch_with_filecompletion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -424,6 +444,7 @@ fn file_completion_quoted() {
|
|||||||
"`te st.txt`".to_string(),
|
"`te st.txt`".to_string(),
|
||||||
"`te#st.txt`".to_string(),
|
"`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)
|
match_suggestions(expected_paths, suggestions)
|
||||||
@ -434,12 +455,12 @@ 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(),
|
||||||
@ -448,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(),
|
||||||
@ -455,6 +477,7 @@ fn flag_completions() {
|
|||||||
"-f".into(),
|
"-f".into(),
|
||||||
"-h".into(),
|
"-h".into(),
|
||||||
"-l".into(),
|
"-l".into(),
|
||||||
|
"-m".into(),
|
||||||
"-s".into(),
|
"-s".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -467,18 +490,18 @@ 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
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
folder(dir.join("another")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
folder(dir.join("another")),
|
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -495,23 +518,26 @@ 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
|
||||||
let suggestions = completer.complete("$nu.", 4);
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
assert_eq!(9, suggestions.len());
|
assert_eq!(12, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
"config-path".into(),
|
"config-path".into(),
|
||||||
"env-path".into(),
|
"env-path".into(),
|
||||||
"history-path".into(),
|
"history-path".into(),
|
||||||
"home-path".into(),
|
"home-path".into(),
|
||||||
|
"is-interactive".into(),
|
||||||
|
"is-login".into(),
|
||||||
"loginshell-path".into(),
|
"loginshell-path".into(),
|
||||||
"os-info".into(),
|
"os-info".into(),
|
||||||
"pid".into(),
|
"pid".into(),
|
||||||
"scope".into(),
|
"scope".into(),
|
||||||
|
"startup-time".into(),
|
||||||
"temp-path".into(),
|
"temp-path".into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -551,9 +577,12 @@ fn variables_completions() {
|
|||||||
// Test completions for $env
|
// Test completions for $env
|
||||||
let suggestions = completer.complete("$env.", 5);
|
let suggestions = completer.complete("$env.", 5);
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
#[cfg(windows)]
|
||||||
|
let expected: Vec<String> = vec!["PWD".into(), "Path".into(), "TEST".into()];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
@ -652,7 +681,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())
|
||||||
@ -669,21 +698,21 @@ fn unknown_command_completion() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
@ -729,24 +758,113 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
"another\\".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder\\".to_string(),
|
".hidden_folder\\".to_string(),
|
||||||
];
|
];
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
"another/".to_string(),
|
|
||||||
"custom_completion.nu".to_string(),
|
|
||||||
".hidden_file".to_string(),
|
".hidden_file".to_string(),
|
||||||
".hidden_folder/".to_string(),
|
".hidden_folder/".to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore = "was reverted, still needs fixing"]
|
||||||
|
#[rstest]
|
||||||
|
fn alias_offset_bug_7648() {
|
||||||
|
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 #7648
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore = "was reverted, still needs fixing"]
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_path_env_var_8003() {
|
||||||
|
// Create a new engine
|
||||||
|
let (_, _, engine, _) = new_engine();
|
||||||
|
// Get the path env var in a platform agnostic way
|
||||||
|
let the_path = engine.get_path_env_var();
|
||||||
|
// Make sure it's not empty
|
||||||
|
assert!(the_path.is_some());
|
||||||
|
}
|
||||||
|
@ -43,6 +43,22 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
#[cfg(windows)]
|
||||||
|
stack.add_env_var(
|
||||||
|
"Path".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: "c:\\some\\path;c:\\some\\other\\path".to_string(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
stack.add_env_var(
|
||||||
|
"PATH".to_string(),
|
||||||
|
Value::String {
|
||||||
|
val: "/some/path:/some/other/path".to_string(),
|
||||||
|
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
|
32
crates/nu-cmd-lang/Cargo.toml
Normal file
32
crates/nu-cmd-lang/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nushell Project Developers"]
|
||||||
|
build = "build.rs"
|
||||||
|
description = "Nushell's core language commands"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-cmd-lang"
|
||||||
|
version = "0.77.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-color-config = { path = "../nu-color-config", version = "0.77.0" }
|
||||||
|
nu-engine = { path = "../nu-engine", version = "0.77.0" }
|
||||||
|
nu-parser = { path = "../nu-parser", version = "0.77.0" }
|
||||||
|
nu-protocol = { path = "../nu-protocol", version = "0.77.0" }
|
||||||
|
nu-utils = { path = "../nu-utils", version = "0.77.0" }
|
||||||
|
|
||||||
|
nu-ansi-term = "0.47.0"
|
||||||
|
|
||||||
|
fancy-regex = "0.11.0"
|
||||||
|
itertools = "0.10.0"
|
||||||
|
log = "0.4.14"
|
||||||
|
shadow-rs = { version = "0.21.0", default-features = false }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
shadow-rs = { version = "0.21.0", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path="../nu-test-support", version = "0.77.0" }
|
21
crates/nu-cmd-lang/LICENSE
Normal file
21
crates/nu-cmd-lang/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -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()
|
||||||
}
|
}
|
61
crates/nu-cmd-lang/src/core_commands/alias.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/alias.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Alias;
|
||||||
|
|
||||||
|
impl Command for Alias {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"alias"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Alias a command (with optional flags) to a new name."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("alias")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.required("name", SyntaxShape::String, "name of the alias")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||||
|
"equals sign followed by 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!["abbr", "aka", "fn", "func", "function"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Alias ll to ls -l",
|
||||||
|
example: "alias ll = ls -l",
|
||||||
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ impl Command for Break {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Break a loop"
|
"Break a loop."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -35,7 +35,7 @@ impl Command for Break {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Err(ShellError::Break(call.head))
|
Err(ShellError::Break(call.head))
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ impl Command for Commandline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"View or modify the current command line input buffer"
|
"View or modify the current command line input buffer."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
104
crates/nu-cmd-lang/src/core_commands/const_.rs
Normal file
104
crates/nu-cmd-lang/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<PipelineData, 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 {
|
||||||
|
msg: "Missing Constant".to_string(),
|
||||||
|
label: "constant not added by the parser".to_string(),
|
||||||
|
span: 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));
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ impl Command for Continue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Continue a loop from the next iteration"
|
"Continue a loop from the next iteration."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -35,7 +35,7 @@ impl Command for Continue {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Err(ShellError::Continue(call.head))
|
Err(ShellError::Continue(call.head))
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Def;
|
pub struct Def;
|
||||||
@ -11,7 +13,7 @@ impl Command for Def {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a custom command"
|
"Define a custom command."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -38,7 +40,7 @@ impl Command for Def {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DefEnv;
|
pub struct DefEnv;
|
||||||
@ -11,7 +13,7 @@ impl Command for DefEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a custom command, which participates in the caller environment"
|
"Define a custom command, which participates in the caller environment."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -26,32 +28,7 @@ impl Command for DefEnv {
|
|||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
"#
|
||||||
=== EXTRA NOTE ===
|
|
||||||
All blocks are scoped, including variable definition and environment variable changes.
|
|
||||||
|
|
||||||
Because of this, the following doesn't work:
|
|
||||||
|
|
||||||
def-env cd_with_fallback [arg = ""] {
|
|
||||||
let fall_back_path = "/tmp"
|
|
||||||
if $arg != "" {
|
|
||||||
cd $arg
|
|
||||||
} else {
|
|
||||||
cd $fall_back_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Instead, you have to use cd in the top level scope:
|
|
||||||
|
|
||||||
def-env cd_with_fallback [arg = ""] {
|
|
||||||
let fall_back_path = "/tmp"
|
|
||||||
let path = if $arg != "" {
|
|
||||||
$arg
|
|
||||||
} else {
|
|
||||||
$fall_back_path
|
|
||||||
}
|
|
||||||
cd $path
|
|
||||||
}"#
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -64,7 +41,7 @@ def-env cd_with_fallback [arg = ""] {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +49,7 @@ def-env cd_with_fallback [arg = ""] {
|
|||||||
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("BAZ", Span::test_data())),
|
result: Some(Value::test_string("BAZ")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Describe;
|
||||||
|
|
||||||
|
impl Command for Describe {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"describe"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Describe the type and structure of the value(s) piped in."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
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(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
|
||||||
|
let no_collect: bool = call.has_flag("no-collect");
|
||||||
|
|
||||||
|
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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
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> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Describe the type of a 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> {
|
||||||
|
vec!["type", "typeof", "info", "structure"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::Describe;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(Describe {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
|
use std::thread;
|
||||||
|
|
||||||
use nu_engine::{eval_block_with_early_return, CallExt};
|
use nu_engine::{eval_block_with_early_return, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,7 +17,7 @@ impl Command for Do {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Run a closure, providing it with the pipeline input"
|
"Run a closure, providing it with the pipeline input."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -106,7 +109,7 @@ impl Command for Do {
|
|||||||
block,
|
block,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
capture_errors || ignore_shell_errors || ignore_program_errors,
|
call.redirect_stdout,
|
||||||
);
|
);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -118,6 +121,59 @@ impl Command for Do {
|
|||||||
metadata,
|
metadata,
|
||||||
trim_end_newline,
|
trim_end_newline,
|
||||||
}) if capture_errors => {
|
}) if capture_errors => {
|
||||||
|
// Use a thread to receive stdout message.
|
||||||
|
// Or we may get a deadlock if child process sends out too much bytes to stderr.
|
||||||
|
//
|
||||||
|
// For example: in normal linux system, stderr pipe's limit is 65535 bytes.
|
||||||
|
// if child process sends out 65536 bytes, the process will be hanged because no consumer
|
||||||
|
// consumes the first 65535 bytes
|
||||||
|
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
||||||
|
// stderr messages.
|
||||||
|
let stdout_handler = stdout.map(|stdout_stream| {
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("stderr redirector".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let ctrlc = stdout_stream.ctrlc.clone();
|
||||||
|
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")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
||||||
|
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
||||||
|
let mut stderr_ctrlc = None;
|
||||||
|
let stderr_msg = match stderr {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(stderr_stream) => {
|
||||||
|
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||||
|
stderr_stream.into_string().map(|s| s.item)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = if let Some(handle) = stdout_handler {
|
||||||
|
match handle.join() {
|
||||||
|
Err(err) => {
|
||||||
|
return Err(ShellError::ExternalCommand {
|
||||||
|
label: "Fail to receive external commands stdout message"
|
||||||
|
.to_string(),
|
||||||
|
help: format!("{err:?}"),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mut exit_code_ctrlc = None;
|
let mut exit_code_ctrlc = None;
|
||||||
let exit_code: Vec<Value> = match exit_code {
|
let exit_code: Vec<Value> = match exit_code {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
@ -128,22 +184,22 @@ impl Command for Do {
|
|||||||
};
|
};
|
||||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||||
if *code != 0 {
|
if *code != 0 {
|
||||||
let stderr_msg = match stderr {
|
return Err(ShellError::ExternalCommand {
|
||||||
None => "".to_string(),
|
label: "External command failed".to_string(),
|
||||||
Some(stderr_stream) => stderr_stream.into_string().map(|s| s.item)?,
|
help: stderr_msg,
|
||||||
};
|
|
||||||
|
|
||||||
return Err(ShellError::ExternalCommand(
|
|
||||||
"External command failed".to_string(),
|
|
||||||
stderr_msg,
|
|
||||||
span,
|
span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::ExternalStream {
|
Ok(PipelineData::ExternalStream {
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
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: Some(ListStream::from_stream(
|
||||||
exit_code.into_iter(),
|
exit_code.into_iter(),
|
||||||
exit_code_ctrlc,
|
exit_code_ctrlc,
|
||||||
@ -160,18 +216,35 @@ impl Command for Do {
|
|||||||
span,
|
span,
|
||||||
metadata,
|
metadata,
|
||||||
trim_end_newline,
|
trim_end_newline,
|
||||||
}) if ignore_program_errors => Ok(PipelineData::ExternalStream {
|
}) if ignore_program_errors && !call.redirect_stdout => {
|
||||||
|
Ok(PipelineData::ExternalStream {
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
exit_code: None,
|
exit_code: None,
|
||||||
span,
|
span,
|
||||||
metadata,
|
metadata,
|
||||||
trim_end_newline,
|
trim_end_newline,
|
||||||
}),
|
})
|
||||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) if ignore_shell_errors => {
|
}
|
||||||
|
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
Err(_) if ignore_shell_errors => Ok(PipelineData::empty()),
|
Ok(PipelineData::ListStream(ls, metadata)) if ignore_shell_errors => {
|
||||||
|
// check if there is a `Value::Error` in given list stream first.
|
||||||
|
let mut values = vec![];
|
||||||
|
let ctrlc = ls.ctrlc.clone();
|
||||||
|
for v in ls {
|
||||||
|
if let Value::Error { .. } = v {
|
||||||
|
values.push(Value::nothing(call.head));
|
||||||
|
} else {
|
||||||
|
values.push(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(PipelineData::ListStream(
|
||||||
|
ListStream::from_stream(values.into_iter(), ctrlc),
|
||||||
|
metadata,
|
||||||
|
))
|
||||||
|
}
|
||||||
r => r,
|
r => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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",
|
||||||
@ -38,7 +39,7 @@ impl Command for ErrorMake {
|
|||||||
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 span = call.head;
|
let span = call.head;
|
||||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||||
let unspanned = call.has_flag("unspanned");
|
let unspanned = call.has_flag("unspanned");
|
@ -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, Example, IntoPipelineData, PipelineData, Signature, Span, Type, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -38,7 +38,7 @@ impl Command for ExportCommand {
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(
|
val: get_full_help(
|
||||||
&ExportCommand.signature(),
|
&ExportCommand.signature(),
|
||||||
@ -56,7 +56,7 @@ impl Command for ExportCommand {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Export a definition from a module",
|
description: "Export a definition from a module",
|
||||||
example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#,
|
example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#,
|
||||||
result: Some(Value::string("hello", Span::test_data())),
|
result: Some(Value::test_string("hello")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExportAlias;
|
||||||
|
|
||||||
|
impl Command for ExportAlias {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"export alias"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Alias a command (with optional flags) to a new name and export it from a module."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("export alias")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.required("name", SyntaxShape::String, "name of the alias")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||||
|
"equals sign followed by 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!["abbr", "aka", "fn", "func", "function"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Alias ll to ls -l and export it from a module",
|
||||||
|
example: "module spam { export alias ll = ls -l }",
|
||||||
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportDef;
|
pub struct ExportDef;
|
||||||
@ -11,7 +13,7 @@ impl Command for ExportDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a custom command and export it from a module"
|
"Define a custom command and export it from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -38,7 +40,7 @@ impl Command for ExportDef {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ impl Command for ExportDef {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Define a custom command in a module and call it",
|
description: "Define a custom command in a module and call it",
|
||||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||||
result: Some(Value::string("foo", Span::test_data())),
|
result: Some(Value::test_string("foo")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportDefEnv;
|
pub struct ExportDefEnv;
|
||||||
@ -11,7 +13,7 @@ impl Command for ExportDefEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a custom command that participates in the environment and export it from a module"
|
"Define a custom command that participates in the environment and export it from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -64,7 +66,7 @@ export def-env cd_with_fallback [arg = ""] {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +74,7 @@ export def-env cd_with_fallback [arg = ""] {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Define a custom command that participates in the environment in a module and call it",
|
description: "Define a custom command that participates in the environment in a module and call it",
|
||||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||||
result: Some(Value::string("BAZ", Span::test_data())),
|
result: Some(Value::test_string("BAZ")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -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, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportExtern;
|
pub struct ExportExtern;
|
||||||
@ -11,7 +11,7 @@ impl Command for ExportExtern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define an extern and export it from a module"
|
"Define an extern and export it from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -37,7 +37,7 @@ impl Command for ExportExtern {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExportUse;
|
pub struct ExportUse;
|
||||||
@ -11,13 +13,18 @@ impl Command for ExportUse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Use definitions from a module and export them from this module"
|
"Use definitions from a module and export them from this module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("export use")
|
Signature::build("export use")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
.required("module", SyntaxShape::String, "Module or module file")
|
||||||
|
.optional(
|
||||||
|
"members",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"Which members of the module to import",
|
||||||
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +43,7 @@ impl Command for ExportUse {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +55,7 @@ impl Command for ExportUse {
|
|||||||
use eggs foo
|
use eggs foo
|
||||||
foo
|
foo
|
||||||
"#,
|
"#,
|
||||||
result: Some(Value::string("foo", Span::test_data())),
|
result: Some(Value::test_string("foo")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -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, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Extern;
|
pub struct Extern;
|
||||||
@ -11,7 +11,7 @@ impl Command for Extern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a signature for an external command"
|
"Define a signature for an external command."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -37,7 +37,7 @@ impl Command for Extern {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use nu_engine::{eval_block, eval_expression, CallExt};
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,11 +14,13 @@ impl Command for For {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Loop over a range"
|
"Loop over a range."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("for")
|
Signature::build("for")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required(
|
.required(
|
||||||
"var_name",
|
"var_name",
|
||||||
SyntaxShape::VarWithOptType,
|
SyntaxShape::VarWithOptType,
|
||||||
@ -32,7 +34,7 @@ impl Command for For {
|
|||||||
.required("block", SyntaxShape::Block, "the block to run")
|
.required("block", SyntaxShape::Block, "the block to run")
|
||||||
.switch(
|
.switch(
|
||||||
"numbered",
|
"numbered",
|
||||||
"returned a numbered item ($it.index and $it.item)",
|
"return a numbered item ($it.index and $it.item)",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
.creates_scope()
|
.creates_scope()
|
||||||
@ -54,7 +56,7 @@ impl Command for For {
|
|||||||
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 head = call.head;
|
let head = call.head;
|
||||||
let var_id = call
|
let var_id = call
|
||||||
.positional_nth(0)
|
.positional_nth(0)
|
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
use crate::help_aliases::help_aliases;
|
||||||
|
use crate::help_commands::help_commands;
|
||||||
|
use crate::help_modules::help_modules;
|
||||||
|
use fancy_regex::Regex;
|
||||||
|
use nu_ansi_term::{
|
||||||
|
Color::{Red, White},
|
||||||
|
Style,
|
||||||
|
};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Help;
|
||||||
|
|
||||||
|
impl Command for Help {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of command, alias or module to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in command names, usage, and search terms",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Display help information about different parts of Nushell."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"`help word` searches for "word" in commands, aliases and modules, in that order."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
if rest.is_empty() && find.is_none() {
|
||||||
|
let msg = r#"Welcome to Nushell.
|
||||||
|
|
||||||
|
Here are some tips to help you get started.
|
||||||
|
* help -h or help help - show available `help` subcommands and examples
|
||||||
|
* help commands - list all available commands
|
||||||
|
* help <name> - display help about a particular command, alias, or module
|
||||||
|
* help --find <text to search> - search through all help commands table
|
||||||
|
|
||||||
|
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||||
|
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||||
|
|
||||||
|
[Examples]
|
||||||
|
|
||||||
|
List the files in the current directory, sorted by size:
|
||||||
|
ls | sort-by size
|
||||||
|
|
||||||
|
Get information about the current system:
|
||||||
|
sys | get host
|
||||||
|
|
||||||
|
Get the processes on your system actively using CPU:
|
||||||
|
ps | where cpu > 0
|
||||||
|
|
||||||
|
You can also learn more at https://www.nushell.sh/book/"#;
|
||||||
|
|
||||||
|
Ok(Value::string(msg, head).into_pipeline_data())
|
||||||
|
} else if find.is_some() {
|
||||||
|
help_commands(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
let result = help_aliases(engine_state, stack, call);
|
||||||
|
|
||||||
|
let result = if let Err(ShellError::AliasNotFound(_)) = result {
|
||||||
|
help_commands(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
||||||
|
help_modules(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(ShellError::ModuleNotFoundAtRuntime {
|
||||||
|
mod_name: _,
|
||||||
|
span: _,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
let rest_spans: Vec<Span> = rest.iter().map(|arg| arg.span).collect();
|
||||||
|
Err(ShellError::NotFound {
|
||||||
|
span: span(&rest_spans),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show help for single command, alias, or module",
|
||||||
|
example: "help match",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single sub-command, alias, or module",
|
||||||
|
example: "help str lpad",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in command names, usage and search terms",
|
||||||
|
example: "help --find char",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highlight_search_in_table(
|
||||||
|
table: Vec<Value>, // list of records
|
||||||
|
search_string: &str,
|
||||||
|
searched_cols: &[&str],
|
||||||
|
string_style: &Style,
|
||||||
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
|
let orig_search_string = search_string;
|
||||||
|
let search_string = search_string.to_lowercase();
|
||||||
|
let mut matches = vec![];
|
||||||
|
|
||||||
|
for record in table {
|
||||||
|
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
|
||||||
|
(cols, vals, span)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::NushellFailedSpanned {
|
||||||
|
msg: "Expected record".to_string(),
|
||||||
|
label: format!("got {}", record.get_type()),
|
||||||
|
span: record.span()?,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let has_match = cols.iter().zip(vals.iter_mut()).fold(
|
||||||
|
Ok(false),
|
||||||
|
|acc: Result<bool, ShellError>, (col, val)| {
|
||||||
|
if searched_cols.contains(&col.as_str()) {
|
||||||
|
if let Value::String { val: s, span } = val {
|
||||||
|
if s.to_lowercase().contains(&search_string) {
|
||||||
|
*val = Value::String {
|
||||||
|
val: highlight_search_string(s, orig_search_string, string_style)?,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// column does not contain the searched string
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ignore non-string values
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't search this column
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if has_match {
|
||||||
|
matches.push(Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: record_span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||||
|
pub fn highlight_search_string(
|
||||||
|
haystack: &str,
|
||||||
|
needle: &str,
|
||||||
|
string_style: &Style,
|
||||||
|
) -> Result<String, ShellError> {
|
||||||
|
let regex_string = format!("(?i){needle}");
|
||||||
|
let regex = match Regex::new(®ex_string) {
|
||||||
|
Ok(regex) => regex,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(ShellError::GenericError(
|
||||||
|
"Could not compile regex".into(),
|
||||||
|
err.to_string(),
|
||||||
|
Some(Span::test_data()),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// strip haystack to remove existing ansi style
|
||||||
|
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
|
||||||
|
let mut last_match_end = 0;
|
||||||
|
let style = Style::new().fg(White).on(Red);
|
||||||
|
let mut highlighted = String::new();
|
||||||
|
|
||||||
|
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
||||||
|
match cap {
|
||||||
|
Ok(capture) => {
|
||||||
|
let start = match capture.get(0) {
|
||||||
|
Some(acap) => acap.start(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
let end = match capture.get(0) {
|
||||||
|
Some(acap) => acap.end(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
highlighted.push_str(
|
||||||
|
&string_style
|
||||||
|
.paint(&stripped_haystack[last_match_end..start])
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||||
|
last_match_end = end;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ShellError::GenericError(
|
||||||
|
"Error with regular expression capture".into(),
|
||||||
|
e.to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
highlighted.push_str(
|
||||||
|
&string_style
|
||||||
|
.paint(&stripped_haystack[last_match_end..])
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
Ok(highlighted)
|
||||||
|
}
|
181
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
181
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{scope::ScopeData, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||||
|
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpAliases;
|
||||||
|
|
||||||
|
impl Command for HelpAliases {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help aliases"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell aliases."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help aliases")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of alias to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in alias names and usage",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show all aliases",
|
||||||
|
example: "help aliases",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single alias",
|
||||||
|
example: "help aliases my-alias",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in alias names and usages",
|
||||||
|
example: "help aliases --find my-alias",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_aliases(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_aliases(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||||
|
let found_cmds_vec =
|
||||||
|
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alias_id = if let Some(id) = engine_state.find_alias(name.as_bytes(), &[]) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::AliasNotFound(span(
|
||||||
|
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let alias_expansion = engine_state
|
||||||
|
.get_alias(alias_id)
|
||||||
|
.iter()
|
||||||
|
.map(|span| String::from_utf8_lossy(engine_state.get_span_contents(span)))
|
||||||
|
.collect::<Vec<Cow<str>>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let alias_usage = engine_state.build_alias_usage(alias_id);
|
||||||
|
|
||||||
|
// TODO: merge this into documentation.rs at some point
|
||||||
|
const G: &str = "\x1b[32m"; // green
|
||||||
|
const C: &str = "\x1b[36m"; // cyan
|
||||||
|
const RESET: &str = "\x1b[0m"; // reset
|
||||||
|
|
||||||
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
|
if let Some((usage, extra_usage)) = alias_usage {
|
||||||
|
long_desc.push_str(&usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !extra_usage.is_empty() {
|
||||||
|
long_desc.push_str(&extra_usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
||||||
|
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
if !config.use_ansi_coloring {
|
||||||
|
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: long_desc,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||||
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
|
scope_data.populate_aliases();
|
||||||
|
|
||||||
|
scope_data.collect_aliases(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpAliases;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpAliases {})
|
||||||
|
}
|
||||||
|
}
|
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{get_full_help, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||||
|
Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpCommands;
|
||||||
|
|
||||||
|
impl Command for HelpCommands {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help commands"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell commands."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help commands")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of command to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in command names, usage, and search terms",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_commands(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_commands(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||||
|
let found_cmds_vec = highlight_search_in_table(
|
||||||
|
all_cmds_vec,
|
||||||
|
&f.item,
|
||||||
|
&["name", "usage", "search_terms"],
|
||||||
|
&string_style,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_commands(engine_state, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = engine_state
|
||||||
|
.get_signatures_with_examples(false)
|
||||||
|
.iter()
|
||||||
|
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||||
|
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||||
|
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: output.join("======================\n\n"),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::CommandNotFound(span(&[
|
||||||
|
rest[0].span,
|
||||||
|
rest[rest.len() - 1].span,
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||||
|
let commands = engine_state.get_decls_sorted(false);
|
||||||
|
let mut found_cmds_vec = Vec::new();
|
||||||
|
|
||||||
|
for (name_bytes, decl_id) in commands {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||||
|
let decl = engine_state.get_decl(decl_id);
|
||||||
|
let sig = decl.signature().update_from_command(name, decl.borrow());
|
||||||
|
|
||||||
|
let signatures = sig.to_string().trim_start().replace("\n ", "\n");
|
||||||
|
let key = sig.name;
|
||||||
|
let usage = sig.usage;
|
||||||
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
|
cols.push("name".into());
|
||||||
|
vals.push(Value::String { val: key, span });
|
||||||
|
|
||||||
|
cols.push("category".into());
|
||||||
|
vals.push(Value::string(sig.category.to_string(), span));
|
||||||
|
|
||||||
|
cols.push("command_type".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("usage".into());
|
||||||
|
vals.push(Value::String { val: usage, span });
|
||||||
|
|
||||||
|
cols.push("signatures".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: if decl.is_parser_keyword() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
signatures
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("search_terms".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: search_terms.join(", "),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||||
|
}
|
||||||
|
|
||||||
|
found_cmds_vec
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpCommands;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpCommands {})
|
||||||
|
}
|
||||||
|
}
|
254
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
254
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{scope::ScopeData, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, AliasId, Category, DeclId, Example, IntoInterruptiblePipelineData, IntoPipelineData,
|
||||||
|
PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpModules;
|
||||||
|
|
||||||
|
impl Command for HelpModules {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help modules"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell modules."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"When requesting help for a single module, its commands and aliases will be highlighted if they
|
||||||
|
are also available in the current scope. Commands/aliases that were imported under a different name
|
||||||
|
(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help modules")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of module to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in module names and usage",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show all modules",
|
||||||
|
example: "help modules",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single module",
|
||||||
|
example: "help modules my-module",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in module names and usages",
|
||||||
|
example: "help modules --find my-module",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_modules(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_modules(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||||
|
let found_cmds_vec =
|
||||||
|
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_id = if let Some(id) = engine_state.find_module(name.as_bytes(), &[]) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::ModuleNotFoundAtRuntime {
|
||||||
|
mod_name: name,
|
||||||
|
span: span(&rest.iter().map(|r| r.span).collect::<Vec<Span>>()),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = engine_state.get_module(module_id);
|
||||||
|
|
||||||
|
let module_usage = engine_state.build_module_usage(module_id);
|
||||||
|
|
||||||
|
// TODO: merge this into documentation.rs at some point
|
||||||
|
const G: &str = "\x1b[32m"; // green
|
||||||
|
const C: &str = "\x1b[36m"; // cyan
|
||||||
|
const CB: &str = "\x1b[1;36m"; // cyan bold
|
||||||
|
const RESET: &str = "\x1b[0m"; // reset
|
||||||
|
|
||||||
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
|
if let Some((usage, extra_usage)) = module_usage {
|
||||||
|
long_desc.push_str(&usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !extra_usage.is_empty() {
|
||||||
|
long_desc.push_str(&extra_usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !module.decls.is_empty() || module.main.is_some() {
|
||||||
|
let commands: Vec<(Vec<u8>, DeclId)> = engine_state.get_decls_sorted(false).collect();
|
||||||
|
|
||||||
|
let mut module_commands = module.decls();
|
||||||
|
module_commands.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
|
let commands_str = module_commands
|
||||||
|
.iter()
|
||||||
|
.map(|(name_bytes, id)| {
|
||||||
|
let name = String::from_utf8_lossy(name_bytes);
|
||||||
|
if let Some((used_name_bytes, _)) =
|
||||||
|
commands.iter().find(|(_, decl_id)| id == decl_id)
|
||||||
|
{
|
||||||
|
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||||
|
format!("{CB}{name}{RESET}")
|
||||||
|
} else {
|
||||||
|
let command_name = String::from_utf8_lossy(used_name_bytes);
|
||||||
|
format!("{name} ({CB}{command_name}{RESET})")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{name}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !module.aliases.is_empty() {
|
||||||
|
let aliases: Vec<(Vec<u8>, AliasId)> = engine_state.get_aliases_sorted(false).collect();
|
||||||
|
|
||||||
|
let mut module_aliases: Vec<(&[u8], AliasId)> = module
|
||||||
|
.aliases
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| (name.as_ref(), *id))
|
||||||
|
.collect();
|
||||||
|
module_aliases.sort_by(|a, b| a.0.cmp(b.0));
|
||||||
|
|
||||||
|
let aliases_str = module_aliases
|
||||||
|
.iter()
|
||||||
|
.map(|(name_bytes, id)| {
|
||||||
|
let name = String::from_utf8_lossy(name_bytes);
|
||||||
|
if let Some((used_name_bytes, _)) =
|
||||||
|
aliases.iter().find(|(_, alias_id)| id == alias_id)
|
||||||
|
{
|
||||||
|
if engine_state.find_alias(name.as_bytes(), &[]).is_some() {
|
||||||
|
format!("{CB}{name}{RESET}")
|
||||||
|
} else {
|
||||||
|
let alias_name = String::from_utf8_lossy(used_name_bytes);
|
||||||
|
format!("{name} ({CB}{alias_name}{RESET})")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{name}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if module.env_block.is_some() {
|
||||||
|
long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
|
||||||
|
} else {
|
||||||
|
long_desc.push_str(&format!(
|
||||||
|
"This module {C}does not export{RESET} environment."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
if !config.use_ansi_coloring {
|
||||||
|
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: long_desc,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||||
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
|
scope_data.populate_modules();
|
||||||
|
|
||||||
|
scope_data.collect_modules(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpModules;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpModules {})
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value,
|
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -17,7 +17,10 @@ impl Command for HelpOperators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("help operators").category(Category::Core)
|
Signature::build("help operators")
|
||||||
|
.category(Category::Core)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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 Hide;
|
||||||
|
|
||||||
|
impl Command for Hide {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hide"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("hide")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.required("module", SyntaxShape::String, "Module or module file")
|
||||||
|
.optional(
|
||||||
|
"members",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"Which members of the module to import",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Hide definitions in the current scope."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Definitions are hidden by priority: First aliases, then custom commands.
|
||||||
|
|
||||||
|
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<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Hide the alias just defined",
|
||||||
|
example: r#"alias lll = ls -l; hide lll"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Hide a custom command",
|
||||||
|
example: r#"def say-hi [] { echo 'Hi!' }; hide say-hi"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +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::{
|
||||||
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
|
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
SyntaxShape, Type, Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -31,7 +31,7 @@ impl Command for HideEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Hide environment variables in the current scope"
|
"Hide environment variables in the current scope."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -40,12 +40,12 @@ impl Command for HideEnv {
|
|||||||
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 env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
let ignore_errors = call.has_flag("ignore-errors");
|
let ignore_errors = call.has_flag("ignore-errors");
|
||||||
|
|
||||||
for name in env_var_names {
|
for name in env_var_names {
|
||||||
if stack.remove_env_var(engine_state, &name.item).is_none() && !ignore_errors {
|
if !stack.remove_env_var(engine_state, &name.item) && !ignore_errors {
|
||||||
let all_names: Vec<String> = stack
|
let all_names: Vec<String> = stack
|
||||||
.get_env_var_names(engine_state)
|
.get_env_var_names(engine_state)
|
||||||
.iter()
|
.iter()
|
||||||
@ -58,7 +58,10 @@ impl Command for HideEnv {
|
|||||||
name.span,
|
name.span,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span));
|
return Err(ShellError::EnvVarNotFoundAtRuntime {
|
||||||
|
envvar_name: name.item,
|
||||||
|
span: name.span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +73,7 @@ impl Command for HideEnv {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Hide an environment variable",
|
description: "Hide an environment variable",
|
||||||
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
||||||
result: Some(Value::boolean(false, Span::test_data())),
|
result: Some(Value::test_bool(false)),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -55,7 +55,7 @@ impl Command for If {
|
|||||||
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 cond = call.positional_nth(0).expect("checked through parser");
|
let cond = call.positional_nth(0).expect("checked through parser");
|
||||||
let then_block: Block = call.req(engine_state, stack, 1)?;
|
let then_block: Block = call.req(engine_state, stack, 1)?;
|
||||||
let else_case = call.positional_nth(2);
|
let else_case = call.positional_nth(2);
|
||||||
@ -111,12 +111,12 @@ impl Command for If {
|
|||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x => Err(ShellError::CantConvert(
|
x => Err(ShellError::CantConvert {
|
||||||
"bool".into(),
|
to_type: "bool".into(),
|
||||||
x.get_type().to_string(),
|
from_type: x.get_type().to_string(),
|
||||||
result.span()?,
|
span: result.span()?,
|
||||||
None,
|
help: 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, Signature, Span, Type, Value};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Ignore;
|
pub struct Ignore;
|
||||||
@ -11,7 +11,7 @@ impl Command for Ignore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Ignore the output of the previous command in the pipeline"
|
"Ignore the output of the previous command in the pipeline."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -30,7 +30,7 @@ impl Command for Ignore {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
input.into_value(call.head);
|
input.into_value(call.head);
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use nu_engine::eval_expression_with_input;
|
use nu_engine::eval_expression_with_input;
|
||||||
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, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Let;
|
pub struct Let;
|
||||||
@ -47,7 +47,7 @@ impl Command for Let {
|
|||||||
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 var_id = call
|
let var_id = call
|
||||||
.positional_nth(0)
|
.positional_nth(0)
|
||||||
.expect("checked through parser")
|
.expect("checked through parser")
|
||||||
@ -60,21 +60,23 @@ impl Command for Let {
|
|||||||
.as_keyword()
|
.as_keyword()
|
||||||
.expect("internal error: missing keyword");
|
.expect("internal error: missing keyword");
|
||||||
|
|
||||||
let rhs = eval_expression_with_input(
|
let (rhs, external_failed) = eval_expression_with_input(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
keyword_expr,
|
keyword_expr,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
call.redirect_stderr,
|
call.redirect_stderr,
|
||||||
)?
|
)?;
|
||||||
.0;
|
if external_failed {
|
||||||
|
// rhs must be a PipelineData::ExternalStream and it's failed
|
||||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
// return the failed stream (with a non-zero exit code) so the engine knows to stop running
|
||||||
|
Ok(rhs)
|
||||||
|
} else {
|
||||||
stack.add_var(var_id, rhs.into_value(call.head));
|
stack.add_var(var_id, rhs.into_value(call.head));
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
@ -2,7 +2,7 @@ use nu_engine::{eval_block, CallExt};
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -19,6 +19,8 @@ impl Command for Loop {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("loop")
|
Signature::build("loop")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("block", SyntaxShape::Block, "block to loop")
|
.required("block", SyntaxShape::Block, "block to loop")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ impl Command for Loop {
|
|||||||
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: Block = call.req(engine_state, stack, 0)?;
|
let block: Block = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -79,7 +81,7 @@ impl Command for Loop {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Loop while a condition is true",
|
description: "Loop while a condition is true",
|
||||||
example: "mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x",
|
example: "mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x",
|
||||||
result: Some(Value::int(11, Span::test_data())),
|
result: Some(Value::test_int(11)),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
mod alias;
|
mod alias;
|
||||||
mod ast;
|
|
||||||
mod break_;
|
mod break_;
|
||||||
mod commandline;
|
mod commandline;
|
||||||
|
mod const_;
|
||||||
mod continue_;
|
mod continue_;
|
||||||
mod debug;
|
|
||||||
mod def;
|
mod def;
|
||||||
mod def_env;
|
mod def_env;
|
||||||
mod describe;
|
mod describe;
|
||||||
@ -19,6 +18,9 @@ mod export_use;
|
|||||||
mod extern_;
|
mod extern_;
|
||||||
mod for_;
|
mod for_;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
|
pub mod help_aliases;
|
||||||
|
pub mod help_commands;
|
||||||
|
pub mod help_modules;
|
||||||
mod help_operators;
|
mod help_operators;
|
||||||
mod hide;
|
mod hide;
|
||||||
mod hide_env;
|
mod hide_env;
|
||||||
@ -26,7 +28,6 @@ mod if_;
|
|||||||
mod ignore;
|
mod ignore;
|
||||||
mod let_;
|
mod let_;
|
||||||
mod loop_;
|
mod loop_;
|
||||||
mod metadata;
|
|
||||||
mod module;
|
mod module;
|
||||||
mod mut_;
|
mod mut_;
|
||||||
pub(crate) mod overlay;
|
pub(crate) mod overlay;
|
||||||
@ -37,11 +38,10 @@ mod version;
|
|||||||
mod while_;
|
mod while_;
|
||||||
|
|
||||||
pub use alias::Alias;
|
pub use alias::Alias;
|
||||||
pub use ast::Ast;
|
|
||||||
pub use break_::Break;
|
pub use break_::Break;
|
||||||
pub use commandline::Commandline;
|
pub use commandline::Commandline;
|
||||||
|
pub use const_::Const;
|
||||||
pub use continue_::Continue;
|
pub use continue_::Continue;
|
||||||
pub use debug::Debug;
|
|
||||||
pub use def::Def;
|
pub use def::Def;
|
||||||
pub use def_env::DefEnv;
|
pub use def_env::DefEnv;
|
||||||
pub use describe::Describe;
|
pub use describe::Describe;
|
||||||
@ -57,6 +57,9 @@ pub use export_use::ExportUse;
|
|||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
pub use help::Help;
|
pub use help::Help;
|
||||||
|
pub use help_aliases::HelpAliases;
|
||||||
|
pub use help_commands::HelpCommands;
|
||||||
|
pub use help_modules::HelpModules;
|
||||||
pub use help_operators::HelpOperators;
|
pub use help_operators::HelpOperators;
|
||||||
pub use hide::Hide;
|
pub use hide::Hide;
|
||||||
pub use hide_env::HideEnv;
|
pub use hide_env::HideEnv;
|
||||||
@ -64,7 +67,6 @@ pub use if_::If;
|
|||||||
pub use ignore::Ignore;
|
pub use ignore::Ignore;
|
||||||
pub use let_::Let;
|
pub use let_::Let;
|
||||||
pub use loop_::Loop;
|
pub use loop_::Loop;
|
||||||
pub use metadata::Metadata;
|
|
||||||
pub use module::Module;
|
pub use module::Module;
|
||||||
pub use mut_::Mut;
|
pub use mut_::Mut;
|
||||||
pub use overlay::*;
|
pub use overlay::*;
|
||||||
@ -73,8 +75,8 @@ pub use try_::Try;
|
|||||||
pub use use_::Use;
|
pub use use_::Use;
|
||||||
pub use version::Version;
|
pub use version::Version;
|
||||||
pub use while_::While;
|
pub use while_::While;
|
||||||
#[cfg(feature = "plugin")]
|
//#[cfg(feature = "plugin")]
|
||||||
mod register;
|
mod register;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
//#[cfg(feature = "plugin")]
|
||||||
pub use register::Register;
|
pub use register::Register;
|
@ -1,6 +1,8 @@
|
|||||||
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, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Module;
|
pub struct Module;
|
||||||
@ -11,7 +13,7 @@ impl Command for Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Define a custom module"
|
"Define a custom module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -37,7 +39,7 @@ impl Command for Module {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,17 +48,17 @@ impl Command for Module {
|
|||||||
Example {
|
Example {
|
||||||
description: "Define a custom command in a module and call it",
|
description: "Define a custom command in a module and call it",
|
||||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||||
result: Some(Value::string("foo", Span::test_data())),
|
result: Some(Value::test_string("foo")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define an environment variable in a module",
|
description: "Define an environment variable in a module",
|
||||||
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||||
result: Some(Value::string("BAZ", Span::test_data())),
|
result: Some(Value::test_string("BAZ")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define a custom command that participates in the environment in a module and call it",
|
description: "Define a custom command that participates in the environment in a module and call it",
|
||||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||||
result: Some(Value::string("BAZ", Span::test_data())),
|
result: Some(Value::test_string("BAZ")),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use nu_engine::eval_expression_with_input;
|
use nu_engine::eval_expression_with_input;
|
||||||
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, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Mut;
|
pub struct Mut;
|
||||||
@ -47,7 +47,7 @@ impl Command for Mut {
|
|||||||
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 var_id = call
|
let var_id = call
|
||||||
.positional_nth(0)
|
.positional_nth(0)
|
||||||
.expect("checked through parser")
|
.expect("checked through parser")
|
@ -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, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,7 +14,9 @@ impl Command for Overlay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("overlay").category(Category::Core)
|
Signature::build("overlay")
|
||||||
|
.category(Category::Core)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -23,7 +25,9 @@ impl Command for Overlay {
|
|||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"This command is a parser keyword. For details, check:
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
You must use one of the following subcommands. Using this command as-is will only produce this help message."#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
@ -36,7 +40,7 @@ impl Command for Overlay {
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: get_full_help(
|
val: get_full_help(
|
||||||
&Overlay.signature(),
|
&Overlay.signature(),
|
||||||
@ -50,15 +54,3 @@ impl Command for Overlay {
|
|||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
|
|
||||||
test_examples(Overlay {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,9 @@
|
|||||||
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::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OverlayHide;
|
pub struct OverlayHide;
|
||||||
@ -12,11 +14,12 @@ impl Command for OverlayHide {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Hide an active overlay"
|
"Hide an active overlay."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("overlay hide")
|
Signature::build("overlay hide")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.optional("name", SyntaxShape::String, "Overlay to hide")
|
.optional("name", SyntaxShape::String, "Overlay to hide")
|
||||||
.switch(
|
.switch(
|
||||||
"keep-custom",
|
"keep-custom",
|
||||||
@ -47,7 +50,7 @@ impl Command for OverlayHide {
|
|||||||
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 overlay_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
|
let overlay_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
@ -58,10 +61,10 @@ impl Command for OverlayHide {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !stack.is_overlay_active(&overlay_name.item) {
|
if !stack.is_overlay_active(&overlay_name.item) {
|
||||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
return Err(ShellError::OverlayNotFoundAtRuntime {
|
||||||
overlay_name.item,
|
overlay_name: overlay_name.item,
|
||||||
overlay_name.span,
|
span: overlay_name.span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let keep_env: Option<Vec<Spanned<String>>> =
|
let keep_env: Option<Vec<Spanned<String>>> =
|
||||||
@ -73,7 +76,12 @@ impl Command for OverlayHide {
|
|||||||
for name in env_var_names_to_keep.into_iter() {
|
for name in env_var_names_to_keep.into_iter() {
|
||||||
match stack.get_env_var(engine_state, &name.item) {
|
match stack.get_env_var(engine_state, &name.item) {
|
||||||
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
|
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
|
||||||
None => return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span)),
|
None => {
|
||||||
|
return Err(ShellError::EnvVarNotFoundAtRuntime {
|
||||||
|
envvar_name: name.item,
|
||||||
|
span: name.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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, Span, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -13,11 +13,13 @@ impl Command for OverlayList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"List all active overlays"
|
"List all active overlays."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("overlay list").category(Category::Core)
|
Signature::build("overlay list")
|
||||||
|
.category(Category::Core)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
@ -50,7 +52,7 @@ impl Command for OverlayList {
|
|||||||
example: r#"module spam { export def foo [] { "foo" } }
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
overlay use spam
|
overlay use spam
|
||||||
overlay list | last"#,
|
overlay list | last"#,
|
||||||
result: Some(Value::string("spam", Span::test_data())),
|
result: Some(Value::test_string("spam")),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
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::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OverlayNew;
|
pub struct OverlayNew;
|
||||||
@ -12,11 +14,13 @@ impl Command for OverlayNew {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Create an empty overlay"
|
"Create an empty overlay."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("overlay new")
|
Signature::build("overlay new")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("name", SyntaxShape::String, "Name of the overlay")
|
.required("name", SyntaxShape::String, "Name of the overlay")
|
||||||
// TODO:
|
// TODO:
|
||||||
// .switch(
|
// .switch(
|
@ -3,7 +3,7 @@ use nu_parser::trim_quotes_str;
|
|||||||
use nu_protocol::ast::{Call, Expr};
|
use nu_protocol::ast::{Call, Expr};
|
||||||
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, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -17,11 +17,13 @@ impl Command for OverlayUse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Use definitions from a module as an overlay"
|
"Use definitions from a module as an overlay."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("overlay use")
|
Signature::build("overlay use")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required(
|
.required(
|
||||||
"name",
|
"name",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -64,43 +66,26 @@ impl Command for OverlayUse {
|
|||||||
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||||
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||||
|
|
||||||
let maybe_origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
|
let maybe_origin_module_id = if let Some(overlay_expr) = call.parser_info_nth(0) {
|
||||||
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
||||||
module_id
|
module_id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
return Err(ShellError::NushellFailedSpanned {
|
||||||
"Not an overlay".to_string(),
|
msg: "Not an overlay".to_string(),
|
||||||
"requires an overlay (path or a string)".to_string(),
|
label: "requires an overlay (path or a string)".to_string(),
|
||||||
overlay_expr.span,
|
span: overlay_expr.span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
return Err(ShellError::NushellFailedSpanned {
|
||||||
"Missing positional".to_string(),
|
msg: "Missing positional".to_string(),
|
||||||
"missing required overlay".to_string(),
|
label: "missing required overlay".to_string(),
|
||||||
call.head,
|
span: call.head,
|
||||||
));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let overlay_name = if let Some(kw_expression) = call.positional_nth(1) {
|
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
|
||||||
// If renamed via the 'as' keyword, use the new name as the overlay name
|
name
|
||||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
|
||||||
if let Some(new_name) = new_name_expression.as_string() {
|
|
||||||
new_name
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
|
||||||
"Wrong keyword type".to_string(),
|
|
||||||
"keyword argument not a string".to_string(),
|
|
||||||
new_name_expression.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
|
||||||
"Wrong keyword type".to_string(),
|
|
||||||
"keyword argument not a keyword".to_string(),
|
|
||||||
kw_expression.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if engine_state
|
} else if engine_state
|
||||||
.find_overlay(name_arg.item.as_bytes())
|
.find_overlay(name_arg.item.as_bytes())
|
||||||
.is_some()
|
.is_some()
|
||||||
@ -113,10 +98,10 @@ impl Command for OverlayUse {
|
|||||||
return Err(ShellError::NonUtf8(name_arg.span));
|
return Err(ShellError::NonUtf8(name_arg.span));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
return Err(ShellError::OverlayNotFoundAtRuntime {
|
||||||
name_arg.item,
|
overlay_name: name_arg.item,
|
||||||
name_arg.span,
|
span: name_arg.span,
|
||||||
));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(module_id) = maybe_origin_module_id {
|
if let Some(module_id) = maybe_origin_module_id {
|
@ -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, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Register;
|
pub struct Register;
|
||||||
@ -11,7 +11,7 @@ impl Command for Register {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Register a plugin"
|
"Register a plugin."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -51,7 +51,7 @@ impl Command for Register {
|
|||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
_call: &Call,
|
_call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ impl Command for Return {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Return early from a function"
|
"Return early from a function."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -39,7 +39,7 @@ impl Command for Return {
|
|||||||
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 return_value: Option<Value> = call.opt(engine_state, stack, 0)?;
|
let return_value: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||||
if let Some(value) = return_value {
|
if let Some(value) = return_value {
|
||||||
Err(ShellError::Return(call.head, Box::new(value)))
|
Err(ShellError::Return(call.head, Box::new(value)))
|
@ -1,7 +1,10 @@
|
|||||||
use nu_engine::{eval_block, CallExt};
|
use nu_engine::{eval_block, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack};
|
use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Try;
|
pub struct Try;
|
||||||
@ -12,7 +15,7 @@ impl Command for Try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Try to run a block, if it fails optionally run a catch block"
|
"Try to run a block, if it fails optionally run a catch block."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
@ -45,7 +48,7 @@ impl Command for Try {
|
|||||||
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 try_block: Block = call.req(engine_state, stack, 0)?;
|
let try_block: Block = call.req(engine_state, stack, 0)?;
|
||||||
let catch_block: Option<Closure> = call.opt(engine_state, stack, 1)?;
|
let catch_block: Option<Closure> = call.opt(engine_state, stack, 1)?;
|
||||||
|
|
||||||
@ -54,54 +57,29 @@ impl Command for Try {
|
|||||||
let result = eval_block(engine_state, stack, try_block, input, false, false);
|
let result = eval_block(engine_state, stack, try_block, input, false, false);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Err(error) | Ok(PipelineData::Value(Value::Error { error }, ..)) => {
|
Err(error) => {
|
||||||
if let Some(catch_block) = catch_block {
|
let error = intercept_block_control(error)?;
|
||||||
let catch_block = engine_state.get_block(catch_block.block_id);
|
let err_value = Value::Error {
|
||||||
|
error: Box::new(error),
|
||||||
if let Some(var) = catch_block.signature.get_positional(0) {
|
};
|
||||||
if let Some(var_id) = &var.var_id {
|
handle_catch(err_value, catch_block, engine_state, stack)
|
||||||
let err_value = Value::Error { error };
|
|
||||||
stack.add_var(*var_id, err_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eval_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
catch_block,
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
}
|
||||||
|
Ok(PipelineData::Value(Value::Error { error }, ..)) => {
|
||||||
|
let error = intercept_block_control(*error)?;
|
||||||
|
let err_value = Value::Error {
|
||||||
|
error: Box::new(error),
|
||||||
|
};
|
||||||
|
handle_catch(err_value, catch_block, engine_state, stack)
|
||||||
}
|
}
|
||||||
// external command may fail to run
|
// external command may fail to run
|
||||||
Ok(pipeline) => {
|
Ok(pipeline) => {
|
||||||
let (pipeline, external_failed) = pipeline.is_external_failed();
|
let (pipeline, external_failed) = pipeline.is_external_failed();
|
||||||
if external_failed {
|
if external_failed {
|
||||||
if let Some(catch_block) = catch_block {
|
// Because external command errors aren't "real" errors,
|
||||||
let catch_block = engine_state.get_block(catch_block.block_id);
|
// (unless do -c is in effect)
|
||||||
|
// they can't be passed in as Nushell values.
|
||||||
if let Some(var) = catch_block.signature.get_positional(0) {
|
|
||||||
if let Some(var_id) = &var.var_id {
|
|
||||||
let err_value = Value::nothing(call.head);
|
let err_value = Value::nothing(call.head);
|
||||||
stack.add_var(*var_id, err_value);
|
handle_catch(err_value, catch_block, engine_state, stack)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eval_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
catch_block,
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::empty())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Ok(pipeline)
|
Ok(pipeline)
|
||||||
}
|
}
|
||||||
@ -125,6 +103,48 @@ impl Command for Try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_catch(
|
||||||
|
err_value: Value,
|
||||||
|
catch_block: Option<Closure>,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if let Some(catch_block) = catch_block {
|
||||||
|
let catch_block = engine_state.get_block(catch_block.block_id);
|
||||||
|
// Put the error value in the positional closure var
|
||||||
|
if let Some(var) = catch_block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack.add_var(*var_id, err_value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eval_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
catch_block,
|
||||||
|
// Make the error accessible with $in, too
|
||||||
|
err_value.into_pipeline_data(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The flow control commands `break`/`continue`/`return` emit their own [`ShellError`] variants
|
||||||
|
/// We need to ignore those in `try` and bubble them through
|
||||||
|
///
|
||||||
|
/// `Err` when flow control to bubble up with `?`
|
||||||
|
fn intercept_block_control(error: ShellError) -> Result<ShellError, ShellError> {
|
||||||
|
match error {
|
||||||
|
nu_protocol::ShellError::Break(_) => Err(error),
|
||||||
|
nu_protocol::ShellError::Continue(_) => Err(error),
|
||||||
|
nu_protocol::ShellError::Return(_, _) => Err(error),
|
||||||
|
_ => Ok(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
@ -2,7 +2,7 @@ use nu_engine::{eval_block, find_in_dirs_env, redirect_env};
|
|||||||
use nu_protocol::ast::{Call, Expr, Expression};
|
use nu_protocol::ast::{Call, Expr, Expression};
|
||||||
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, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,13 +14,18 @@ impl Command for Use {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Use definitions from a module"
|
"Use definitions from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("use")
|
Signature::build("use")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
.required("module", SyntaxShape::String, "Module or module file")
|
||||||
|
.optional(
|
||||||
|
"members",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"Which members of the module to import",
|
||||||
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +48,7 @@ impl Command for Use {
|
|||||||
let import_pattern = if let Some(Expression {
|
let import_pattern = if let Some(Expression {
|
||||||
expr: Expr::ImportPattern(pat),
|
expr: Expr::ImportPattern(pat),
|
||||||
..
|
..
|
||||||
}) = call.positional_nth(0)
|
}) = call.parser_info_nth(0)
|
||||||
{
|
{
|
||||||
pat
|
pat
|
||||||
} else {
|
} else {
|
||||||
@ -117,12 +122,12 @@ impl Command for Use {
|
|||||||
Example {
|
Example {
|
||||||
description: "Define a custom command in a module and call it",
|
description: "Define a custom command in a module and call it",
|
||||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||||
result: Some(Value::string("foo", Span::test_data())),
|
result: Some(Value::test_string("foo")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define a custom command that participates in the environment in a module and call it",
|
description: "Define a custom command that participates in the environment in a module and call it",
|
||||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||||
result: Some(Value::string("BAZ", Span::test_data())),
|
result: Some(Value::test_string("BAZ")),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,10 +1,9 @@
|
|||||||
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::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value};
|
use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value};
|
||||||
|
use shadow_rs::shadow;
|
||||||
|
|
||||||
pub mod shadow {
|
shadow!(build);
|
||||||
include!(concat!(env!("OUT_DIR"), "/shadow.rs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Version;
|
pub struct Version;
|
||||||
@ -21,7 +20,7 @@ impl Command for Version {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Display Nu version."
|
"Display Nu version, and its build configuration."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -49,66 +48,70 @@ pub fn version(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let tag = call.head;
|
// Pre-allocate the arrays in the worst case (12 items):
|
||||||
let mut cols = vec![];
|
// - version
|
||||||
let mut vals = vec![];
|
// - branch
|
||||||
|
// - commit_hash
|
||||||
|
// - build_os
|
||||||
|
// - build_target
|
||||||
|
// - rust_version
|
||||||
|
// - cargo_version
|
||||||
|
// - build_time
|
||||||
|
// - build_rust_channel
|
||||||
|
// - features
|
||||||
|
// - installed_plugins
|
||||||
|
let mut cols = Vec::with_capacity(12);
|
||||||
|
let mut vals = Vec::with_capacity(12);
|
||||||
|
|
||||||
cols.push("version".to_string());
|
cols.push("version".to_string());
|
||||||
vals.push(Value::string(env!("CARGO_PKG_VERSION"), tag));
|
vals.push(Value::string(env!("CARGO_PKG_VERSION"), call.head));
|
||||||
|
|
||||||
cols.push("branch".to_string());
|
cols.push("branch".to_string());
|
||||||
vals.push(Value::string(shadow::BRANCH, call.head));
|
vals.push(Value::string(build::BRANCH, call.head));
|
||||||
|
|
||||||
let commit_hash: Option<&str> = option_env!("NU_COMMIT_HASH");
|
let commit_hash = option_env!("NU_COMMIT_HASH");
|
||||||
if let Some(commit_hash) = commit_hash {
|
if let Some(commit_hash) = commit_hash {
|
||||||
cols.push("commit_hash".to_string());
|
cols.push("commit_hash".to_string());
|
||||||
vals.push(Value::string(commit_hash, call.head));
|
vals.push(Value::string(commit_hash, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let build_os: Option<&str> = Some(shadow::BUILD_OS).filter(|x| !x.is_empty());
|
let build_os = Some(build::BUILD_OS).filter(|x| !x.is_empty());
|
||||||
if let Some(build_os) = build_os {
|
if let Some(build_os) = build_os {
|
||||||
cols.push("build_os".to_string());
|
cols.push("build_os".to_string());
|
||||||
vals.push(Value::string(build_os, call.head));
|
vals.push(Value::string(build_os, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let build_target: Option<&str> = Some(shadow::BUILD_TARGET).filter(|x| !x.is_empty());
|
let build_target = Some(build::BUILD_TARGET).filter(|x| !x.is_empty());
|
||||||
if let Some(build_target) = build_target {
|
if let Some(build_target) = build_target {
|
||||||
cols.push("build_target".to_string());
|
cols.push("build_target".to_string());
|
||||||
vals.push(Value::string(build_target, call.head));
|
vals.push(Value::string(build_target, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rust_version: Option<&str> = Some(shadow::RUST_VERSION).filter(|x| !x.is_empty());
|
let rust_version = Some(build::RUST_VERSION).filter(|x| !x.is_empty());
|
||||||
if let Some(rust_version) = rust_version {
|
if let Some(rust_version) = rust_version {
|
||||||
cols.push("rust_version".to_string());
|
cols.push("rust_version".to_string());
|
||||||
vals.push(Value::string(rust_version, call.head));
|
vals.push(Value::string(rust_version, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rust_channel: Option<&str> = Some(shadow::RUST_CHANNEL).filter(|x| !x.is_empty());
|
let rust_channel = Some(build::RUST_CHANNEL).filter(|x| !x.is_empty());
|
||||||
if let Some(rust_channel) = rust_channel {
|
if let Some(rust_channel) = rust_channel {
|
||||||
cols.push("rust_channel".to_string());
|
cols.push("rust_channel".to_string());
|
||||||
vals.push(Value::string(rust_channel, call.head));
|
vals.push(Value::string(rust_channel, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cargo_version: Option<&str> = Some(shadow::CARGO_VERSION).filter(|x| !x.is_empty());
|
let cargo_version = Some(build::CARGO_VERSION).filter(|x| !x.is_empty());
|
||||||
if let Some(cargo_version) = cargo_version {
|
if let Some(cargo_version) = cargo_version {
|
||||||
cols.push("cargo_version".to_string());
|
cols.push("cargo_version".to_string());
|
||||||
vals.push(Value::string(cargo_version, call.head));
|
vals.push(Value::string(cargo_version, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pkg_version: Option<&str> = Some(shadow::PKG_VERSION).filter(|x| !x.is_empty());
|
let build_time = Some(build::BUILD_TIME).filter(|x| !x.is_empty());
|
||||||
if let Some(pkg_version) = pkg_version {
|
|
||||||
cols.push("pkg_version".to_string());
|
|
||||||
vals.push(Value::string(pkg_version, call.head));
|
|
||||||
}
|
|
||||||
|
|
||||||
let build_time: Option<&str> = Some(shadow::BUILD_TIME).filter(|x| !x.is_empty());
|
|
||||||
if let Some(build_time) = build_time {
|
if let Some(build_time) = build_time {
|
||||||
cols.push("build_time".to_string());
|
cols.push("build_time".to_string());
|
||||||
vals.push(Value::string(build_time, call.head));
|
vals.push(Value::string(build_time, call.head));
|
||||||
}
|
}
|
||||||
|
|
||||||
let build_rust_channel: Option<&str> =
|
let build_rust_channel = Some(build::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty());
|
||||||
Some(shadow::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty());
|
|
||||||
if let Some(build_rust_channel) = build_rust_channel {
|
if let Some(build_rust_channel) = build_rust_channel {
|
||||||
cols.push("build_rust_channel".to_string());
|
cols.push("build_rust_channel".to_string());
|
||||||
vals.push(Value::string(build_rust_channel, call.head));
|
vals.push(Value::string(build_rust_channel, call.head));
|
||||||
@ -123,7 +126,6 @@ pub fn version(
|
|||||||
// Get a list of command names and check for plugins
|
// Get a list of command names and check for plugins
|
||||||
let installed_plugins = engine_state
|
let installed_plugins = engine_state
|
||||||
.plugin_decls()
|
.plugin_decls()
|
||||||
.into_iter()
|
|
||||||
.filter(|x| x.is_plugin().is_some())
|
.filter(|x| x.is_plugin().is_some())
|
||||||
.map(|x| x.name())
|
.map(|x| x.name())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@ -162,7 +164,7 @@ fn features_enabled() -> Vec<String> {
|
|||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
{
|
{
|
||||||
names.push("database".to_string());
|
names.push("sqlite".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dataframe")]
|
#[cfg(feature = "dataframe")]
|
@ -1,7 +1,9 @@
|
|||||||
use nu_engine::{eval_block, eval_expression, CallExt};
|
use nu_engine::{eval_block, eval_expression, CallExt};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value};
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct While;
|
pub struct While;
|
||||||
@ -17,6 +19,8 @@ impl Command for While {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("while")
|
Signature::build("while")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.required("cond", SyntaxShape::Expression, "condition to check")
|
.required("cond", SyntaxShape::Expression, "condition to check")
|
||||||
.required(
|
.required(
|
||||||
"block",
|
"block",
|
||||||
@ -41,7 +45,7 @@ impl Command for While {
|
|||||||
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 cond = call.positional_nth(0).expect("checked through parser");
|
let cond = call.positional_nth(0).expect("checked through parser");
|
||||||
let block: Block = call.req(engine_state, stack, 1)?;
|
let block: Block = call.req(engine_state, stack, 1)?;
|
||||||
|
|
||||||
@ -85,12 +89,12 @@ impl Command for While {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
x => {
|
x => {
|
||||||
return Err(ShellError::CantConvert(
|
return Err(ShellError::CantConvert {
|
||||||
"bool".into(),
|
to_type: "bool".into(),
|
||||||
x.get_type().to_string(),
|
from_type: x.get_type().to_string(),
|
||||||
result.span()?,
|
span: result.span()?,
|
||||||
None,
|
help: None,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
74
crates/nu-cmd-lang/src/default_context.rs
Normal file
74
crates/nu-cmd-lang/src/default_context.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub fn create_default_context() -> EngineState {
|
||||||
|
let mut engine_state = EngineState::new();
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
macro_rules! bind_command {
|
||||||
|
( $( $command:expr ),* $(,)? ) => {
|
||||||
|
$( working_set.add_decl(Box::new($command)); )*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core
|
||||||
|
bind_command! {
|
||||||
|
Alias,
|
||||||
|
Break,
|
||||||
|
Commandline,
|
||||||
|
Const,
|
||||||
|
Continue,
|
||||||
|
Def,
|
||||||
|
DefEnv,
|
||||||
|
Describe,
|
||||||
|
Do,
|
||||||
|
Echo,
|
||||||
|
ErrorMake,
|
||||||
|
ExportAlias,
|
||||||
|
ExportCommand,
|
||||||
|
ExportDef,
|
||||||
|
ExportDefEnv,
|
||||||
|
ExportExtern,
|
||||||
|
ExportUse,
|
||||||
|
Extern,
|
||||||
|
For,
|
||||||
|
Help,
|
||||||
|
HelpAliases,
|
||||||
|
HelpCommands,
|
||||||
|
HelpModules,
|
||||||
|
HelpOperators,
|
||||||
|
Hide,
|
||||||
|
HideEnv,
|
||||||
|
If,
|
||||||
|
Ignore,
|
||||||
|
Overlay,
|
||||||
|
OverlayUse,
|
||||||
|
OverlayList,
|
||||||
|
OverlayNew,
|
||||||
|
OverlayHide,
|
||||||
|
Let,
|
||||||
|
Loop,
|
||||||
|
Module,
|
||||||
|
Mut,
|
||||||
|
Return,
|
||||||
|
Try,
|
||||||
|
Use,
|
||||||
|
Version,
|
||||||
|
While,
|
||||||
|
};
|
||||||
|
|
||||||
|
//#[cfg(feature = "plugin")]
|
||||||
|
bind_command!(Register);
|
||||||
|
|
||||||
|
working_set.render()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
|
eprintln!("Error creating default context: {err:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
}
|
222
crates/nu-cmd-lang/src/example_support.rs
Normal file
222
crates/nu-cmd-lang/src/example_support.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Block,
|
||||||
|
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
||||||
|
Example, PipelineData, Signature, Span, Type, Value,
|
||||||
|
};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub fn check_example_input_and_output_types_match_command_signature(
|
||||||
|
example: &Example,
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
engine_state: &mut Box<EngineState>,
|
||||||
|
signature_input_output_types: &Vec<(Type, Type)>,
|
||||||
|
signature_operates_on_cell_paths: bool,
|
||||||
|
signature_vectorizes_over_list: bool,
|
||||||
|
) -> HashSet<(Type, Type)> {
|
||||||
|
let mut witnessed_type_transformations = HashSet::<(Type, Type)>::new();
|
||||||
|
|
||||||
|
// Skip tests that don't have results to compare to
|
||||||
|
if let Some(example_output) = example.result.as_ref() {
|
||||||
|
if let Some(example_input_type) =
|
||||||
|
eval_pipeline_without_terminal_expression(example.example, cwd, engine_state)
|
||||||
|
{
|
||||||
|
let example_input_type = example_input_type.get_type();
|
||||||
|
let example_output_type = example_output.get_type();
|
||||||
|
|
||||||
|
let example_matches_signature =
|
||||||
|
signature_input_output_types
|
||||||
|
.iter()
|
||||||
|
.any(|(sig_in_type, sig_out_type)| {
|
||||||
|
example_input_type.is_subtype(sig_in_type)
|
||||||
|
&& example_output_type.is_subtype(sig_out_type)
|
||||||
|
&& {
|
||||||
|
witnessed_type_transformations
|
||||||
|
.insert((sig_in_type.clone(), sig_out_type.clone()));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The example type checks as vectorization over an input list if both:
|
||||||
|
// 1. The command is declared to vectorize over list input.
|
||||||
|
// 2. There exists an entry t -> u in the type map such that the
|
||||||
|
// example_input_type is a subtype of list<t> and the
|
||||||
|
// example_output_type is a subtype of list<u>.
|
||||||
|
let example_matches_signature_via_vectorization_over_list =
|
||||||
|
signature_vectorizes_over_list
|
||||||
|
&& match &example_input_type {
|
||||||
|
Type::List(ex_in_type) => {
|
||||||
|
match signature_input_output_types.iter().find_map(
|
||||||
|
|(sig_in_type, sig_out_type)| {
|
||||||
|
if ex_in_type.is_subtype(sig_in_type) {
|
||||||
|
Some((sig_in_type, sig_out_type))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Some((sig_in_type, sig_out_type)) => match &example_output_type {
|
||||||
|
Type::List(ex_out_type)
|
||||||
|
if ex_out_type.is_subtype(sig_out_type) =>
|
||||||
|
{
|
||||||
|
witnessed_type_transformations
|
||||||
|
.insert((sig_in_type.clone(), sig_out_type.clone()));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The example type checks as a cell path operation if both:
|
||||||
|
// 1. The command is declared to operate on cell paths.
|
||||||
|
// 2. The example_input_type is list or record or table, and the example
|
||||||
|
// output shape is the same as the input shape.
|
||||||
|
let example_matches_signature_via_cell_path_operation = signature_operates_on_cell_paths
|
||||||
|
&& example_input_type.accepts_cell_paths()
|
||||||
|
// TODO: This is too permissive; it should make use of the signature.input_output_types at least.
|
||||||
|
&& example_output_type.to_shape() == example_input_type.to_shape();
|
||||||
|
|
||||||
|
if !(example_matches_signature
|
||||||
|
|| example_matches_signature_via_vectorization_over_list
|
||||||
|
|| example_matches_signature_via_cell_path_operation)
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"The example `{}` demonstrates a transformation of type {:?} -> {:?}. \
|
||||||
|
However, this does not match the declared signature: {:?}.{} \
|
||||||
|
For this command, `vectorizes_over_list` is {} and `operates_on_cell_paths()` is {}.",
|
||||||
|
example.example,
|
||||||
|
example_input_type,
|
||||||
|
example_output_type,
|
||||||
|
signature_input_output_types,
|
||||||
|
if signature_input_output_types.is_empty() { " (Did you forget to declare the input and output types for the command?)" } else { "" },
|
||||||
|
signature_vectorizes_over_list,
|
||||||
|
signature_operates_on_cell_paths
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
witnessed_type_transformations
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_pipeline_without_terminal_expression(
|
||||||
|
src: &str,
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
engine_state: &mut Box<EngineState>,
|
||||||
|
) -> Option<Value> {
|
||||||
|
let (mut block, delta) = parse(src, engine_state);
|
||||||
|
if block.pipelines.len() == 1 {
|
||||||
|
let n_expressions = block.pipelines[0].elements.len();
|
||||||
|
block.pipelines[0].elements.truncate(&n_expressions - 1);
|
||||||
|
|
||||||
|
if !block.pipelines[0].elements.is_empty() {
|
||||||
|
let empty_input = PipelineData::empty();
|
||||||
|
Some(eval_block(block, empty_input, cwd, engine_state, delta))
|
||||||
|
} else {
|
||||||
|
Some(Value::nothing(Span::test_data()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// E.g. multiple semicolon-separated statements
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(contents: &str, engine_state: &EngineState) -> (Block, StateDelta) {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let (output, err) = nu_parser::parse(&mut working_set, None, contents.as_bytes(), false, &[]);
|
||||||
|
|
||||||
|
if let Some(err) = err {
|
||||||
|
panic!("test parse error in `{contents}`: {err:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, working_set.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_block(
|
||||||
|
block: Block,
|
||||||
|
input: PipelineData,
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
engine_state: &mut Box<EngineState>,
|
||||||
|
delta: StateDelta,
|
||||||
|
) -> Value {
|
||||||
|
engine_state
|
||||||
|
.merge_delta(delta)
|
||||||
|
.expect("Error merging delta");
|
||||||
|
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
|
match nu_engine::eval_block(engine_state, &mut stack, &block, input, true, true) {
|
||||||
|
Err(err) => panic!("test eval error in `{}`: {:?}", "TODO", err),
|
||||||
|
Ok(result) => result.into_value(Span::test_data()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_example_evaluates_to_expected_output(
|
||||||
|
example: &Example,
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
engine_state: &mut Box<EngineState>,
|
||||||
|
) {
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// Set up PWD
|
||||||
|
stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
.merge_env(&mut stack, cwd)
|
||||||
|
.expect("Error merging environment");
|
||||||
|
|
||||||
|
let empty_input = PipelineData::empty();
|
||||||
|
let result = eval(example.example, empty_input, cwd, engine_state);
|
||||||
|
|
||||||
|
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
||||||
|
// If the command you are testing requires to compare another case, then
|
||||||
|
// you need to define its equality in the Value struct
|
||||||
|
if let Some(expected) = example.result.as_ref() {
|
||||||
|
assert_eq!(
|
||||||
|
&result, expected,
|
||||||
|
"The example result differs from the expected value",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
signature: Signature,
|
||||||
|
witnessed_type_transformations: HashSet<(Type, Type)>,
|
||||||
|
) {
|
||||||
|
let declared_type_transformations =
|
||||||
|
HashSet::from_iter(signature.input_output_types.into_iter());
|
||||||
|
assert!(
|
||||||
|
witnessed_type_transformations.is_subset(&declared_type_transformations),
|
||||||
|
"This should not be possible (bug in test): the type transformations \
|
||||||
|
collected in the course of matching examples to the signature type map \
|
||||||
|
contain type transformations not present in the signature type map."
|
||||||
|
);
|
||||||
|
|
||||||
|
if !signature.allow_variants_without_examples {
|
||||||
|
assert_eq!(
|
||||||
|
witnessed_type_transformations,
|
||||||
|
declared_type_transformations,
|
||||||
|
"There are entries in the signature type map which do not correspond to any example: \
|
||||||
|
{:?}",
|
||||||
|
declared_type_transformations
|
||||||
|
.difference(&witnessed_type_transformations)
|
||||||
|
.map(|(s1, s2)| format!("{s1} -> {s2}"))
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval(
|
||||||
|
contents: &str,
|
||||||
|
input: PipelineData,
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
engine_state: &mut Box<EngineState>,
|
||||||
|
) -> Value {
|
||||||
|
let (block, delta) = parse(contents, engine_state);
|
||||||
|
eval_block(block, input, cwd, engine_state, delta)
|
||||||
|
}
|
81
crates/nu-cmd-lang/src/example_test.rs
Normal file
81
crates/nu-cmd-lang/src/example_test.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
use nu_protocol::engine::Command;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn test_examples(cmd: impl Command + 'static) {
|
||||||
|
test_examples::test_examples(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_examples {
|
||||||
|
use crate::example_support::{
|
||||||
|
check_all_signature_input_output_types_entries_have_examples,
|
||||||
|
check_example_evaluates_to_expected_output,
|
||||||
|
check_example_input_and_output_types_match_command_signature,
|
||||||
|
};
|
||||||
|
use crate::{Break, Describe, Mut};
|
||||||
|
use crate::{Echo, If, Let};
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{Command, EngineState, StateWorkingSet},
|
||||||
|
Type,
|
||||||
|
};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub fn test_examples(cmd: impl Command + 'static) {
|
||||||
|
let examples = cmd.examples();
|
||||||
|
let signature = cmd.signature();
|
||||||
|
let mut engine_state = make_engine_state(cmd.clone_box());
|
||||||
|
|
||||||
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
|
|
||||||
|
let mut witnessed_type_transformations = HashSet::<(Type, Type)>::new();
|
||||||
|
|
||||||
|
for example in examples {
|
||||||
|
if example.result.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
witnessed_type_transformations.extend(
|
||||||
|
check_example_input_and_output_types_match_command_signature(
|
||||||
|
&example,
|
||||||
|
&cwd,
|
||||||
|
&mut make_engine_state(cmd.clone_box()),
|
||||||
|
&signature.input_output_types,
|
||||||
|
signature.operates_on_cell_paths(),
|
||||||
|
signature.vectorizes_over_list,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
signature,
|
||||||
|
witnessed_type_transformations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
||||||
|
let mut engine_state = Box::new(EngineState::new());
|
||||||
|
|
||||||
|
let delta = {
|
||||||
|
// Base functions that are needed for testing
|
||||||
|
// Try to keep this working set small to keep tests running as fast as possible
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
working_set.add_decl(Box::new(Break));
|
||||||
|
working_set.add_decl(Box::new(Describe));
|
||||||
|
working_set.add_decl(Box::new(Echo));
|
||||||
|
working_set.add_decl(Box::new(If));
|
||||||
|
working_set.add_decl(Box::new(Let));
|
||||||
|
working_set.add_decl(Box::new(Mut));
|
||||||
|
|
||||||
|
// Adding the command that is being tested to the working set
|
||||||
|
working_set.add_decl(cmd);
|
||||||
|
|
||||||
|
working_set.render()
|
||||||
|
};
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
.merge_delta(delta)
|
||||||
|
.expect("Error merging delta");
|
||||||
|
engine_state
|
||||||
|
}
|
||||||
|
}
|
10
crates/nu-cmd-lang/src/lib.rs
Normal file
10
crates/nu-cmd-lang/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
mod core_commands;
|
||||||
|
mod default_context;
|
||||||
|
pub mod example_support;
|
||||||
|
mod example_test;
|
||||||
|
|
||||||
|
pub use core_commands::*;
|
||||||
|
pub use default_context::*;
|
||||||
|
pub use example_support::*;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub use example_test::test_examples;
|
@ -5,18 +5,21 @@ 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.73.0"
|
version = "0.77.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
# used only for text_style Alignments
|
# used only for text_style Alignments
|
||||||
tabled = { version = "0.10.0", features = ["color"], default-features = false }
|
tabled = { version = "0.10.0", features = ["color"], default-features = false }
|
||||||
|
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.73.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.77.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.47.0"
|
||||||
nu-utils = { path = "../nu-utils", version = "0.73.0" }
|
nu-utils = { path = "../nu-utils", version = "0.77.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.73.0" }
|
nu-engine = { path = "../nu-engine", version = "0.77.0" }
|
||||||
nu-json = { path="../nu-json", version = "0.73.0" }
|
nu-json = { path="../nu-json", version = "0.77.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.73.0" }
|
nu-test-support = { path="../nu-test-support", version = "0.77.0" }
|
||||||
|
@ -65,7 +65,7 @@ fn color_to_string(color: Color) -> Option<String> {
|
|||||||
Color::White => Some(String::from("white")),
|
Color::White => Some(String::from("white")),
|
||||||
Color::LightGray => Some(String::from("light_gray")),
|
Color::LightGray => Some(String::from("light_gray")),
|
||||||
Color::Default => Some(String::from("default")),
|
Color::Default => Some(String::from("default")),
|
||||||
Color::Rgb(r, g, b) => Some(format!("#{:X}{:X}{:X}", r, g, b)),
|
Color::Rgb(r, g, b) => Some(format!("#{r:X}{g:X}{b:X}")),
|
||||||
Color::Fixed(_) => None,
|
Color::Fixed(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
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};
|
||||||
|
|
||||||
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
// The default colors for shapes, used when there is no config for them.
|
||||||
match conf.color_config.get(shape.as_str()) {
|
pub fn default_shape_color(shape: String) -> Style {
|
||||||
Some(int_color) => match int_color.as_string() {
|
match shape.as_ref() {
|
||||||
Ok(int_color) => lookup_ansi_color_style(&int_color),
|
|
||||||
Err(_) => Style::default(),
|
|
||||||
},
|
|
||||||
None => match shape.as_ref() {
|
|
||||||
"shape_and" => Style::new().fg(Color::Purple).bold(),
|
"shape_and" => Style::new().fg(Color::Purple).bold(),
|
||||||
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
"shape_binary" => Style::new().fg(Color::Purple).bold(),
|
||||||
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
"shape_block" => Style::new().fg(Color::Blue).bold(),
|
||||||
@ -40,6 +36,21 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
|||||||
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
"shape_table" => Style::new().fg(Color::Blue).bold(),
|
||||||
"shape_variable" => Style::new().fg(Color::Purple),
|
"shape_variable" => Style::new().fg(Color::Purple),
|
||||||
_ => Style::default(),
|
_ => Style::default(),
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shape_color(shape: String, conf: &Config) -> Style {
|
||||||
|
match conf.color_config.get(shape.as_str()) {
|
||||||
|
Some(int_color) => {
|
||||||
|
// Shapes do not use color_config closures, currently.
|
||||||
|
match int_color {
|
||||||
|
Value::Record { .. } => color_record_to_nustyle(int_color),
|
||||||
|
Value::String { val, .. } => lookup_ansi_color_style(val),
|
||||||
|
// Defer to the default in the event of incorrect types being given
|
||||||
|
// (i.e. treat null, etc. as the value being unset)
|
||||||
|
_ => default_shape_color(shape),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => default_shape_color(shape),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user