forked from extern/nushell
Compare commits
471 Commits
Author | SHA1 | Date | |
---|---|---|---|
c74cff213e | |||
2ea5819b02 | |||
1115190a49 | |||
7132c5ad02 | |||
1920ece759 | |||
6f59abaf43 | |||
5f7425a7b4 | |||
1ab9ec3ebc | |||
f2095ed0cc | |||
7e26b4fcc2 | |||
ad95e4cc27 | |||
ee5a18167c | |||
75c9e3e5df | |||
77f10eb270 | |||
42bb42a2e1 | |||
f597380112 | |||
b92b4120dc | |||
de5ad5de19 | |||
64695cd67c | |||
21b3eeed99 | |||
a86a7e6c29 | |||
15421dc45e | |||
9522052063 | |||
ba880277bf | |||
40241e9be6 | |||
34f3da7150 | |||
534287ed65 | |||
913c2b8d1c | |||
aeffa188f0 | |||
543a25599c | |||
7ad0e5541e | |||
c1cc1c82cc | |||
b0b4c3dffd | |||
f3de373c59 | |||
df1fecd2cb | |||
9620e27e4f | |||
072ecec8ca | |||
748d82cec1 | |||
5e5d1ea81b | |||
bb570e42be | |||
3a050864df | |||
8cfa96b4c0 | |||
6f384da57e | |||
f8e63328d8 | |||
5d98a727ca | |||
109f629cb6 | |||
ff6a67d293 | |||
03ae01f11e | |||
697f3c03f1 | |||
1cecb37628 | |||
89436e978b | |||
cd0a52cf00 | |||
c6043eb500 | |||
729373aba0 | |||
f59a6990dc | |||
0f9094ead2 | |||
70f7db14d4 | |||
0cba269d80 | |||
ec2593efb8 | |||
c2283596ac | |||
c9399f5142 | |||
c9c93f5b4d | |||
2264682443 | |||
247c33b6d6 | |||
a6da8ce769 | |||
020e121391 | |||
7d5bd0d6be | |||
87717b9ddd | |||
3c6fac059e | |||
9092fc1b12 | |||
5b557a888e | |||
398b756aee | |||
533c1a89af | |||
84742275a1 | |||
92d968b8c8 | |||
50102bf69b | |||
156232fe08 | |||
0a3761a594 | |||
44dc890124 | |||
6ead98effb | |||
d5f76c02f0 | |||
fd77114d82 | |||
78f52e8b66 | |||
5b01685fc3 | |||
c2b684464f | |||
fd56768fdc | |||
070afa4528 | |||
23fec8eb0d | |||
c9841f67e9 | |||
76482cc1b2 | |||
d43f4253e8 | |||
0fba08808c | |||
b3a52a247f | |||
4763801cb2 | |||
ecb3b3a364 | |||
3e5f81ae14 | |||
ca05553fc6 | |||
fa5d7babb9 | |||
94b27267fd | |||
d1390ac95b | |||
d717e8faeb | |||
a95a4505ef | |||
b03f1efac4 | |||
5d5088b5d5 | |||
c1c73811d5 | |||
51bf8d9f6a | |||
858c93d2e5 | |||
31146a7591 | |||
05d7d6d6ad | |||
fb3350ebc3 | |||
2ffe30ecf0 | |||
f8dc3421b0 | |||
c1a30ac60f | |||
fc06afd051 | |||
c9aa6ba0f3 | |||
f8c82588b6 | |||
67eec92e76 | |||
b227eea668 | |||
58d002d469 | |||
5d283755e3 | |||
35e8db160d | |||
7d8df4ba9e | |||
f36c055c50 | |||
76bdda1178 | |||
5c07e82fc0 | |||
6ea5bdcf47 | |||
15c7e1b725 | |||
e5d9f405db | |||
80881c75f9 | |||
250ee62e16 | |||
54d73748e4 | |||
d08e254d16 | |||
7f771babb7 | |||
b9b9eaaca5 | |||
0303d709e6 | |||
0e1322e6d6 | |||
e290fa0e68 | |||
112306aab5 | |||
98952082ae | |||
8386bc0919 | |||
182b0ab4fb | |||
fa83458a6d | |||
54398f9546 | |||
077d1c8125 | |||
1ff8c2d81d | |||
d77f1753c2 | |||
85c6047b71 | |||
d37893cca0 | |||
95ac436d26 | |||
c2a46070aa | |||
57808ca7cc | |||
6cfe35eb7e | |||
b2734db015 | |||
823e578c46 | |||
95a745e622 | |||
776df7cd93 | |||
d5677625a7 | |||
64288b4350 | |||
a42fd3611a | |||
e36f69bf3c | |||
5ad7b8f029 | |||
ffb80b8873 | |||
177e800a07 | |||
a7e8970383 | |||
a324a50bb7 | |||
0578cf85ac | |||
1b54dd5418 | |||
8ad5d8bb6a | |||
869b01205c | |||
f5b2f5a9ee | |||
adfa4d00c0 | |||
12effd9b4e | |||
c26fca7419 | |||
da59dfe7d2 | |||
08715e6308 | |||
07d7899a97 | |||
494a5a5286 | |||
f41c93b2d3 | |||
5063e01c12 | |||
d1137cc700 | |||
3966c0a9fd | |||
dbdb1f6600 | |||
84cdc0d521 | |||
ab59dab129 | |||
e0c8a3d14c | |||
e93e51d672 | |||
4205edbc70 | |||
80bee40807 | |||
461837773b | |||
52d4259f58 | |||
274a8366c6 | |||
a1dfc35968 | |||
5886a74ccc | |||
4367aa9f58 | |||
e9c298713e | |||
c110ddff66 | |||
a806717f35 | |||
2b5f1ee5b3 | |||
77a1c3c7b2 | |||
82b3ae826f | |||
1b3092ae7c | |||
942ff7df4d | |||
51abe4c262 | |||
e8e0526f57 | |||
0b25385109 | |||
415b1273b4 | |||
6bee80dcd7 | |||
588a078872 | |||
93096a07aa | |||
523d0bca16 | |||
fe92051bb3 | |||
ee648ecb7d | |||
91920373b5 | |||
33a7bc405f | |||
cd75640a90 | |||
0f600bc3f5 | |||
aed4b626b8 | |||
92503e6571 | |||
44c0db46e1 | |||
1fd3bc1ba6 | |||
59ea28cf06 | |||
435abadd8a | |||
86cd387439 | |||
edbf3aaccb | |||
b03ef56bcb | |||
55316a9f27 | |||
d3ec3dc66b | |||
7ebae0b5f7 | |||
f45aed257f | |||
60da7abbc7 | |||
7a3cbf43e8 | |||
45b02ce2ab | |||
c039e4b3d0 | |||
9b202d560d | |||
1874082a2c | |||
1359b26da2 | |||
b9bc527d27 | |||
51cdd9fbb2 | |||
d838871063 | |||
4d16d92847 | |||
fc01701a41 | |||
51d5d0eac8 | |||
81d00f71a9 | |||
77fbf3e2d2 | |||
f565661f42 | |||
1a864ea6f4 | |||
c1738620e3 | |||
56e35fc3f9 | |||
29591c97a7 | |||
697dee6750 | |||
0ca8fcf58c | |||
a46048f362 | |||
0569a9c92e | |||
c1ca10ffd1 | |||
15c22db8f4 | |||
1c52b112c8 | |||
275dba82d5 | |||
cf7040a215 | |||
72cb4b6032 | |||
d4cbab454e | |||
3645178ff1 | |||
005180f269 | |||
72f7b9b7cc | |||
3f61ca19f0 | |||
d8c59eddb3 | |||
ac43372618 | |||
dccb2b48f3 | |||
2e68e6ddbf | |||
3dfe1a4f0e | |||
c87bac04c0 | |||
4b301710d3 | |||
7d67ca3652 | |||
01d8961eb7 | |||
7ac5a01e2f | |||
e2fb0e5b82 | |||
a11e41332c | |||
f3656f7822 | |||
38f4ab0bc9 | |||
f35741d50e | |||
d93315d8f5 | |||
8429aec57f | |||
f043a8a8ff | |||
c6016d7659 | |||
78b4472b32 | |||
cb754befe9 | |||
0588a4fc19 | |||
ff3a0a0de3 | |||
c799f77577 | |||
d3182a6737 | |||
b5e09b8a30 | |||
05efd735b9 | |||
5e0499fcf9 | |||
74d3f3c1d6 | |||
de6edf18d9 | |||
a35ecb4837 | |||
a01ef85bda | |||
6445c4e7de | |||
066c9118ae | |||
52e8b0afb2 | |||
db3f3eaf5a | |||
878f0cf6e1 | |||
7caf27b665 | |||
22b375ccd4 | |||
6a2539534f | |||
f310a9be8c | |||
d0dc6986dd | |||
11480c77be | |||
4fd2b702ee | |||
030e55acbf | |||
c5e1b64b40 | |||
999f7b229f | |||
de1c7bb39f | |||
54bc662e0e | |||
5f2089a15b | |||
adb99938f7 | |||
b907939916 | |||
27e6271402 | |||
0a8f27f6f2 | |||
1662e61ecb | |||
b58819d51e | |||
9692240b4f | |||
d204defb68 | |||
9e7f84afb0 | |||
ed8dee04b6 | |||
4171c654a5 | |||
22ee041002 | |||
7162d4d9aa | |||
9c70c68914 | |||
93b4aa5fcf | |||
71b0e12b57 | |||
09b3dab35d | |||
88a87158c2 | |||
faf84e69b4 | |||
5d8763ed1d | |||
58124e66a4 | |||
25af32d3a8 | |||
5a746c0ed6 | |||
89ccdc2b06 | |||
76ee00e013 | |||
4e5a1ced13 | |||
1f62024a15 | |||
6181ea5fc1 | |||
1751ac12f4 | |||
6cff54ed0d | |||
ec3e4ce120 | |||
f97443aff6 | |||
c925537c48 | |||
c5545c59c6 | |||
55044aa7d6 | |||
4be7004289 | |||
814a5caf9a | |||
81ece18d5e | |||
0ba81f1d51 | |||
c81fa397b6 | |||
8c36e9df44 | |||
1402508416 | |||
dc3c34275d | |||
f77fe04425 | |||
20ac30b6e2 | |||
4007256cfd | |||
b634f1b010 | |||
8a77d1ed92 | |||
69a17fb247 | |||
9f144798d3 | |||
0b651b6372 | |||
ce09186e2e | |||
0c67d742f0 | |||
2ef34a3b4b | |||
2d72f892fe | |||
ee4e0a933b | |||
765b303689 | |||
e427c68731 | |||
ff6c0fcb81 | |||
bcf3537395 | |||
4efccb2b1c | |||
67b5e1bde9 | |||
eb4fd144eb | |||
d51e82f33b | |||
7827b1fb87 | |||
7dbda76fad | |||
0dbd014d8b | |||
d064d187ab | |||
399319476a | |||
4f4e8c984e | |||
129ae0bf3e | |||
471c58448e | |||
a03c1c266c | |||
afdb68dc71 | |||
bc1b2fa5bd | |||
8c507dc984 | |||
4a82ee6c11 | |||
e8da57b05e | |||
f481879ed3 | |||
7c1487e18d | |||
2cc4191ec9 | |||
844cb1213b | |||
783f2a9342 | |||
eb6870cab5 | |||
0d367af24a | |||
5c15a4dd6e | |||
eeade99452 | |||
679879f79b | |||
c4bbc3edad | |||
6c6d215197 | |||
4b9ec03110 | |||
b9ecfeb890 | |||
4dbbacc35d | |||
28ef14399c | |||
20aaaaf90c | |||
7c274ad4d8 | |||
30c331e882 | |||
fa2e6e5d53 | |||
7eaa6d01ab | |||
d34581db4a | |||
85d6529f0d | |||
50039164f1 | |||
c64017de7b | |||
9e445fd4c5 | |||
cc4f8bbd82 | |||
16453b6986 | |||
d6b9153ac5 | |||
80a183dde2 | |||
f2af12af2c | |||
7ad4c679b3 | |||
9a0c6f2e02 | |||
78d0e1d0b8 | |||
dc739f703a | |||
ac7263d957 | |||
9c52b93975 | |||
80220b722b | |||
d1dc610769 | |||
cc767463e6 | |||
8f4ea69c22 | |||
6c026242d4 | |||
4a26719b0c | |||
d2f513da36 | |||
feef612388 | |||
65074ec449 | |||
a19cac2673 | |||
5326e51e4f | |||
b39cca91e5 | |||
57825a5c45 | |||
19cee5fda1 | |||
95a4649cc9 | |||
e96039fb1b | |||
65e2733571 | |||
bc437da5c7 | |||
65c4083ae6 | |||
6a2fd91a01 | |||
b6d31e0e45 | |||
d2c87ad4b4 | |||
a26a01c8d0 | |||
414216edfa | |||
6df001f72d | |||
4880721b73 | |||
1bb953877e | |||
ef7ade59f3 | |||
1c677c9577 | |||
1072bd06c3 | |||
4aa9102523 | |||
0c7a8e3634 | |||
addf8ca942 | |||
7cfd4d2cfa | |||
4ae53d93fb | |||
29e809ad77 | |||
e1c6be0682 | |||
bf40f035f6 | |||
989a14709c | |||
8d8b44342b | |||
7980ad9f7f | |||
af15f794b4 |
@ -26,3 +26,8 @@ rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-stati
|
|||||||
# [target.aarch64-apple-darwin]
|
# [target.aarch64-apple-darwin]
|
||||||
# linker = "clang"
|
# linker = "clang"
|
||||||
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
|
[target.aarch64-apple-darwin]
|
||||||
|
# We can guarantee that this target will always run on a CPU with _at least_
|
||||||
|
# these capabilities, so let's optimize for them
|
||||||
|
rustflags = ["-Ctarget-cpu=apple-m1"]
|
2
.github/.typos.toml
vendored
2
.github/.typos.toml
vendored
@ -11,3 +11,5 @@ Plasticos = "Plasticos"
|
|||||||
IIF = "IIF"
|
IIF = "IIF"
|
||||||
numer = "numer"
|
numer = "numer"
|
||||||
ratatui = "ratatui"
|
ratatui = "ratatui"
|
||||||
|
doas = "doas"
|
||||||
|
wheres = "wheres"
|
||||||
|
45
.github/workflows/ci.yml
vendored
45
.github/workflows/ci.yml
vendored
@ -9,6 +9,7 @@ name: continuous-integration
|
|||||||
env:
|
env:
|
||||||
NUSHELL_CARGO_PROFILE: ci
|
NUSHELL_CARGO_PROFILE: ci
|
||||||
NU_LOG_LEVEL: DEBUG
|
NU_LOG_LEVEL: DEBUG
|
||||||
|
# If changing these settings also change toolkit.nu
|
||||||
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
|
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -40,13 +41,14 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
# If changing these settings also change toolkit.nu
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- $CLIPPY_OPTIONS
|
run: cargo clippy --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
|
|
||||||
@ -83,13 +85,24 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
- 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 }}
|
||||||
|
|
||||||
|
- name: Check for clean repo
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "there are changes";
|
||||||
|
git status --porcelain
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "no changes in working directory";
|
||||||
|
fi
|
||||||
|
|
||||||
std-lib-and-python-virtualenv:
|
std-lib-and-python-virtualenv:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@ -104,7 +117,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
@ -115,7 +128,7 @@ jobs:
|
|||||||
run: nu -c 'use std testing; testing run-tests --path crates/nu-std'
|
run: nu -c 'use std testing; testing run-tests --path crates/nu-std'
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
@ -127,6 +140,17 @@ jobs:
|
|||||||
run: nu scripts/test_virtualenv.nu
|
run: nu scripts/test_virtualenv.nu
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Check for clean repo
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "there are changes";
|
||||||
|
git status --porcelain
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "no changes in working directory";
|
||||||
|
fi
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@ -139,7 +163,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
@ -148,3 +172,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --profile ci --package nu_plugin_*
|
run: cargo test --profile ci --package nu_plugin_*
|
||||||
|
|
||||||
|
- name: Check for clean repo
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "there are changes";
|
||||||
|
git status --porcelain
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "no changes in working directory";
|
||||||
|
fi
|
||||||
|
141
.github/workflows/nightly-build.yml
vendored
141
.github/workflows/nightly-build.yml
vendored
@ -36,10 +36,10 @@ jobs:
|
|||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.6
|
uses: hustcer/setup-nu@v3.8
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.84.0
|
version: 0.86.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -66,8 +66,8 @@ jobs:
|
|||||||
git push origin --tags
|
git push origin --tags
|
||||||
}
|
}
|
||||||
|
|
||||||
release:
|
standard:
|
||||||
name: Release
|
name: Std
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -117,10 +117,10 @@ jobs:
|
|||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-latest
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
@ -128,20 +128,22 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- 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.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.6
|
uses: hustcer/setup-nu@v3.8
|
||||||
with:
|
with:
|
||||||
version: 0.84.0
|
version: 0.86.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -149,6 +151,121 @@ jobs:
|
|||||||
id: nu
|
id: nu
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
|
RELEASE_TYPE: standard
|
||||||
|
OS: ${{ matrix.os }}
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
|
_EXTRA_: ${{ matrix.extra }}
|
||||||
|
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
|
||||||
|
|
||||||
|
- name: Create an Issue for Release Failure
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: JasonEtco/create-an-issue@v2.9.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
update_existing: true
|
||||||
|
search_existing: open
|
||||||
|
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
|
||||||
|
|
||||||
|
- name: Set Outputs of Short SHA
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
sha_short=$(git rev-parse --short HEAD)
|
||||||
|
echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
|
# Create a release only in nushell/nightly repo
|
||||||
|
- name: Publish Archive
|
||||||
|
uses: softprops/action-gh-release@v0.1.15
|
||||||
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
files: ${{ steps.nu.outputs.archive }}
|
||||||
|
tag_name: nightly-${{ steps.vars.outputs.sha_short }}
|
||||||
|
name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
full:
|
||||||
|
name: Full
|
||||||
|
needs: prepare
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- aarch64-apple-darwin
|
||||||
|
- x86_64-apple-darwin
|
||||||
|
- x86_64-pc-windows-msvc
|
||||||
|
- aarch64-pc-windows-msvc
|
||||||
|
- x86_64-unknown-linux-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
- aarch64-unknown-linux-gnu
|
||||||
|
extra: ['bin']
|
||||||
|
include:
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: 'bin'
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: msi
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
extra: 'bin'
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
extra: msi
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Update Rust Toolchain Target
|
||||||
|
run: |
|
||||||
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
|
- name: Setup Rust toolchain and cache
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
|
with:
|
||||||
|
rustflags: ''
|
||||||
|
|
||||||
|
- name: Setup Nushell
|
||||||
|
uses: hustcer/setup-nu@v3.8
|
||||||
|
with:
|
||||||
|
version: 0.86.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Release Nu Binary
|
||||||
|
id: nu
|
||||||
|
run: nu .github/workflows/release-pkg.nu
|
||||||
|
env:
|
||||||
|
RELEASE_TYPE: full
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
@ -204,9 +321,9 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.6
|
uses: hustcer/setup-nu@v3.8
|
||||||
with:
|
with:
|
||||||
version: 0.84.0
|
version: 0.86.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
67
.github/workflows/release-pkg.nu
vendored
67
.github/workflows/release-pkg.nu
vendored
@ -9,34 +9,36 @@
|
|||||||
# Instructions for manually creating an MSI for Winget Releases when they fail
|
# 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
|
||||||
# Updated again on 2023-02-23 because msis are still failing validation
|
# Updated again on 2023-02-23 because msis are still failing validation
|
||||||
|
# Update on 2023-10-18 to use RELEASE_TYPE env var to determine if full or not
|
||||||
# To run this manual for windows here are the steps I take
|
# To run this manual for windows here are the steps I take
|
||||||
# checkout the release you want to publish
|
# checkout the release you want to publish
|
||||||
# 1. git checkout 0.76.0
|
# 1. git checkout 0.86.0
|
||||||
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
|
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
|
||||||
# 2. $env:CARGO_TARGET_DIR = ""
|
# 2. $env:CARGO_TARGET_DIR = ""
|
||||||
# 2. hide-env CARGO_TARGET_DIR
|
# 2. hide-env CARGO_TARGET_DIR
|
||||||
# 3. $env.TARGET = 'x86_64-pc-windows-msvc'
|
# 3. $env.TARGET = 'x86_64-pc-windows-msvc'
|
||||||
# 4. $env.TARGET_RUSTFLAGS = ''
|
# 4. $env.TARGET_RUSTFLAGS = ''
|
||||||
# 5. $env.GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
# 5. $env.GITHUB_WORKSPACE = 'D:\nushell'
|
||||||
# 6. $env.GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
|
# 6. $env.GITHUB_OUTPUT = 'D:\nushell\output\out.txt'
|
||||||
# 7. $env.OS = 'windows-latest'
|
# 7. $env.OS = 'windows-latest'
|
||||||
|
# 8. $env.RELEASE_TYPE = '' # There is full and '' for normal releases
|
||||||
# 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. $env.Path = ($env.Path | append 'c:\apps\7-zip')
|
# 9. $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. $env.Path = ($env.Path | append 'c:\path\to\aria2c')
|
# 10. $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/
|
||||||
# 10. $env.Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
# 11. $env.Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
||||||
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
|
# 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'
|
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
|
||||||
# 11. $env._EXTRA_ = 'bin'
|
# 12. $env._EXTRA_ = 'bin'
|
||||||
# 12. source .github\workflows\release-pkg.nu
|
# 13. source .github\workflows\release-pkg.nu
|
||||||
# 13. cd ..
|
# 14. cd ..
|
||||||
# 14. $env._EXTRA_ = 'msi'
|
# 15. $env._EXTRA_ = 'msi'
|
||||||
# 15. source .github\workflows\release-pkg.nu
|
# 16. source .github\workflows\release-pkg.nu
|
||||||
# After msi is generated, you have to update winget-pkgs repo, you'll need to patch the release
|
# 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
|
# 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
|
# 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
|
# 17. 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
|
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
|
||||||
|
|
||||||
|
|
||||||
@ -51,11 +53,26 @@ let dist = $'($env.GITHUB_WORKSPACE)/output'
|
|||||||
let version = (open Cargo.toml | get package.version)
|
let version = (open Cargo.toml | get package.version)
|
||||||
|
|
||||||
print $'Debugging info:'
|
print $'Debugging info:'
|
||||||
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
print { version: $version, bin: $bin, os: $os, releaseType: $env.RELEASE_TYPE, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
||||||
|
|
||||||
|
# Rename the full release name so that we won't break the existing scripts for standard release downloading, such as:
|
||||||
|
# curl -s https://api.github.com/repos/chmln/sd/releases/latest | grep browser_download_url | cut -d '"' -f 4 | grep x86_64-unknown-linux-musl
|
||||||
|
const FULL_RLS_NAMING = {
|
||||||
|
x86_64-apple-darwin: 'x86_64-darwin-full',
|
||||||
|
aarch64-apple-darwin: 'aarch64-darwin-full',
|
||||||
|
x86_64-unknown-linux-gnu: 'x86_64-linux-gnu-full',
|
||||||
|
x86_64-pc-windows-msvc: 'x86_64-windows-msvc-full',
|
||||||
|
x86_64-unknown-linux-musl: 'x86_64-linux-musl-full',
|
||||||
|
aarch64-unknown-linux-gnu: 'aarch64-linux-gnu-full',
|
||||||
|
aarch64-pc-windows-msvc: 'aarch64-windows-msvc-full',
|
||||||
|
riscv64gc-unknown-linux-gnu: 'riscv64-linux-gnu-full',
|
||||||
|
armv7-unknown-linux-gnueabihf: 'armv7-linux-gnueabihf-full',
|
||||||
|
}
|
||||||
|
|
||||||
# $env
|
# $env
|
||||||
|
|
||||||
let USE_UBUNTU = 'ubuntu-20.04'
|
let USE_UBUNTU = 'ubuntu-20.04'
|
||||||
|
let FULL_NAME = $FULL_RLS_NAMING | get -i $target | default 'unknown-target-full'
|
||||||
|
|
||||||
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||||
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||||
@ -65,8 +82,8 @@ print $'Start building ($bin)...'; hr-line
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Build for Ubuntu and macOS
|
# Build for Ubuntu and macOS
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
||||||
if $os == $USE_UBUNTU {
|
if $os starts-with ubuntu {
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt-get install libxcb-composite0-dev -y
|
sudo apt-get install libxcb-composite0-dev -y
|
||||||
}
|
}
|
||||||
@ -89,7 +106,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
_ => {
|
_ => {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
if $os starts-with ubuntu { sudo apt install musl-tools -y }
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,10 +136,8 @@ print (ls -f $executable); sleep 1sec
|
|||||||
print $'(char nl)Copying release files...'; hr-line
|
print $'(char nl)Copying release files...'; hr-line
|
||||||
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||||
|
|
||||||
> register ./nu_plugin_query" | save $'($dist)/README.txt'
|
> register ./nu_plugin_query" | save $'($dist)/README.txt' -f
|
||||||
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
||||||
# Sleep a few seconds to make sure the cp process finished successfully
|
|
||||||
sleep 3sec
|
|
||||||
|
|
||||||
print $'(char nl)Check binary release version detail:'; hr-line
|
print $'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os == 'windows-latest' {
|
let ver = if $os == 'windows-latest' {
|
||||||
@ -131,17 +146,17 @@ let ver = if $os == 'windows-latest' {
|
|||||||
(do -i { ./output/nu -c 'version' }) | str join
|
(do -i { ./output/nu -c 'version' }) | str join
|
||||||
}
|
}
|
||||||
if ($ver | str trim | is-empty) {
|
if ($ver | str trim | is-empty) {
|
||||||
print $'(ansi r)Incompatible nu binary...(ansi reset)'
|
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
|
||||||
} else { print $ver }
|
} else { print $ver }
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Create a release archive and send it to output for the following steps
|
# Create a release archive and send it to output for the following steps
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
||||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
||||||
|
|
||||||
let files = (ls | get name)
|
let files = (ls | get name)
|
||||||
let dest = $'($bin)-($version)-($target)'
|
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
|
||||||
let archive = $'($dist)/($dest).tar.gz'
|
let archive = $'($dist)/($dest).tar.gz'
|
||||||
|
|
||||||
mkdir $dest
|
mkdir $dest
|
||||||
@ -156,7 +171,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
|
|
||||||
} else if $os == 'windows-latest' {
|
} else if $os == 'windows-latest' {
|
||||||
|
|
||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
|
||||||
|
|
||||||
print $'(char nl)Download less related stuffs...'; hr-line
|
print $'(char nl)Download less related stuffs...'; hr-line
|
||||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||||
@ -173,7 +188,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
cargo install cargo-wix --version 0.3.4
|
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
|
||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
let archive = ($wixRelease | str replace -a '\' '/')
|
let archive = ($wixRelease | str replace --all '\' '/')
|
||||||
print $'archive: ---> ($archive)';
|
print $'archive: ---> ($archive)';
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
@ -185,7 +200,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | is-empty) {
|
if not ($pkg | is-empty) {
|
||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
let archive = ($pkg | get 0 | str replace -a '\' '/')
|
let archive = ($pkg | get 0 | str replace --all '\' '/')
|
||||||
print $'archive: ---> ($archive)'
|
print $'archive: ---> ($archive)'
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
}
|
}
|
||||||
@ -210,7 +225,7 @@ def 'cargo-build-nu' [ options: string ] {
|
|||||||
|
|
||||||
# Print a horizontal line marker
|
# Print a horizontal line marker
|
||||||
def 'hr-line' [
|
def 'hr-line' [
|
||||||
--blank-line(-b): bool
|
--blank-line(-b)
|
||||||
] {
|
] {
|
||||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||||
if $blank_line { char nl }
|
if $blank_line { char nl }
|
||||||
|
109
.github/workflows/release.yml
vendored
109
.github/workflows/release.yml
vendored
@ -14,8 +14,8 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
all:
|
standard:
|
||||||
name: All
|
name: Std
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -64,10 +64,10 @@ jobs:
|
|||||||
target_rustflags: ''
|
target_rustflags: ''
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-latest
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
@ -79,14 +79,15 @@ jobs:
|
|||||||
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.5.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.6
|
uses: hustcer/setup-nu@v3.8
|
||||||
with:
|
with:
|
||||||
version: 0.84.0
|
version: 0.86.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -94,6 +95,98 @@ jobs:
|
|||||||
id: nu
|
id: nu
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
|
RELEASE_TYPE: standard
|
||||||
|
OS: ${{ matrix.os }}
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
|
_EXTRA_: ${{ matrix.extra }}
|
||||||
|
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
|
||||||
|
|
||||||
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
|
- name: Publish Archive
|
||||||
|
uses: softprops/action-gh-release@v0.1.15
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: ${{ steps.nu.outputs.archive }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
full:
|
||||||
|
name: Full
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- aarch64-apple-darwin
|
||||||
|
- x86_64-apple-darwin
|
||||||
|
- x86_64-pc-windows-msvc
|
||||||
|
- aarch64-pc-windows-msvc
|
||||||
|
- x86_64-unknown-linux-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
- aarch64-unknown-linux-gnu
|
||||||
|
extra: ['bin']
|
||||||
|
include:
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: 'bin'
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
extra: msi
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
extra: 'bin'
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
extra: msi
|
||||||
|
os: windows-latest
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
- target: aarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target_rustflags: '--features=dataframe,extra'
|
||||||
|
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Update Rust Toolchain Target
|
||||||
|
run: |
|
||||||
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
|
- name: Setup Rust toolchain and cache
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
||||||
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
|
with:
|
||||||
|
rustflags: ''
|
||||||
|
|
||||||
|
- name: Setup Nushell
|
||||||
|
uses: hustcer/setup-nu@v3.8
|
||||||
|
with:
|
||||||
|
version: 0.86.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Release Nu Binary
|
||||||
|
id: nu
|
||||||
|
run: nu .github/workflows/release-pkg.nu
|
||||||
|
env:
|
||||||
|
RELEASE_TYPE: full
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,6 +10,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.16.11
|
uses: crate-ci/typos@v1.17.0
|
||||||
with:
|
with:
|
||||||
config: ./.github/.typos.toml
|
config: ./.github/.typos.toml
|
||||||
|
3
.github/workflows/winget-submission.yml
vendored
3
.github/workflows/winget-submission.yml
vendored
@ -20,6 +20,9 @@ jobs:
|
|||||||
uses: vedantmgoyal2009/winget-releaser@v2
|
uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
identifier: Nushell.Nushell
|
identifier: Nushell.Nushell
|
||||||
|
# Exclude all `*-msvc-full.msi` full release files,
|
||||||
|
# and only the default `*msvc.msi` files will be included
|
||||||
|
installers-regex: 'msvc\.msi$'
|
||||||
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
token: ${{ secrets.NUSHELL_PAT }}
|
token: ${{ secrets.NUSHELL_PAT }}
|
||||||
|
@ -10,11 +10,16 @@ Welcome to Nushell and thank you for considering contributing!
|
|||||||
- [Useful commands](#useful-commands)
|
- [Useful commands](#useful-commands)
|
||||||
- [Debugging tips](#debugging-tips)
|
- [Debugging tips](#debugging-tips)
|
||||||
- [Git etiquette](#git-etiquette)
|
- [Git etiquette](#git-etiquette)
|
||||||
- [Our Rust style](#our-rust-style)
|
|
||||||
- [Generally discouraged](#generally-discouraged)
|
|
||||||
- [Things we want to get better at](#things-we-want-to-get-better-at)
|
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
## Other helpful resources
|
||||||
|
|
||||||
|
More resources can be found in the nascent [developer documentation](devdocs/README.md) in this repo.
|
||||||
|
|
||||||
|
- [Developer FAQ](FAQ.md)
|
||||||
|
- [Platform support policy](PLATFORM_SUPPORT.md)
|
||||||
|
- [Our Rust style](devdocs/rust_style.md)
|
||||||
|
|
||||||
## Proposing design changes
|
## Proposing design changes
|
||||||
|
|
||||||
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||||
@ -46,7 +51,7 @@ cargo build
|
|||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
It is a good practice to cover your changes with a test. Also, try to think about corner cases and various ways how your changes could break. Cover those in the tests as well.
|
It is good practice to cover your changes with a test. Also, try to think about corner cases and various ways how your changes could break. Cover those in the tests as well.
|
||||||
|
|
||||||
Tests can be found in different places:
|
Tests can be found in different places:
|
||||||
* `/tests`
|
* `/tests`
|
||||||
@ -58,79 +63,79 @@ Most of the tests are built upon the `nu-test-support` crate. For testing specif
|
|||||||
|
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
As Nushell is build using a cargo workspace consisting of multiple crates keep in mind that you may need to pass additional flags compared to how you may be used to it from a single crate project.
|
As Nushell is built using a cargo workspace consisting of multiple crates keep in mind that you may need to pass additional flags compared to how you may be used to it from a single crate project.
|
||||||
Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/reference/workspaces.html
|
Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/reference/workspaces.html
|
||||||
|
|
||||||
- Build and run Nushell:
|
- Build and run Nushell:
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
- Build and run with dataframe support.
|
- Build and run with dataframe support.
|
||||||
```shell
|
```nushell
|
||||||
cargo run --features=dataframe
|
cargo run --features=dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run Clippy on Nushell:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used
|
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used
|
||||||
```
|
```
|
||||||
or via the `toolkit.nu` command:
|
or via the `toolkit.nu` command:
|
||||||
```shell
|
```nushell
|
||||||
use toolkit.nu clippy
|
use toolkit.nu clippy
|
||||||
clippy
|
clippy
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests:
|
- Run all tests:
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
along with dataframe tests
|
along with dataframe tests
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo test --workspace --features=dataframe
|
cargo test --workspace --features=dataframe
|
||||||
```
|
```
|
||||||
or via the `toolkit.nu` command:
|
or via the `toolkit.nu` command:
|
||||||
```shell
|
```nushell
|
||||||
use toolkit.nu test
|
use toolkit.nu test
|
||||||
test
|
test
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run all tests for a specific command
|
- Run all tests for a specific command
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
cargo test --package nu-cli --test main -- commands::<command_name_here>
|
||||||
```
|
```
|
||||||
|
|
||||||
- Check to see if there are code formatting issues
|
- Check to see if there are code formatting issues
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
```
|
```
|
||||||
or via the `toolkit.nu` command:
|
or via the `toolkit.nu` command:
|
||||||
```shell
|
```nushell
|
||||||
use toolkit.nu fmt
|
use toolkit.nu fmt
|
||||||
fmt --check
|
fmt --check
|
||||||
```
|
```
|
||||||
|
|
||||||
- Format the code in the project
|
- Format the code in the project
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
```
|
```
|
||||||
or via the `toolkit.nu` command:
|
or via the `toolkit.nu` command:
|
||||||
```shell
|
```nushell
|
||||||
use toolkit.nu fmt
|
use toolkit.nu fmt
|
||||||
fmt
|
fmt
|
||||||
```
|
```
|
||||||
|
|
||||||
- Set up `git` hooks to check formatting and run `clippy` before committing and pushing:
|
- Set up `git` hooks to check formatting and run `clippy` before committing and pushing:
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
use toolkit.nu setup-git-hooks
|
use toolkit.nu setup-git-hooks
|
||||||
setup-git-hooks
|
setup-git-hooks
|
||||||
```
|
```
|
||||||
@ -140,12 +145,12 @@ Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/ref
|
|||||||
|
|
||||||
- To view verbose logs when developing, enable the `trace` log level.
|
- To view verbose logs when developing, enable the `trace` log level.
|
||||||
|
|
||||||
```shell
|
```nushell
|
||||||
cargo run --release -- --log-level trace
|
cargo run --release -- --log-level trace
|
||||||
```
|
```
|
||||||
|
|
||||||
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
||||||
```shell
|
```nushell
|
||||||
cargo run --release -- --log-level trace --log-target file
|
cargo run --release -- --log-level trace --log-target file
|
||||||
open $"($nu.temp-path)/nu-($nu.pid).log"
|
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||||
```
|
```
|
||||||
@ -237,51 +242,6 @@ You can help us to make the review process a smooth experience:
|
|||||||
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
|
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
|
||||||
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
|
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
|
||||||
|
|
||||||
## Our Rust style
|
|
||||||
To make the collaboration on a project the scale of Nushell easy, we want to work towards a style of Rust code that can easily be understood by all of our contributors. We conservatively rely on most of [`clippy`s suggestions](https://github.com/rust-lang/rust-clippy) to get to the holy grail of "idiomatic" code. Good code in our eyes is not the most clever use of all available language features or with the most unique personal touch but readable and strikes a balance between being concise, and also unsurprising and explicit in the places where it matters.
|
|
||||||
One example of this philosophy is that we generally avoid to fight the borrow-checker in our data model but rather try to get to a correct and simple solution first and then figure out where we should reuse data to achieve the necessary performance. As we are still pre-1.0 this served us well to be able to quickly refactor or change larger parts of the code base.
|
|
||||||
|
|
||||||
### Generally discouraged
|
|
||||||
#### `+nightly` language features or things only available in the most recent `+stable`
|
|
||||||
To make life for the people easier that maintain the Nushell packages in various distributions with their own release cycle of `rustc` we typically rely on slightly older Rust versions. We do not make explicit guarantees how far back in the past we live but you can find out in our [`rust-toolchain.toml`](https://github.com/nushell/nushell/blob/main/rust-toolchain.toml)
|
|
||||||
(As a rule of thumb this has been typically been approximately 2 releases behind the newest stable compiler.)
|
|
||||||
The use of nightly features is prohibited.
|
|
||||||
|
|
||||||
#### Panicking
|
|
||||||
As Nushell aims to provide a reliable foundational way for folks to interact with their computer, we cannot carelessly crash the execution of their work by panicking Nushell.
|
|
||||||
Thus panicking is not an allowed error handling strategy for anything that could be triggered by user input OR behavior of the outside system. If Nushell panics this is a bug or we are against all odds already in an unrecoverable state (The system stopped cooperating, we went out of memory). The use of `.unwrap()` is thus outright banned and any uses of `.expect()` or related panicking macros like `unreachable!` should include a helpful description which assumptions have been violated.
|
|
||||||
|
|
||||||
#### `unsafe` code
|
|
||||||
For any use of `unsafe` code we need to require even higher standards and additional review. If you add or alter `unsafe` blocks you have to be familiar with the promises you need to uphold as found in the [Rustonomicon](https://doc.rust-lang.org/nomicon/intro.html). All `unsafe` uses should include `// SAFETY:` comments explaining how the invariants are upheld and thus alerting you what to watch out for when making a change.
|
|
||||||
##### FFI with system calls and the outside world
|
|
||||||
As a shell Nushell needs to interact with system APIs in several places, for which FFI code with unsafe blocks may be necessary. In some cases this can be handled by safe API wrapper crates but in some cases we may choose to directly do those calls.
|
|
||||||
If you do so you need to document the system behavior on top of the Rust memory model guarantees that you uphold. This means documenting whether using a particular system call is safe to use in a particular context and all failure cases are properly recovered.
|
|
||||||
##### Implementing self-contained data structures
|
|
||||||
Another motivation for reaching to `unsafe` code might be to try to implement a particular data structure that is not expressible on safe `std` library APIs. Doing so in the Nushell code base would have to clear a high bar for need based on profiling results. Also you should first do a survey of the [crate ecosystem](https://crates.io) that there doesn't exist a usable well vetted crate that already provides safe APIs to the desired datastructure.
|
|
||||||
##### Make things go faster by removing checks
|
|
||||||
This is probably a bad idea if you feel tempted to do so. Don't
|
|
||||||
#### Macros
|
|
||||||
Another advanced feature people feel tempted to use to work around perceived limitations of Rusts syntax and we are not particularly fans of are custom macros.
|
|
||||||
They have clear downsides not only in terms of readability if they locally introduce a different syntax. Most tooling apart from the compiler will struggle more with them. This limits for example consistent automatic formatting or automated refactors with `rust-analyzer`.
|
|
||||||
That you can fluently read `macro_rules!` is less likely than regular code. This can lead people to introduce funky behavior when using a macro. Be it because a macro is not following proper hygiene rules or because it introduces excessive work at compile time.
|
|
||||||
|
|
||||||
So we generally discourage the addition of macros. In a lot of cases your macro may start do something that can be expressed with functions or generics in a much more reusable fashion.
|
|
||||||
The only exceptions we may allow need to demonstrate that the macro can fix something that is otherwise extremely unreadable, error-prone, or consistently worse at compile time.
|
|
||||||
### Things we want to get better at
|
|
||||||
These are things we did pretty liberally to get Nushell off the ground, that make things harder for a high quality stable product. You may run across them but shouldn't take them as an endorsed example.
|
|
||||||
#### Liberal use of third-party dependencies
|
|
||||||
The amazing variety of crates on [crates.io](https://crates.io) allowed us to quickly get Nushell into a feature rich state but it left us with a bunch of baggage to clean up.
|
|
||||||
Each dependency introduces a compile time cost and duplicated code can add to the overall binary size. Also vetting more for correct and secure implementations takes unreasonably more time as this is also a continuous process of reacting to updates or potential vulnerabilities.
|
|
||||||
|
|
||||||
Thus we only want to accept dependencies that are essential and well tested implementations of a particular requirement of Nushells codebase.
|
|
||||||
Also as a project for the move to 1.0 we will try to unify among a set of dependencies if they possibly implement similar things in an area. We don't need three different crates with potentially perfect fit for three problems but rather one reliable crate with a maximized overlap between what it provides and what we need.
|
|
||||||
We will favor crates that are well tested and used and promise to be more stable and still frequently maintained.
|
|
||||||
#### Deeply nested code
|
|
||||||
As Nushell uses a lot of enums in its internal data representation there are a lot of `match` expressions. Combined with the need to handle a lot of edge cases and be defensive about any errors this has led to some absolutely hard to read deeply nested code (e.g. in the parser but also in the implementation of several commands).
|
|
||||||
This can be observed both as a "rightward drift" where the main part of the code is found after many levels of indentations or by long function bodies with several layers of branching with seemingly repeated branching inside the higher branch level.
|
|
||||||
This can also be exacerbated by "quick" bugfixes/enhancements that may just try to add a special case to catch a previously unexpected condition. The likelihood of introducing a bug in a sea of code duplication is high.
|
|
||||||
To combat this, consider using the early-`return` pattern to reject invalid data early in one place instead of building a tree through Rust's expression constructs with a lot of duplicated paths. Unpacking data into a type that expresses that the necessary things already have been checked and using functions to properly deal with separate and common behavior can also help.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
We use the [MIT License](https://github.com/nushell/nushell/blob/main/LICENSE) in all of our Nushell projects. If you are including or referencing a crate that uses the [GPL License](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) unfortunately we will not be able to accept your PR.
|
We use the [MIT License](https://github.com/nushell/nushell/blob/main/LICENSE) in all of our Nushell projects. If you are including or referencing a crate that uses the [GPL License](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) unfortunately we will not be able to accept your PR.
|
||||||
|
2268
Cargo.lock
generated
2268
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
60
Cargo.toml
60
Cargo.toml
@ -10,8 +10,8 @@ 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.72.1"
|
||||||
version = "0.85.0"
|
version = "0.88.2"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -33,6 +33,11 @@ members = [
|
|||||||
"crates/nu-cmd-lang",
|
"crates/nu-cmd-lang",
|
||||||
"crates/nu-cmd-dataframe",
|
"crates/nu-cmd-dataframe",
|
||||||
"crates/nu-command",
|
"crates/nu-command",
|
||||||
|
"crates/nu-color-config",
|
||||||
|
"crates/nu-explore",
|
||||||
|
"crates/nu-json",
|
||||||
|
"crates/nu-lsp",
|
||||||
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
"crates/nu_plugin_inc",
|
"crates/nu_plugin_inc",
|
||||||
@ -42,32 +47,36 @@ members = [
|
|||||||
"crates/nu_plugin_custom_values",
|
"crates/nu_plugin_custom_values",
|
||||||
"crates/nu_plugin_formats",
|
"crates/nu_plugin_formats",
|
||||||
"crates/nu-std",
|
"crates/nu-std",
|
||||||
|
"crates/nu-table",
|
||||||
|
"crates/nu-term-grid",
|
||||||
"crates/nu-utils",
|
"crates/nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.85.0" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.88.2" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.85.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.88.2" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.85.0" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.88.2" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.85.0" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.88.2" }
|
||||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.85.0", features = ["dataframe"], optional = true }
|
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.88.2", features = ["dataframe"], optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.85.0", optional = true }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.88.2", optional = true }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.85.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.88.2" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.85.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.88.2" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.85.0" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.88.2" }
|
||||||
nu-json = { path = "./crates/nu-json", version = "0.85.0" }
|
nu-json = { path = "./crates/nu-json", version = "0.88.2" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.85.0" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.88.2" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.85.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.88.2" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.85.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.88.2" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.85.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.88.2" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.85.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.88.2" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.85.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.88.2" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.85.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.88.2" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.85.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.88.2" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.85.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.88.2" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.85.0" }
|
nu-std = { path = "./crates/nu-std", version = "0.88.2" }
|
||||||
|
nu-utils = { path = "./crates/nu-utils", version = "0.88.2" }
|
||||||
|
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.24.0", features = ["bashisms", "sqlite"] }
|
reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
@ -95,7 +104,7 @@ nix = { version = "0.27", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.85.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.88.2" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
criterion = "0.5"
|
criterion = "0.5"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
@ -164,8 +173,9 @@ 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"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
|
# uu_cp = { git = "https://github.com/uutils/coreutils.git", branch = "main" }
|
||||||
|
|
||||||
# Criterion benchmarking setup
|
# Criterion benchmarking setup
|
||||||
# Run all benchmarks with `cargo bench`
|
# Run all benchmarks with `cargo bench`
|
||||||
|
@ -54,6 +54,7 @@ Detailed installation instructions can be found in the [installation chapter of
|
|||||||
|
|
||||||
[](https://repology.org/project/nushell/versions)
|
[](https://repology.org/project/nushell/versions)
|
||||||
|
|
||||||
|
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -198,7 +199,7 @@ topics that have been presented.
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has first-class support for Windows, macOS, and Linux.
|
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has [first-class support for Windows, macOS, and Linux](devdocs/PLATFORM_SUPPORT.md).
|
||||||
|
|
||||||
- Nu ensures compatibility with existing platform-specific executables.
|
- Nu ensures compatibility with existing platform-specific executables.
|
||||||
|
|
||||||
@ -220,6 +221,7 @@ Please submit an issue or PR to be added to this list.
|
|||||||
- [atuin](https://github.com/ellie/atuin)
|
- [atuin](https://github.com/ellie/atuin)
|
||||||
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
||||||
- [Dorothy](http://github.com/bevry/dorothy)
|
- [Dorothy](http://github.com/bevry/dorothy)
|
||||||
|
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -4,10 +4,31 @@ use nu_parser::parse;
|
|||||||
use nu_plugin::{EncodingType, PluginResponse};
|
use nu_plugin::{EncodingType, PluginResponse};
|
||||||
use nu_protocol::{engine::EngineState, PipelineData, Span, Value};
|
use nu_protocol::{engine::EngineState, PipelineData, Span, Value};
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
fn load_bench_commands() -> EngineState {
|
fn load_bench_commands() -> EngineState {
|
||||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
||||||
|
let cwd = engine_state.current_work_dir();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
match nu_path::canonicalize_with(path, cwd) {
|
||||||
|
Ok(canon_path) => canon_path,
|
||||||
|
Err(_) => path.to_owned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home_path(engine_state: &EngineState) -> PathBuf {
|
||||||
|
nu_path::home_dir()
|
||||||
|
.map(|path| canonicalize_path(engine_state, &path))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
// 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
|
// 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
|
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||||
@ -15,10 +36,12 @@ fn load_bench_commands() -> EngineState {
|
|||||||
|
|
||||||
fn parser_benchmarks(c: &mut Criterion) {
|
fn parser_benchmarks(c: &mut Criterion) {
|
||||||
let mut engine_state = load_bench_commands();
|
let mut engine_state = load_bench_commands();
|
||||||
// parsing config.nu breaks without PWD set
|
let home_path = get_home_path(&engine_state);
|
||||||
|
|
||||||
|
// parsing config.nu breaks without PWD set, so set a valid path
|
||||||
engine_state.add_env_var(
|
engine_state.add_env_var(
|
||||||
"PWD".into(),
|
"PWD".into(),
|
||||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let default_env = get_default_env().as_bytes();
|
let default_env = get_default_env().as_bytes();
|
||||||
@ -41,7 +64,6 @@ fn parser_benchmarks(c: &mut Criterion) {
|
|||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
c.bench_function("eval default_env.nu", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let mut engine_state = load_bench_commands();
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
eval_source(
|
eval_source(
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
@ -56,12 +78,6 @@ fn parser_benchmarks(c: &mut Criterion) {
|
|||||||
|
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
c.bench_function("eval default_config.nu", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let mut engine_state = load_bench_commands();
|
|
||||||
// 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();
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
eval_source(
|
eval_source(
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
@ -76,9 +92,17 @@ fn parser_benchmarks(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn eval_benchmarks(c: &mut Criterion) {
|
fn eval_benchmarks(c: &mut Criterion) {
|
||||||
|
let mut engine_state = load_bench_commands();
|
||||||
|
let home_path = get_home_path(&engine_state);
|
||||||
|
|
||||||
|
// parsing config.nu breaks without PWD set, so set a valid path
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
||||||
|
);
|
||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
c.bench_function("eval default_env.nu", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let mut engine_state = load_bench_commands();
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
eval_source(
|
eval_source(
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
@ -93,12 +117,6 @@ fn eval_benchmarks(c: &mut Criterion) {
|
|||||||
|
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
c.bench_function("eval default_config.nu", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let mut engine_state = load_bench_commands();
|
|
||||||
// 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();
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
eval_source(
|
eval_source(
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
|
@ -5,39 +5,42 @@ 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.85.0"
|
version = "0.88.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.85.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.85.0" }
|
nu-command = { path = "../nu-command", version = "0.88.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.85.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
||||||
rstest = { version = "0.18.1", default-features = false }
|
rstest = { version = "0.18.1", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.85.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.88.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.85.0" }
|
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.85.0" }
|
nu-path = { path = "../nu-path", version = "0.88.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.85.0" }
|
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.85.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.85.0" }
|
nu-utils = { path = "../nu-utils", version = "0.88.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.85.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.88.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.24.0", features = ["bashisms", "sqlite"] }
|
reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
fancy-regex = "0.11"
|
fancy-regex = "0.12"
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = "0.3"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
sysinfo = "0.29"
|
pathdiff = "0.2"
|
||||||
|
sysinfo = "0.30"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
|
uuid = { version = "1.6.0", features = ["v4"] }
|
||||||
|
which = "5.0.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -91,7 +91,7 @@ impl Command for Commandline {
|
|||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
span: cmd.span(),
|
span: cmd.span(),
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
r#"string "{cmd_str}" does not represent a valid integer"#
|
r#"string "{cmd_str}" does not represent a valid int"#
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ impl Command for History {
|
|||||||
"Show long listing of entries for sqlite history",
|
"Show long listing of entries for sqlite history",
|
||||||
Some('l'),
|
Some('l'),
|
||||||
)
|
)
|
||||||
.category(Category::Misc)
|
.category(Category::History)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -107,7 +107,7 @@ impl Command for History {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound(head))?
|
.ok_or(ShellError::FileNotFound { span: head })?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
@ -119,12 +119,12 @@ impl Command for History {
|
|||||||
create_history_record(idx, entry, long, head)
|
create_history_record(idx, entry, long, head)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound(head))?
|
.ok_or(ShellError::FileNotFound { span: head })?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::FileNotFound(head))
|
Err(ShellError::FileNotFound { span: head })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ impl Command for HistorySession {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("history session")
|
Signature::build("history session")
|
||||||
.category(Category::Misc)
|
.category(Category::History)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||||
}
|
}
|
||||||
|
|
5
crates/nu-cli/src/commands/history/mod.rs
Normal file
5
crates/nu-cli/src/commands/history/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod history_;
|
||||||
|
mod history_session;
|
||||||
|
|
||||||
|
pub use history_::History;
|
||||||
|
pub use history_session::HistorySession;
|
@ -36,7 +36,7 @@ impl Command for KeybindingsList {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Get list of key modifiers",
|
description: "Get list of key modifiers",
|
||||||
example: "keybindings list -m",
|
example: "keybindings list --modifiers",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crossterm::execute;
|
||||||
use crossterm::QueueableCommand;
|
use crossterm::QueueableCommand;
|
||||||
use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
|
use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
@ -44,13 +45,13 @@ impl Command for KeybindingsListen {
|
|||||||
Ok(v) => Ok(v.into_pipeline_data()),
|
Ok(v) => Ok(v.into_pipeline_data()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode()?;
|
||||||
Err(ShellError::GenericError(
|
Err(ShellError::GenericError {
|
||||||
"Error with input".to_string(),
|
error: "Error with input".into(),
|
||||||
"".to_string(),
|
msg: "".into(),
|
||||||
None,
|
span: None,
|
||||||
Some(e.to_string()),
|
help: Some(e.to_string()),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,6 +70,32 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
|
|
||||||
stdout().flush()?;
|
stdout().flush()?;
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
|
if config.use_kitty_protocol {
|
||||||
|
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
||||||
|
println!("WARN: The terminal doesn't support use_kitty_protocol config.\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable kitty protocol
|
||||||
|
//
|
||||||
|
// Note that, currently, only the following support this protocol:
|
||||||
|
// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
|
||||||
|
// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
|
||||||
|
// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
|
||||||
|
// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
|
||||||
|
// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
|
||||||
|
// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
|
||||||
|
// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
|
||||||
|
//
|
||||||
|
// Refer to https://sw.kovidgoyal.net/kitty/keyboard-protocol/ if you're curious.
|
||||||
|
let _ = execute!(
|
||||||
|
stdout(),
|
||||||
|
crossterm::event::PushKeyboardEnhancementFlags(
|
||||||
|
crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -95,6 +122,14 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
stdout.queue(crossterm::style::Print("\r\n"))?;
|
stdout.queue(crossterm::style::Print("\r\n"))?;
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.use_kitty_protocol {
|
||||||
|
let _ = execute!(
|
||||||
|
std::io::stdout(),
|
||||||
|
crossterm::event::PopKeyboardEnhancementFlags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode()?;
|
||||||
|
|
||||||
Ok(Value::nothing(Span::unknown()))
|
Ok(Value::nothing(Span::unknown()))
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
mod commandline;
|
mod commandline;
|
||||||
mod default_context;
|
mod default_context;
|
||||||
mod history;
|
mod history;
|
||||||
mod history_session;
|
|
||||||
mod keybindings;
|
mod keybindings;
|
||||||
mod keybindings_default;
|
mod keybindings_default;
|
||||||
mod keybindings_list;
|
mod keybindings_list;
|
||||||
mod keybindings_listen;
|
mod keybindings_listen;
|
||||||
|
|
||||||
pub use commandline::Commandline;
|
pub use commandline::Commandline;
|
||||||
pub use history::History;
|
pub use history::{History, HistorySession};
|
||||||
pub use history_session::HistorySession;
|
|
||||||
pub use keybindings::Keybindings;
|
pub use keybindings::Keybindings;
|
||||||
pub use keybindings_default::KeybindingsDefault;
|
pub use keybindings_default::KeybindingsDefault;
|
||||||
pub use keybindings_list::KeybindingsList;
|
pub use keybindings_list::KeybindingsList;
|
||||||
|
@ -234,7 +234,7 @@ pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usiz
|
|||||||
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
||||||
|
|
||||||
let result = match contents.get(cur_pos..) {
|
let result = match contents.get(cur_pos..) {
|
||||||
Some(contents) => contents.starts_with(b"sudo "),
|
Some(contents) => contents.starts_with(b"sudo ") || contents.starts_with(b"doas "),
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
if result {
|
if result {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
@ -39,15 +39,12 @@ impl NuCompleter {
|
|||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let mut options = CompletionOptions {
|
let options = CompletionOptions {
|
||||||
case_sensitive: config.case_sensitive_completions,
|
case_sensitive: config.case_sensitive_completions,
|
||||||
|
match_algorithm: config.completion_algorithm.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if config.completion_algorithm == "fuzzy" {
|
|
||||||
options.match_algorithm = MatchAlgorithm::Fuzzy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
let mut suggestions =
|
let mut suggestions =
|
||||||
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
|
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
|
||||||
@ -70,7 +67,7 @@ impl NuCompleter {
|
|||||||
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
||||||
|
|
||||||
// Line
|
// Line
|
||||||
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
if let Some(pos_arg) = block.signature.required_positional.first() {
|
||||||
if let Some(var_id) = pos_arg.var_id {
|
if let Some(var_id) = pos_arg.var_id {
|
||||||
callee_stack.add_var(
|
callee_stack.add_var(
|
||||||
var_id,
|
var_id,
|
||||||
@ -125,35 +122,45 @@ impl NuCompleter {
|
|||||||
for pipeline_element in pipeline.elements {
|
for pipeline_element in pipeline.elements {
|
||||||
match pipeline_element {
|
match pipeline_element {
|
||||||
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::SameTargetRedirection { cmd: (_, expr), .. }
|
| PipelineElement::SameTargetRedirection { cmd: (_, expr), .. }
|
||||||
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
|
| PipelineElement::SeparateRedirection {
|
||||||
|
out: (_, expr, _), ..
|
||||||
|
} => {
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||||
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
|
let is_passthrough_command = spans
|
||||||
.first()
|
.first()
|
||||||
.filter(|content| content.as_str() == "sudo")
|
.filter(|content| {
|
||||||
|
content.as_str() == "sudo" || content.as_str() == "doas"
|
||||||
|
})
|
||||||
.is_some();
|
.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);
|
||||||
|
|
||||||
|
let is_last_span = pos >= flat.0.start && pos < flat.0.end;
|
||||||
|
|
||||||
// Skip the last 'a' as span item
|
// Skip the last 'a' as span item
|
||||||
if flat_idx == flattened.len() - 1 {
|
if is_last_span {
|
||||||
let mut chars = current_span_str.chars();
|
let offset = pos - flat.0.start;
|
||||||
chars.next_back();
|
if offset == 0 {
|
||||||
let current_span_str = chars.as_str().to_owned();
|
spans.push(String::new())
|
||||||
spans.push(current_span_str.to_string());
|
} else {
|
||||||
|
let mut current_span_str = current_span_str.to_string();
|
||||||
|
current_span_str.remove(offset);
|
||||||
|
spans.push(current_span_str);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
spans.push(current_span_str.to_string());
|
spans.push(current_span_str.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete based on the last span
|
// Complete based on the last span
|
||||||
if pos >= flat.0.start && pos < flat.0.end {
|
if is_last_span {
|
||||||
// Context variables
|
// Context variables
|
||||||
let most_left_var =
|
let most_left_var =
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
@ -243,7 +250,9 @@ impl NuCompleter {
|
|||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||||
|
|
||||||
// Completion for .nu files
|
// Completion for .nu files
|
||||||
if prev_expr_str == b"use" || prev_expr_str == b"source-env"
|
if prev_expr_str == b"use"
|
||||||
|
|| prev_expr_str == b"overlay use"
|
||||||
|
|| prev_expr_str == b"source-env"
|
||||||
{
|
{
|
||||||
let mut completer =
|
let mut completer =
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
DotNuCompletion::new(self.engine_state.clone());
|
||||||
|
199
crates/nu-cli/src/completions/completion_common.rs
Normal file
199
crates/nu-cli/src/completions/completion_common.rs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
use crate::completions::{matches, CompletionOptions};
|
||||||
|
use nu_path::home_dir;
|
||||||
|
use nu_protocol::{engine::StateWorkingSet, Span};
|
||||||
|
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
||||||
|
|
||||||
|
fn complete_rec(
|
||||||
|
partial: &[String],
|
||||||
|
cwd: &Path,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
dir: bool,
|
||||||
|
isdir: bool,
|
||||||
|
) -> Vec<PathBuf> {
|
||||||
|
let mut completions = vec![];
|
||||||
|
|
||||||
|
if let Ok(result) = cwd.read_dir() {
|
||||||
|
for entry in result.filter_map(|e| e.ok()) {
|
||||||
|
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if !dir || path.is_dir() {
|
||||||
|
match partial.first() {
|
||||||
|
Some(base) if matches(base, &entry_name, options) => {
|
||||||
|
let partial = &partial[1..];
|
||||||
|
if !partial.is_empty() || isdir {
|
||||||
|
completions.extend(complete_rec(partial, &path, options, dir, isdir));
|
||||||
|
if entry_name.eq(base) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completions.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => completions.push(path),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completions
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OriginalCwd {
|
||||||
|
None,
|
||||||
|
Home(PathBuf),
|
||||||
|
Some(PathBuf),
|
||||||
|
// referencing a single local file
|
||||||
|
Local(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginalCwd {
|
||||||
|
fn apply(&self, p: &Path) -> String {
|
||||||
|
let mut ret = match self {
|
||||||
|
Self::None => p.to_string_lossy().into_owned(),
|
||||||
|
Self::Some(base) => pathdiff::diff_paths(p, base)
|
||||||
|
.unwrap_or(p.to_path_buf())
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned(),
|
||||||
|
Self::Home(home) => match p.strip_prefix(home) {
|
||||||
|
Ok(suffix) => format!("~{}{}", SEP, suffix.to_string_lossy()),
|
||||||
|
_ => p.to_string_lossy().into_owned(),
|
||||||
|
},
|
||||||
|
Self::Local(base) => Path::new(".")
|
||||||
|
.join(pathdiff::diff_paths(p, base).unwrap_or(p.to_path_buf()))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if p.is_dir() {
|
||||||
|
ret.push(SEP);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surround_remove(partial: &str) -> String {
|
||||||
|
for c in ['`', '"', '\''] {
|
||||||
|
if partial.starts_with(c) {
|
||||||
|
let ret = partial.strip_prefix(c).unwrap_or(partial);
|
||||||
|
return match ret.split(c).collect::<Vec<_>>()[..] {
|
||||||
|
[inside] => inside.to_string(),
|
||||||
|
[inside, outside] if inside.ends_with(is_separator) => format!("{inside}{outside}"),
|
||||||
|
_ => ret.to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partial.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete_item(
|
||||||
|
want_directory: bool,
|
||||||
|
span: nu_protocol::Span,
|
||||||
|
partial: &str,
|
||||||
|
cwd: &str,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<(nu_protocol::Span, String)> {
|
||||||
|
let partial = surround_remove(partial);
|
||||||
|
let isdir = partial.ends_with(is_separator);
|
||||||
|
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
||||||
|
let mut original_cwd = OriginalCwd::None;
|
||||||
|
let mut components = Path::new(&partial).components().peekable();
|
||||||
|
let mut cwd = match components.peek().cloned() {
|
||||||
|
Some(c @ Component::Prefix(..)) => {
|
||||||
|
// windows only by definition
|
||||||
|
components.next();
|
||||||
|
if let Some(Component::RootDir) = components.peek().cloned() {
|
||||||
|
components.next();
|
||||||
|
};
|
||||||
|
[c, Component::RootDir].iter().collect()
|
||||||
|
}
|
||||||
|
Some(c @ Component::RootDir) => {
|
||||||
|
components.next();
|
||||||
|
PathBuf::from(c.as_os_str())
|
||||||
|
}
|
||||||
|
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
||||||
|
components.next();
|
||||||
|
original_cwd = OriginalCwd::Home(home_dir().unwrap_or(cwd_pathbuf.clone()));
|
||||||
|
home_dir().unwrap_or(cwd_pathbuf)
|
||||||
|
}
|
||||||
|
Some(Component::CurDir) => {
|
||||||
|
components.next();
|
||||||
|
original_cwd = match components.peek().cloned() {
|
||||||
|
Some(Component::Normal(_)) | None => OriginalCwd::Local(cwd_pathbuf.clone()),
|
||||||
|
_ => OriginalCwd::Some(cwd_pathbuf.clone()),
|
||||||
|
};
|
||||||
|
cwd_pathbuf
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
original_cwd = OriginalCwd::Some(cwd_pathbuf.clone());
|
||||||
|
cwd_pathbuf
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut partial = vec![];
|
||||||
|
|
||||||
|
for component in components {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(..) => unreachable!(),
|
||||||
|
Component::RootDir => unreachable!(),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
if partial.pop().is_none() {
|
||||||
|
cwd.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component::Normal(c) => partial.push(c.to_string_lossy().into_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| (span, escape_path(original_cwd.apply(&p), want_directory)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix files or folders with quotes or hashes
|
||||||
|
pub fn escape_path(path: String, dir: bool) -> String {
|
||||||
|
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
||||||
|
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
||||||
|
let maybe_flag = path.starts_with('-');
|
||||||
|
let maybe_number = path.parse::<f64>().is_ok();
|
||||||
|
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_number {
|
||||||
|
format!("`{path}`")
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AdjustView {
|
||||||
|
pub prefix: String,
|
||||||
|
pub span: Span,
|
||||||
|
pub readjusted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adjust_if_intermediate(
|
||||||
|
prefix: &[u8],
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
mut span: nu_protocol::Span,
|
||||||
|
) -> AdjustView {
|
||||||
|
let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
|
||||||
|
let mut prefix = String::from_utf8_lossy(prefix).to_string();
|
||||||
|
|
||||||
|
// A difference of 1 because of the cursor's unicode code point in between.
|
||||||
|
// Using .chars().count() because unicode and Windows.
|
||||||
|
let readjusted = span_contents.chars().count() - prefix.chars().count() > 1;
|
||||||
|
if readjusted {
|
||||||
|
let remnant: String = span_contents
|
||||||
|
.chars()
|
||||||
|
.skip(prefix.chars().count() + 1)
|
||||||
|
.take_while(|&c| !is_separator(c))
|
||||||
|
.collect();
|
||||||
|
prefix.push_str(&remnant);
|
||||||
|
span = Span::new(span.start, span.start + prefix.chars().count() + 1);
|
||||||
|
}
|
||||||
|
AdjustView {
|
||||||
|
prefix,
|
||||||
|
span,
|
||||||
|
readjusted,
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
|
use nu_protocol::CompletionAlgorithm;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
@ -55,6 +56,15 @@ impl MatchAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<CompletionAlgorithm> for MatchAlgorithm {
|
||||||
|
fn from(value: CompletionAlgorithm) -> Self {
|
||||||
|
match value {
|
||||||
|
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
||||||
|
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for MatchAlgorithm {
|
impl TryFrom<String> for MatchAlgorithm {
|
||||||
type Error = InvalidMatchAlgorithm;
|
type Error = InvalidMatchAlgorithm;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ use nu_protocol::{
|
|||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -79,21 +80,20 @@ impl Completer for CustomCompletion {
|
|||||||
.map(|pd| {
|
.map(|pd| {
|
||||||
let value = pd.into_value(span);
|
let value = pd.into_value(span);
|
||||||
match &value {
|
match &value {
|
||||||
Value::Record { .. } => {
|
Value::Record { val, .. } => {
|
||||||
let completions = value
|
let completions = val
|
||||||
.get_data_by_key("completions")
|
.get("completions")
|
||||||
.and_then(|val| {
|
.and_then(|val| {
|
||||||
val.as_list()
|
val.as_list()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|it| map_value_completions(it.iter(), span, offset))
|
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let options = value.get_data_by_key("options");
|
let options = val.get("options");
|
||||||
|
|
||||||
if let Some(Value::Record { .. }) = &options {
|
if let Some(Value::Record { val: options, .. }) = &options {
|
||||||
let options = options.unwrap_or_default();
|
|
||||||
let should_sort = options
|
let should_sort = options
|
||||||
.get_data_by_key("sort")
|
.get("sort")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
@ -103,11 +103,11 @@ impl Completer for CustomCompletion {
|
|||||||
|
|
||||||
custom_completion_options = Some(CompletionOptions {
|
custom_completion_options = Some(CompletionOptions {
|
||||||
case_sensitive: options
|
case_sensitive: options
|
||||||
.get_data_by_key("case_sensitive")
|
.get("case_sensitive")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(true),
|
.unwrap_or(true),
|
||||||
positional: options
|
positional: options
|
||||||
.get_data_by_key("positional")
|
.get("positional")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(true),
|
.unwrap_or(true),
|
||||||
sort_by: if should_sort {
|
sort_by: if should_sort {
|
||||||
@ -115,9 +115,7 @@ impl Completer for CustomCompletion {
|
|||||||
} else {
|
} else {
|
||||||
SortBy::None
|
SortBy::None
|
||||||
},
|
},
|
||||||
match_algorithm: match options
|
match_algorithm: match options.get("completion_algorithm") {
|
||||||
.get_data_by_key("completion_algorithm")
|
|
||||||
{
|
|
||||||
Some(option) => option
|
Some(option) => option
|
||||||
.as_string()
|
.as_string()
|
||||||
.ok()
|
.ok()
|
||||||
@ -156,8 +154,8 @@ fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) ->
|
|||||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
(true, true) => it.value.as_bytes().starts_with(prefix),
|
||||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||||
(false, positional) => {
|
(false, positional) => {
|
||||||
let value = it.value.to_lowercase();
|
let value = it.value.to_folded_case();
|
||||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_lowercase();
|
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
||||||
if positional {
|
if positional {
|
||||||
value.starts_with(&prefix)
|
value.starts_with(&prefix)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
use crate::completions::{matches, Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
|
Completer, CompletionOptions, SortBy,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::fs;
|
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{partial_from, prepend_base_dir, SortBy};
|
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DirectoryCompletion {
|
pub struct DirectoryCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
@ -26,18 +24,22 @@ impl DirectoryCompletion {
|
|||||||
impl Completer for DirectoryCompletion {
|
impl Completer for DirectoryCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
prefix: Vec<u8>,
|
prefix: Vec<u8>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = self.engine_state.current_work_dir();
|
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
|
||||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
let output: Vec<_> = directory_completion(span, &partial, &cwd, options)
|
let output: Vec<_> = directory_completion(
|
||||||
|
span,
|
||||||
|
&prefix,
|
||||||
|
&self.engine_state.current_work_dir(),
|
||||||
|
options,
|
||||||
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
@ -111,60 +113,5 @@ pub fn directory_completion(
|
|||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
) -> Vec<(nu_protocol::Span, String)> {
|
||||||
let original_input = partial;
|
complete_item(true, span, partial, cwd, options)
|
||||||
|
|
||||||
let (base_dir_name, partial) = partial_from(partial);
|
|
||||||
|
|
||||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
|
||||||
|
|
||||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
|
||||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
|
||||||
if base_dir == Path::new("") {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(result) = base_dir.read_dir() {
|
|
||||||
return result
|
|
||||||
.filter_map(|entry| {
|
|
||||||
entry.ok().and_then(|entry| {
|
|
||||||
if let Ok(metadata) = fs::metadata(entry.path()) {
|
|
||||||
if metadata.is_dir() {
|
|
||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
|
||||||
if matches(&partial, &file_name, options) {
|
|
||||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
|
||||||
format!("{base_dir_name}{file_name}")
|
|
||||||
} else {
|
|
||||||
file_name.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
if entry.path().is_dir() {
|
|
||||||
path.push(SEP);
|
|
||||||
file_name.push(SEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix files or folders with quotes or hash
|
|
||||||
if path.contains('\'')
|
|
||||||
|| path.contains('"')
|
|
||||||
|| path.contains(' ')
|
|
||||||
|| path.contains('#')
|
|
||||||
{
|
|
||||||
path = format!("`{path}`");
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((span, path))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec::new()
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
||||||
file_path_completion, partial_from, Completer, CompletionOptions, SortBy,
|
|
||||||
};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::sync::Arc;
|
use std::{
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DotNuCompletion {
|
pub struct DotNuCompletion {
|
||||||
@ -30,9 +30,16 @@ impl Completer for DotNuCompletion {
|
|||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
||||||
let mut search_dirs: Vec<String> = vec![];
|
let mut search_dirs: Vec<String> = vec![];
|
||||||
let (base_dir, mut partial) = partial_from(&prefix_str);
|
|
||||||
|
// If prefix_str is only a word we want to search in the current dir
|
||||||
|
let (base, partial) = prefix_str
|
||||||
|
.rsplit_once(is_separator)
|
||||||
|
.unwrap_or((".", &prefix_str));
|
||||||
|
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
|
||||||
|
let mut partial = partial.to_string();
|
||||||
|
// On windows, this standardizes paths to use \
|
||||||
let mut is_current_folder = false;
|
let mut is_current_folder = false;
|
||||||
|
|
||||||
// Fetch the lib dirs
|
// Fetch the lib dirs
|
||||||
@ -58,7 +65,8 @@ 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}") {
|
// rsplit_once removes the separator
|
||||||
|
if base_dir != "." {
|
||||||
// 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());
|
||||||
|
|
||||||
@ -83,17 +91,22 @@ impl Completer for DotNuCompletion {
|
|||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
let output: Vec<Suggestion> = search_dirs
|
let output: Vec<Suggestion> = search_dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|search_dir| {
|
||||||
file_path_completion(span, &partial, &it, options)
|
let completions = file_path_completion(span, &partial, &search_dir, options);
|
||||||
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| {
|
.filter(move |it| {
|
||||||
// Different base dir, so we list the .nu files or folders
|
// Different base dir, so we list the .nu files or folders
|
||||||
if !is_current_folder {
|
if !is_current_folder {
|
||||||
it.1.ends_with(".nu") || it.1.ends_with(SEP)
|
it.1.ends_with(".nu") || it.1.ends_with(SEP)
|
||||||
} else {
|
} else {
|
||||||
// Lib dirs, so we filter only the .nu files
|
// Lib dirs, so we filter only the .nu files or directory modules
|
||||||
|
if it.1.ends_with(SEP) {
|
||||||
|
Path::new(&search_dir).join(&it.1).join("mod.nu").exists()
|
||||||
|
} else {
|
||||||
it.1.ends_with(".nu")
|
it.1.ends_with(".nu")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
|
Completer, CompletionOptions, SortBy,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path};
|
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::SortBy;
|
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FileCompletion {
|
pub struct FileCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
@ -25,16 +25,26 @@ impl FileCompletion {
|
|||||||
impl Completer for FileCompletion {
|
impl Completer for FileCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
prefix: Vec<u8>,
|
prefix: Vec<u8>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = self.engine_state.current_work_dir();
|
let AdjustView {
|
||||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
prefix,
|
||||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
span,
|
||||||
|
readjusted,
|
||||||
|
} = adjust_if_intermediate(&prefix, working_set, span);
|
||||||
|
|
||||||
|
let output: Vec<_> = complete_item(
|
||||||
|
readjusted,
|
||||||
|
span,
|
||||||
|
&prefix,
|
||||||
|
&self.engine_state.current_work_dir(),
|
||||||
|
options,
|
||||||
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
@ -102,84 +112,13 @@ impl Completer for FileCompletion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn partial_from(input: &str) -> (String, String) {
|
|
||||||
let partial = input.replace('`', "");
|
|
||||||
|
|
||||||
// If partial is only a word we want to search in the current dir
|
|
||||||
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", &partial));
|
|
||||||
// On windows, this standardizes paths to use \
|
|
||||||
let mut base = base.replace(is_separator, &SEP.to_string());
|
|
||||||
|
|
||||||
// rsplit_once removes the separator
|
|
||||||
base.push(SEP);
|
|
||||||
|
|
||||||
(base.to_string(), rest.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_path_completion(
|
pub fn file_path_completion(
|
||||||
span: nu_protocol::Span,
|
span: nu_protocol::Span,
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
) -> Vec<(nu_protocol::Span, String)> {
|
||||||
let original_input = partial;
|
complete_item(false, span, partial, cwd, options)
|
||||||
let (base_dir_name, partial) = partial_from(partial);
|
|
||||||
|
|
||||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
|
||||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
|
||||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
|
||||||
if base_dir == Path::new("") {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(result) = base_dir.read_dir() {
|
|
||||||
return result
|
|
||||||
.filter_map(|entry| {
|
|
||||||
entry.ok().and_then(|entry| {
|
|
||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
|
||||||
if matches(&partial, &file_name, options) {
|
|
||||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
|
||||||
format!("{base_dir_name}{file_name}")
|
|
||||||
} else {
|
|
||||||
file_name.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
if entry.path().is_dir() {
|
|
||||||
path.push(SEP);
|
|
||||||
file_name.push(SEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix files or folders with quotes or hashes
|
|
||||||
if 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}`");
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((span, path))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||||
@ -187,28 +126,8 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
|||||||
if !options.case_sensitive {
|
if !options.case_sensitive {
|
||||||
return options
|
return options
|
||||||
.match_algorithm
|
.match_algorithm
|
||||||
.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase());
|
.matches_str(&from.to_folded_case(), &partial.to_folded_case());
|
||||||
}
|
}
|
||||||
|
|
||||||
options.match_algorithm.matches_str(from, partial)
|
options.match_algorithm.matches_str(from, partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the base_dir should be prepended to the file path
|
|
||||||
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
|
||||||
if base_dir == format!(".{SEP}") {
|
|
||||||
// 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.
|
|
||||||
let manually_entered = {
|
|
||||||
let mut chars = input.chars();
|
|
||||||
let first_char = chars.next();
|
|
||||||
let second_char = chars.next();
|
|
||||||
|
|
||||||
first_char == Some('.') && second_char.map(is_separator).unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
manually_entered
|
|
||||||
} else {
|
|
||||||
// always prepend the base dir if it is a subfolder
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod base;
|
mod base;
|
||||||
mod command_completions;
|
mod command_completions;
|
||||||
mod completer;
|
mod completer;
|
||||||
|
mod completion_common;
|
||||||
mod completion_options;
|
mod completion_options;
|
||||||
mod custom_completions;
|
mod custom_completions;
|
||||||
mod directory_completions;
|
mod directory_completions;
|
||||||
@ -16,8 +17,6 @@ pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
|||||||
pub use custom_completions::CustomCompletion;
|
pub use custom_completions::CustomCompletion;
|
||||||
pub use directory_completions::DirectoryCompletion;
|
pub use directory_completions::DirectoryCompletion;
|
||||||
pub use dotnu_completions::DotNuCompletion;
|
pub use dotnu_completions::DotNuCompletion;
|
||||||
pub use file_completions::{
|
pub use file_completions::{file_path_completion, matches, FileCompletion};
|
||||||
file_path_completion, matches, partial_from, prepend_base_dir, FileCompletion,
|
|
||||||
};
|
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
pub use variable_completions::VariableCompletion;
|
pub use variable_completions::VariableCompletion;
|
||||||
|
@ -43,10 +43,8 @@ impl Completer for VariableCompletion {
|
|||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
let builtins = ["$nu", "$in", "$env"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
||||||
.unwrap_or("")
|
|
||||||
.to_lowercase();
|
|
||||||
let var_id = working_set.find_variable(&self.var_context.0);
|
let var_id = working_set.find_variable(&self.var_context.0);
|
||||||
let current_span = reedline::Span {
|
let current_span = reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
@ -57,7 +55,7 @@ impl Completer for VariableCompletion {
|
|||||||
// Completions for the given variable
|
// Completions for the given variable
|
||||||
if !var_str.is_empty() {
|
if !var_str.is_empty() {
|
||||||
// Completion for $env.<tab>
|
// Completion for $env.<tab>
|
||||||
if var_str.as_str() == "$env" {
|
if var_str == "$env" {
|
||||||
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
||||||
|
|
||||||
// Return nested values
|
// Return nested values
|
||||||
@ -109,7 +107,7 @@ impl Completer for VariableCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Completions for $nu.<tab>
|
// Completions for $nu.<tab>
|
||||||
if var_str.as_str() == "$nu" {
|
if var_str == "$nu" {
|
||||||
// Eval nu var
|
// Eval nu var
|
||||||
if let Ok(nuval) = eval_variable(
|
if let Ok(nuval) = eval_variable(
|
||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
@ -237,9 +235,9 @@ fn nested_suggestions(
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
// Add all the columns as completion
|
// Add all the columns as completion
|
||||||
for item in val.cols {
|
for (col, _) in val.into_iter() {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: item,
|
value: col,
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: current_span,
|
span: current_span,
|
||||||
|
@ -28,13 +28,17 @@ pub fn evaluate_commands(
|
|||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
config.table_mode = t_mode.as_string()?;
|
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
||||||
engine_state.set_config(&config);
|
engine_state.set_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
||||||
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
|
report_error(&working_set, warning);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ pub fn evaluate_commands(
|
|||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
if let Some(t_mode) = table_mode {
|
if let Some(t_mode) = table_mode {
|
||||||
config.table_mode = t_mode.as_string()?;
|
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use crate::util::eval_source;
|
|||||||
use log::info;
|
use log::info;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_engine::eval_block_with_early_return;
|
use nu_engine::eval_block;
|
||||||
use nu_engine::{convert_env_values, current_dir};
|
use nu_engine::{convert_env_values, current_dir};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
@ -35,10 +35,10 @@ pub fn evaluate_file(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::FileNotFoundCustom(
|
&ShellError::FileNotFoundCustom {
|
||||||
format!("Could not access file '{}': {:?}", path, e.to_string()),
|
msg: format!("Could not access file '{}': {:?}", path, e.to_string()),
|
||||||
Span::unknown(),
|
span: Span::unknown(),
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
@ -47,13 +47,13 @@ pub fn evaluate_file(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::NonUtf8Custom(
|
&ShellError::NonUtf8Custom {
|
||||||
format!(
|
msg: format!(
|
||||||
"Input file name '{}' is not valid UTF8",
|
"Input file name '{}' is not valid UTF8",
|
||||||
file_path.to_string_lossy()
|
file_path.to_string_lossy()
|
||||||
),
|
),
|
||||||
Span::unknown(),
|
span: Span::unknown(),
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
@ -64,14 +64,14 @@ pub fn evaluate_file(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::FileNotFoundCustom(
|
&ShellError::FileNotFoundCustom {
|
||||||
format!(
|
msg: format!(
|
||||||
"Could not read file '{}': {:?}",
|
"Could not read file '{}': {:?}",
|
||||||
file_path_str,
|
file_path_str,
|
||||||
e.to_string()
|
e.to_string()
|
||||||
),
|
),
|
||||||
Span::unknown(),
|
span: Span::unknown(),
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
@ -82,10 +82,10 @@ pub fn evaluate_file(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::FileNotFoundCustom(
|
&ShellError::FileNotFoundCustom {
|
||||||
format!("The file path '{file_path_str}' does not have a parent"),
|
msg: format!("The file path '{file_path_str}' does not have a parent"),
|
||||||
Span::unknown(),
|
span: Span::unknown(),
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
@ -98,6 +98,10 @@ pub fn evaluate_file(
|
|||||||
"CURRENT_FILE".to_string(),
|
"CURRENT_FILE".to_string(),
|
||||||
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
||||||
);
|
);
|
||||||
|
stack.add_env_var(
|
||||||
|
"PROCESS_PATH".to_string(),
|
||||||
|
Value::string(path, Span::unknown()),
|
||||||
|
);
|
||||||
|
|
||||||
let source_filename = file_path
|
let source_filename = file_path
|
||||||
.file_name()
|
.file_name()
|
||||||
@ -126,14 +130,22 @@ pub fn evaluate_file(
|
|||||||
if engine_state.find_decl(b"main", &[]).is_some() {
|
if engine_state.find_decl(b"main", &[]).is_some() {
|
||||||
let args = format!("main {}", args.join(" "));
|
let args = format!("main {}", args.join(" "));
|
||||||
|
|
||||||
let pipeline_data = eval_block_with_early_return(
|
let pipeline_data = eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&block,
|
&block,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
)
|
);
|
||||||
|
let pipeline_data = match pipeline_data {
|
||||||
|
Err(ShellError::Return { .. }) => {
|
||||||
|
// allows early exists before `main` is run.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
x => x,
|
||||||
|
}
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|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);
|
||||||
@ -187,7 +199,7 @@ pub(crate) fn print_table_or_error(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Change the engine_state config to use the passed in configuration
|
// Change the engine_state config to use the passed in configuration
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config.clone());
|
||||||
|
|
||||||
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);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use nu_engine::documentation::get_flags_section;
|
use nu_engine::documentation::get_flags_section;
|
||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -13,21 +14,19 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let full_commands = self.0.get_signatures_with_examples(false);
|
let full_commands = self.0.get_signatures_with_examples(false);
|
||||||
|
let folded_line = line.to_folded_case();
|
||||||
|
|
||||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||||
let mut commands = full_commands
|
let mut commands = full_commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(sig, _, _, _, _)| {
|
.filter(|(sig, _, _, _, _)| {
|
||||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
sig.name.to_folded_case().contains(&folded_line)
|
||||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
|| sig.usage.to_folded_case().contains(&folded_line)
|
||||||
|| sig
|
|| sig
|
||||||
.search_terms
|
.search_terms
|
||||||
.iter()
|
.iter()
|
||||||
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
.any(|term| term.to_folded_case().contains(&folded_line))
|
||||||
|| sig
|
|| sig.extra_usage.to_folded_case().contains(&folded_line)
|
||||||
.extra_usage
|
|
||||||
.to_lowercase()
|
|
||||||
.contains(&line.to_lowercase())
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ impl NuHelpCompleter {
|
|||||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(sig, |v| {
|
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
|
||||||
v.into_string_parsable(", ", &self.0.config)
|
v.into_string_parsable(", ", &self.0.config)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -80,24 +80,18 @@ fn convert_to_suggestions(
|
|||||||
only_buffer_difference: bool,
|
only_buffer_difference: bool,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
match value {
|
match value {
|
||||||
Value::Record { .. } => {
|
Value::Record { val, .. } => {
|
||||||
let text = value
|
let text = val
|
||||||
.get_data_by_key("value")
|
.get("value")
|
||||||
.and_then(|val| val.as_string().ok())
|
.and_then(|val| val.as_string().ok())
|
||||||
.unwrap_or_else(|| "No value key".to_string());
|
.unwrap_or_else(|| "No value key".to_string());
|
||||||
|
|
||||||
let description = value
|
let description = val.get("description").and_then(|val| val.as_string().ok());
|
||||||
.get_data_by_key("description")
|
|
||||||
.and_then(|val| val.as_string().ok());
|
|
||||||
|
|
||||||
let span = match value.get_data_by_key("span") {
|
let span = match val.get("span") {
|
||||||
Some(span @ Value::Record { .. }) => {
|
Some(Value::Record { val: span, .. }) => {
|
||||||
let start = span
|
let start = span.get("start").and_then(|val| val.as_int().ok());
|
||||||
.get_data_by_key("start")
|
let end = span.get("end").and_then(|val| val.as_int().ok());
|
||||||
.and_then(|val| val.as_int().ok());
|
|
||||||
let end = span
|
|
||||||
.get_data_by_key("end")
|
|
||||||
.and_then(|val| val.as_int().ok());
|
|
||||||
match (start, end) {
|
match (start, end) {
|
||||||
(Some(start), Some(end)) => {
|
(Some(start), Some(end)) => {
|
||||||
let start = start.min(end);
|
let start = start.min(end);
|
||||||
@ -126,12 +120,12 @@ fn convert_to_suggestions(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let extra = match value.get_data_by_key("extra") {
|
let extra = match val.get("extra") {
|
||||||
Some(Value::List { vals, .. }) => {
|
Some(Value::List { vals, .. }) => {
|
||||||
let extra: Vec<String> = vals
|
let extra: Vec<String> = vals
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|extra| match extra {
|
.filter_map(|extra| match extra {
|
||||||
Value::String { val, .. } => Some(val),
|
Value::String { val, .. } => Some(val.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::DefaultPrompt;
|
use reedline::DefaultPrompt;
|
||||||
@ -11,6 +12,7 @@ use {
|
|||||||
/// Nushell prompt definition
|
/// Nushell prompt definition
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NushellPrompt {
|
pub struct NushellPrompt {
|
||||||
|
shell_integration: bool,
|
||||||
left_prompt_string: Option<String>,
|
left_prompt_string: Option<String>,
|
||||||
right_prompt_string: Option<String>,
|
right_prompt_string: Option<String>,
|
||||||
default_prompt_indicator: Option<String>,
|
default_prompt_indicator: Option<String>,
|
||||||
@ -20,15 +22,10 @@ pub struct NushellPrompt {
|
|||||||
render_right_prompt_on_last_line: bool,
|
render_right_prompt_on_last_line: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NushellPrompt {
|
|
||||||
fn default() -> Self {
|
|
||||||
NushellPrompt::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NushellPrompt {
|
impl NushellPrompt {
|
||||||
pub fn new() -> NushellPrompt {
|
pub fn new(shell_integration: bool) -> NushellPrompt {
|
||||||
NushellPrompt {
|
NushellPrompt {
|
||||||
|
shell_integration,
|
||||||
left_prompt_string: None,
|
left_prompt_string: None,
|
||||||
right_prompt_string: None,
|
right_prompt_string: None,
|
||||||
default_prompt_indicator: None,
|
default_prompt_indicator: None,
|
||||||
@ -111,9 +108,13 @@ impl Prompt for NushellPrompt {
|
|||||||
.to_string()
|
.to_string()
|
||||||
.replace('\n', "\r\n");
|
.replace('\n', "\r\n");
|
||||||
|
|
||||||
|
if self.shell_integration {
|
||||||
|
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
|
||||||
|
} else {
|
||||||
prompt.into()
|
prompt.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_prompt_right(&self) -> Cow<str> {
|
fn render_prompt_right(&self) -> Cow<str> {
|
||||||
if let Some(prompt_string) = &self.right_prompt_string {
|
if let Some(prompt_string) = &self.right_prompt_string {
|
||||||
|
@ -15,10 +15,19 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
|||||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_COMMAND: &str = "TRANSIENT_PROMPT_COMMAND";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_COMMAND_RIGHT: &str = "TRANSIENT_PROMPT_COMMAND_RIGHT";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR: &str = "TRANSIENT_PROMPT_INDICATOR";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_INSERT: &str =
|
||||||
|
"TRANSIENT_PROMPT_INDICATOR_VI_INSERT";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_NORMAL: &str =
|
||||||
|
"TRANSIENT_PROMPT_INDICATOR_VI_NORMAL";
|
||||||
|
pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
|
||||||
|
"TRANSIENT_PROMPT_MULTILINE_INDICATOR";
|
||||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
pub(crate) const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||||
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
pub(crate) const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||||
|
|
||||||
fn get_prompt_string(
|
fn get_prompt_string(
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
@ -29,13 +38,9 @@ fn get_prompt_string(
|
|||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Closure {
|
Value::Closure { val, .. } => {
|
||||||
val: block_id,
|
let block = engine_state.get_block(val.block_id);
|
||||||
captures,
|
let mut stack = stack.captures_to_stack(val.captures);
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let block = engine_state.get_block(block_id);
|
|
||||||
let mut stack = stack.captures_to_stack(&captures);
|
|
||||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||||
let ret_val =
|
let ret_val =
|
||||||
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
||||||
@ -91,12 +96,12 @@ fn get_prompt_string(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_prompt<'prompt>(
|
pub(crate) fn update_prompt(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
nu_prompt: &'prompt mut NushellPrompt,
|
nu_prompt: &mut NushellPrompt,
|
||||||
) -> &'prompt dyn Prompt {
|
) {
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
||||||
@ -139,9 +144,55 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||||
config.render_right_prompt_on_last_line,
|
config.render_right_prompt_on_last_line,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
|
||||||
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
|
}
|
||||||
ret_val
|
|
||||||
|
/// Construct the transient prompt based on the normal nu_prompt
|
||||||
|
pub(crate) fn make_transient_prompt(
|
||||||
|
config: &Config,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
nu_prompt: &NushellPrompt,
|
||||||
|
) -> Box<dyn Prompt> {
|
||||||
|
let mut nu_prompt = nu_prompt.clone();
|
||||||
|
|
||||||
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND, config, engine_state, stack) {
|
||||||
|
nu_prompt.update_prompt_left(Some(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND_RIGHT, config, engine_state, stack)
|
||||||
|
{
|
||||||
|
nu_prompt.update_prompt_right(Some(s), config.render_right_prompt_on_last_line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_INDICATOR, config, engine_state, stack) {
|
||||||
|
nu_prompt.update_prompt_indicator(Some(s))
|
||||||
|
}
|
||||||
|
if let Some(s) = get_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
) {
|
||||||
|
nu_prompt.update_prompt_vi_insert(Some(s))
|
||||||
|
}
|
||||||
|
if let Some(s) = get_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
) {
|
||||||
|
nu_prompt.update_prompt_vi_normal(Some(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = get_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
|
||||||
|
config,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
) {
|
||||||
|
nu_prompt.update_prompt_multiline(Some(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::new(nu_prompt)
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ 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, ParsedKeybinding, ParsedMenu, PipelineData, Record, ShellError, Span,
|
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
||||||
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,
|
||||||
@ -139,18 +139,18 @@ fn add_menu(
|
|||||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
"columnar, list or description".to_string(),
|
expected: "columnar, list or description".to_string(),
|
||||||
menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.into_abbreviated_string(config),
|
||||||
menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::UnsupportedConfigValue(
|
Err(ShellError::UnsupportedConfigValue {
|
||||||
"only record type".to_string(),
|
expected: "only record type".to_string(),
|
||||||
menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.into_abbreviated_string(config),
|
||||||
menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,11 +248,11 @@ pub(crate) fn add_columnar_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||||
}
|
}
|
||||||
Value::Closure { val, captures, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
val.block_id,
|
||||||
span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(val.captures.clone()),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
);
|
);
|
||||||
@ -261,11 +261,11 @@ pub(crate) fn add_columnar_menu(
|
|||||||
completer: Box::new(menu_completer),
|
completer: Box::new(menu_completer),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
"block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
value: menu.source.into_abbreviated_string(config),
|
||||||
span,
|
span,
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,11 +330,11 @@ pub(crate) fn add_list_menu(
|
|||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||||
}
|
}
|
||||||
Value::Closure { val, captures, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
val.block_id,
|
||||||
span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(val.captures.clone()),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
);
|
);
|
||||||
@ -343,11 +343,11 @@ pub(crate) fn add_list_menu(
|
|||||||
completer: Box::new(menu_completer),
|
completer: Box::new(menu_completer),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
"block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
value: menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span(),
|
span: menu.source.span(),
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,11 +448,11 @@ pub(crate) fn add_description_menu(
|
|||||||
completer,
|
completer,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Value::Closure { val, captures, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
val.block_id,
|
||||||
span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(val.captures.clone()),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
);
|
);
|
||||||
@ -461,11 +461,11 @@ pub(crate) fn add_description_menu(
|
|||||||
completer: Box::new(menu_completer),
|
completer: Box::new(menu_completer),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
"closure or omitted value".to_string(),
|
expected: "closure or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
value: menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span(),
|
span: menu.source.span(),
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,11 +537,11 @@ pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, She
|
|||||||
let mut insert_keybindings = default_vi_insert_keybindings();
|
let mut insert_keybindings = default_vi_insert_keybindings();
|
||||||
let mut normal_keybindings = default_vi_normal_keybindings();
|
let mut normal_keybindings = default_vi_normal_keybindings();
|
||||||
|
|
||||||
match config.edit_mode.as_str() {
|
match config.edit_mode {
|
||||||
"emacs" => {
|
EditBindings::Emacs => {
|
||||||
add_menu_keybindings(&mut emacs_keybindings);
|
add_menu_keybindings(&mut emacs_keybindings);
|
||||||
}
|
}
|
||||||
_ => {
|
EditBindings::Vi => {
|
||||||
add_menu_keybindings(&mut insert_keybindings);
|
add_menu_keybindings(&mut insert_keybindings);
|
||||||
add_menu_keybindings(&mut normal_keybindings);
|
add_menu_keybindings(&mut normal_keybindings);
|
||||||
}
|
}
|
||||||
@ -557,9 +557,9 @@ pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, She
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
match config.edit_mode.as_str() {
|
match config.edit_mode {
|
||||||
"emacs" => Ok(KeybindingsMode::Emacs(emacs_keybindings)),
|
EditBindings::Emacs => Ok(KeybindingsMode::Emacs(emacs_keybindings)),
|
||||||
_ => Ok(KeybindingsMode::Vi {
|
EditBindings::Vi => Ok(KeybindingsMode::Vi {
|
||||||
insert_keybindings,
|
insert_keybindings,
|
||||||
normal_keybindings,
|
normal_keybindings,
|
||||||
}),
|
}),
|
||||||
@ -580,11 +580,11 @@ fn add_keybinding(
|
|||||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
||||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
||||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
||||||
m => Err(ShellError::UnsupportedConfigValue(
|
m => Err(ShellError::UnsupportedConfigValue {
|
||||||
"emacs, vi_insert or vi_normal".to_string(),
|
expected: "emacs, vi_insert or vi_normal".to_string(),
|
||||||
m.to_string(),
|
value: m.to_string(),
|
||||||
span,
|
span,
|
||||||
)),
|
}),
|
||||||
},
|
},
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for inner_mode in vals {
|
for inner_mode in vals {
|
||||||
@ -600,11 +600,11 @@ fn add_keybinding(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
"string or list of strings".to_string(),
|
expected: "string or list of strings".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
value: v.into_abbreviated_string(config),
|
||||||
v.span(),
|
span: v.span(),
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,7 +616,7 @@ fn add_parsed_keybinding(
|
|||||||
let modifier = match keybinding
|
let modifier = match keybinding
|
||||||
.modifier
|
.modifier
|
||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.to_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"control" => KeyModifiers::CONTROL,
|
"control" => KeyModifiers::CONTROL,
|
||||||
@ -630,18 +630,18 @@ fn add_parsed_keybinding(
|
|||||||
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
|
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"CONTROL, SHIFT, ALT or NONE".to_string(),
|
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||||
keybinding.modifier.into_abbreviated_string(config),
|
value: keybinding.modifier.into_abbreviated_string(config),
|
||||||
keybinding.modifier.span(),
|
span: keybinding.modifier.span(),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let keycode = match keybinding
|
let keycode = match keybinding
|
||||||
.keycode
|
.keycode
|
||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.to_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"backspace" => KeyCode::Backspace,
|
"backspace" => KeyCode::Backspace,
|
||||||
@ -654,11 +654,11 @@ fn add_parsed_keybinding(
|
|||||||
let char = if let (Some(char), None) = (pos1, pos2) {
|
let char = if let (Some(char), None) = (pos1, pos2) {
|
||||||
char
|
char
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"char_<CHAR: unicode codepoint>".to_string(),
|
expected: "char_<CHAR: unicode codepoint>".to_string(),
|
||||||
c.to_string(),
|
value: c.to_string(),
|
||||||
keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
@ -681,21 +681,21 @@ fn add_parsed_keybinding(
|
|||||||
.parse()
|
.parse()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|num| matches!(num, 1..=20))
|
.filter(|num| matches!(num, 1..=20))
|
||||||
.ok_or(ShellError::UnsupportedConfigValue(
|
.ok_or(ShellError::UnsupportedConfigValue {
|
||||||
"(f1|f2|...|f20)".to_string(),
|
expected: "(f1|f2|...|f20)".to_string(),
|
||||||
format!("unknown function key: {c}"),
|
value: format!("unknown function key: {c}"),
|
||||||
keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
))?;
|
})?;
|
||||||
KeyCode::F(fn_num)
|
KeyCode::F(fn_num)
|
||||||
}
|
}
|
||||||
"null" => KeyCode::Null,
|
"null" => KeyCode::Null,
|
||||||
"esc" | "escape" => KeyCode::Esc,
|
"esc" | "escape" => KeyCode::Esc,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"crossterm KeyCode".to_string(),
|
expected: "crossterm KeyCode".to_string(),
|
||||||
keybinding.keycode.into_abbreviated_string(config),
|
value: keybinding.keycode.into_abbreviated_string(config),
|
||||||
keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(event) = parse_event(&keybinding.event, config)? {
|
if let Some(event) = parse_event(&keybinding.event, config)? {
|
||||||
@ -719,7 +719,10 @@ impl<'config> EventType<'config> {
|
|||||||
.map(Self::Send)
|
.map(Self::Send)
|
||||||
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
||||||
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
||||||
.map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), span))
|
.map_err(|_| ShellError::MissingConfigValue {
|
||||||
|
missing_value: "send, edit or until".to_string(),
|
||||||
|
span,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,7 +731,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
||||||
EventType::Send(value) => event_from_record(
|
EventType::Send(value) => event_from_record(
|
||||||
value.into_string("", config).to_lowercase().as_str(),
|
value.into_string("", config).to_ascii_lowercase().as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -736,7 +739,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.map(Some),
|
.map(Some),
|
||||||
EventType::Edit(value) => {
|
EventType::Edit(value) => {
|
||||||
let edit = edit_from_record(
|
let edit = edit_from_record(
|
||||||
value.into_string("", config).to_lowercase().as_str(),
|
value.into_string("", config).to_ascii_lowercase().as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -749,11 +752,11 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|value| match parse_event(value, config) {
|
.map(|value| match parse_event(value, config) {
|
||||||
Ok(inner) => match inner {
|
Ok(inner) => match inner {
|
||||||
None => Err(ShellError::UnsupportedConfigValue(
|
None => Err(ShellError::UnsupportedConfigValue {
|
||||||
"List containing valid events".to_string(),
|
expected: "List containing valid events".to_string(),
|
||||||
"Nothing value (null)".to_string(),
|
value: "Nothing value (null)".to_string(),
|
||||||
value.span(),
|
span: value.span(),
|
||||||
)),
|
}),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
},
|
},
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -762,11 +765,11 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
|
|
||||||
Ok(Some(ReedlineEvent::UntilFound(events)))
|
Ok(Some(ReedlineEvent::UntilFound(events)))
|
||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
"list of events".to_string(),
|
expected: "list of events".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
value: v.into_abbreviated_string(config),
|
||||||
v.span(),
|
span: v.span(),
|
||||||
)),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -774,11 +777,11 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|value| match parse_event(value, config) {
|
.map(|value| match parse_event(value, config) {
|
||||||
Ok(inner) => match inner {
|
Ok(inner) => match inner {
|
||||||
None => Err(ShellError::UnsupportedConfigValue(
|
None => Err(ShellError::UnsupportedConfigValue {
|
||||||
"List containing valid events".to_string(),
|
expected: "List containing valid events".to_string(),
|
||||||
"Nothing value (null)".to_string(),
|
value: "Nothing value (null)".to_string(),
|
||||||
value.span(),
|
span: value.span(),
|
||||||
)),
|
}),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
},
|
},
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -788,11 +791,11 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
Ok(Some(ReedlineEvent::Multiple(events)))
|
Ok(Some(ReedlineEvent::Multiple(events)))
|
||||||
}
|
}
|
||||||
Value::Nothing { .. } => Ok(None),
|
Value::Nothing { .. } => Ok(None),
|
||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
"record or list of records, null to unbind key".to_string(),
|
expected: "record or list of records, null to unbind key".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
value: v.into_abbreviated_string(config),
|
||||||
v.span(),
|
span: v.span(),
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,11 +843,11 @@ fn event_from_record(
|
|||||||
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
|
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
|
||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"Reedline event".to_string(),
|
expected: "Reedline event".to_string(),
|
||||||
v.to_string(),
|
value: v.to_string(),
|
||||||
span,
|
span,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -954,11 +957,11 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"complete" => EditCommand::Complete,
|
"complete" => EditCommand::Complete,
|
||||||
e => {
|
e => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"reedline EditCommand".to_string(),
|
expected: "reedline EditCommand".to_string(),
|
||||||
e.to_string(),
|
value: e.to_string(),
|
||||||
span,
|
span,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -971,18 +974,23 @@ fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
|||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ShellError::MissingConfigValue("char to insert".to_string(), span))
|
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||||
|
missing_value: "char to insert".to_string(),
|
||||||
|
span,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use nu_protocol::record;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_send_event() {
|
fn test_send_event() {
|
||||||
let cols = vec!["send".to_string()];
|
let event = record! {
|
||||||
let vals = vec![Value::test_string("Enter")];
|
"send" => Value::test_string("Enter"),
|
||||||
let event = Record { vals, cols };
|
};
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
@ -997,9 +1005,9 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_event() {
|
fn test_edit_event() {
|
||||||
let cols = vec!["edit".to_string()];
|
let event = record! {
|
||||||
let vals = vec![Value::test_string("Clear")];
|
"edit" => Value::test_string("Clear"),
|
||||||
let event = Record { vals, cols };
|
};
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
@ -1017,12 +1025,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_send_menu() {
|
fn test_send_menu() {
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
let event = record! {
|
||||||
let vals = vec![
|
"send" => Value::test_string("Menu"),
|
||||||
Value::test_string("Menu"),
|
"name" => Value::test_string("history_menu"),
|
||||||
Value::test_string("history_menu"),
|
};
|
||||||
];
|
|
||||||
let event = Record { vals, cols };
|
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
@ -1040,28 +1046,19 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_until_event() {
|
fn test_until_event() {
|
||||||
// Menu event
|
let menu_event = Value::test_record(record! {
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
"send" => Value::test_string("Menu"),
|
||||||
let vals = vec![
|
"name" => Value::test_string("history_menu"),
|
||||||
Value::test_string("Menu"),
|
});
|
||||||
Value::test_string("history_menu"),
|
let enter_event = Value::test_record(record! {
|
||||||
];
|
"send" => Value::test_string("Enter"),
|
||||||
|
});
|
||||||
let menu_event = Value::test_record(Record { cols, vals });
|
let event = record! {
|
||||||
|
"until" => Value::list(
|
||||||
// Enter event
|
|
||||||
let cols = vec!["send".to_string()];
|
|
||||||
let vals = vec![Value::test_string("Enter")];
|
|
||||||
|
|
||||||
let enter_event = Value::test_record(Record { cols, vals });
|
|
||||||
|
|
||||||
// Until event
|
|
||||||
let cols = vec!["until".to_string()];
|
|
||||||
let vals = vec![Value::list(
|
|
||||||
vec![menu_event, enter_event],
|
vec![menu_event, enter_event],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)];
|
),
|
||||||
let event = Record { cols, vals };
|
};
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
@ -1082,22 +1079,13 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_event() {
|
fn test_multiple_event() {
|
||||||
// Menu event
|
let menu_event = Value::test_record(record! {
|
||||||
let cols = vec!["send".to_string(), "name".to_string()];
|
"send" => Value::test_string("Menu"),
|
||||||
let vals = vec![
|
"name" => Value::test_string("history_menu"),
|
||||||
Value::test_string("Menu"),
|
});
|
||||||
Value::test_string("history_menu"),
|
let enter_event = Value::test_record(record! {
|
||||||
];
|
"send" => Value::test_string("Enter"),
|
||||||
|
});
|
||||||
let menu_event = Value::test_record(Record { cols, vals });
|
|
||||||
|
|
||||||
// Enter event
|
|
||||||
let cols = vec!["send".to_string()];
|
|
||||||
let vals = vec![Value::test_string("Enter")];
|
|
||||||
|
|
||||||
let enter_event = Value::test_record(Record { cols, vals });
|
|
||||||
|
|
||||||
// Multiple event
|
|
||||||
let event = Value::list(vec![menu_event, enter_event], Span::test_data());
|
let event = Value::list(vec![menu_event, enter_event], Span::test_data());
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
@ -1113,12 +1101,12 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error() {
|
fn test_error() {
|
||||||
let cols = vec!["not_exist".to_string()];
|
let event = record! {
|
||||||
let vals = vec![Value::test_string("Enter")];
|
"not_exist" => Value::test_string("Enter"),
|
||||||
let event = Record { cols, vals };
|
};
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span);
|
let b = EventType::try_from_record(&event, span);
|
||||||
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
|
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ use crate::{
|
|||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
use miette::{ErrReport, IntoDiagnostic, Result};
|
use miette::{ErrReport, IntoDiagnostic, Result};
|
||||||
use nu_cmd_base::hook::eval_hook;
|
|
||||||
use nu_cmd_base::util::get_guaranteed_cwd;
|
use nu_cmd_base::util::get_guaranteed_cwd;
|
||||||
|
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_engine::convert_env_values;
|
use nu_engine::convert_env_values;
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
@ -22,16 +22,17 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use reedline::{
|
use reedline::{
|
||||||
CursorConfig, DefaultHinter, EditCommand, Emacs, FileBackedHistory, HistorySessionId, Reedline,
|
CursorConfig, CwdAwareHinter, EditCommand, Emacs, FileBackedHistory, HistorySessionId,
|
||||||
SqliteBackedHistory, Vi,
|
Reedline, SqliteBackedHistory, Vi,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
env::temp_dir,
|
||||||
io::{self, IsTerminal, Write},
|
io::{self, IsTerminal, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use sysinfo::SystemExt;
|
use sysinfo::System;
|
||||||
|
|
||||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
@ -53,7 +54,8 @@ pub fn evaluate_repl(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use nu_cmd_base::hook;
|
use nu_cmd_base::hook;
|
||||||
use reedline::Signal;
|
use reedline::Signal;
|
||||||
let use_color = engine_state.get_config().use_ansi_coloring;
|
let config = engine_state.get_config();
|
||||||
|
let use_color = 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
|
||||||
@ -67,7 +69,7 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
|
||||||
let mut nu_prompt = NushellPrompt::new();
|
let mut nu_prompt = NushellPrompt::new(config.shell_integration);
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
@ -94,6 +96,7 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let mut start_time = std::time::Instant::now();
|
let mut start_time = std::time::Instant::now();
|
||||||
let mut line_editor = Reedline::create();
|
let mut line_editor = Reedline::create();
|
||||||
|
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||||
|
|
||||||
// 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
|
||||||
store_history_id_in_engine(engine_state, &line_editor);
|
store_history_id_in_engine(engine_state, &line_editor);
|
||||||
@ -106,16 +109,8 @@ pub fn evaluate_repl(
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
let config = engine_state.get_config();
|
|
||||||
if config.bracketed_paste {
|
|
||||||
// try to enable bracketed paste
|
|
||||||
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let _ = line_editor.enable_bracketed_paste();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup history_isolation aka "history per session"
|
// Setup history_isolation aka "history per session"
|
||||||
let history_isolation = config.history_isolation;
|
let history_isolation = engine_state.get_config().history_isolation;
|
||||||
let history_session_id = if history_isolation {
|
let history_session_id = if history_isolation {
|
||||||
Reedline::create_history_session_id()
|
Reedline::create_history_session_id()
|
||||||
} else {
|
} else {
|
||||||
@ -140,17 +135,6 @@ pub fn evaluate_repl(
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
|
||||||
let sys = sysinfo::System::new();
|
|
||||||
perf(
|
|
||||||
"get sysinfo",
|
|
||||||
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,
|
||||||
@ -180,6 +164,10 @@ pub fn evaluate_repl(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
|
||||||
|
warn!("Terminal doesn't support use_kitty_protocol config");
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let loop_start_time = std::time::Instant::now();
|
let loop_start_time = std::time::Instant::now();
|
||||||
|
|
||||||
@ -214,20 +202,6 @@ pub fn evaluate_repl(
|
|||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
|
||||||
// Reset the SIGQUIT handler
|
|
||||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
|
||||||
sig_quit.store(false, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
perf(
|
|
||||||
"reset sig_quit",
|
|
||||||
start_time,
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!(),
|
|
||||||
use_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
@ -235,15 +209,9 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
// Find the configured cursor shapes for each mode
|
// Find the configured cursor shapes for each mode
|
||||||
let cursor_config = CursorConfig {
|
let cursor_config = CursorConfig {
|
||||||
vi_insert: config
|
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
||||||
.cursor_shape_vi_insert
|
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||||
.map(map_nucursorshape_to_cursorshape),
|
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||||
vi_normal: config
|
|
||||||
.cursor_shape_vi_normal
|
|
||||||
.map(map_nucursorshape_to_cursorshape),
|
|
||||||
emacs: config
|
|
||||||
.cursor_shape_emacs
|
|
||||||
.map(map_nucursorshape_to_cursorshape),
|
|
||||||
};
|
};
|
||||||
perf(
|
perf(
|
||||||
"get config/cursor config",
|
"get config/cursor config",
|
||||||
@ -257,6 +225,10 @@ pub fn evaluate_repl(
|
|||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
line_editor = line_editor
|
line_editor = line_editor
|
||||||
|
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
||||||
|
// try to enable bracketed paste
|
||||||
|
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
||||||
|
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
||||||
.with_highlighter(Box::new(NuHighlighter {
|
.with_highlighter(Box::new(NuHighlighter {
|
||||||
engine_state: engine_reference.clone(),
|
engine_state: engine_reference.clone(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
@ -288,7 +260,7 @@ pub fn evaluate_repl(
|
|||||||
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.
|
||||||
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||||
DefaultHinter::default().with_style(style)
|
CwdAwareHinter::default().with_style(style)
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
line_editor.disable_hints()
|
line_editor.disable_hints()
|
||||||
@ -318,23 +290,17 @@ pub fn evaluate_repl(
|
|||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let buffer_editor = if !config.buffer_editor.is_empty() {
|
let buffer_editor = get_editor(engine_state, stack, Span::unknown());
|
||||||
Some(config.buffer_editor.clone())
|
|
||||||
} else {
|
|
||||||
stack
|
|
||||||
.get_env_var(engine_state, "EDITOR")
|
|
||||||
.map(|v| v.as_string().unwrap_or_default())
|
|
||||||
.filter(|v| !v.is_empty())
|
|
||||||
.or_else(|| {
|
|
||||||
stack
|
|
||||||
.get_env_var(engine_state, "VISUAL")
|
|
||||||
.map(|v| v.as_string().unwrap_or_default())
|
|
||||||
.filter(|v| !v.is_empty())
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
line_editor = if let Some(buffer_editor) = buffer_editor {
|
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
||||||
line_editor.with_buffer_editor(buffer_editor, "nu".into())
|
let mut command = std::process::Command::new(&cmd);
|
||||||
|
command.args(args).envs(
|
||||||
|
engine_state
|
||||||
|
.render_env_vars()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| v.as_string().ok().map(|v| (k, v))),
|
||||||
|
);
|
||||||
|
line_editor.with_buffer_editor(command, temp_file.clone())
|
||||||
} else {
|
} else {
|
||||||
line_editor
|
line_editor
|
||||||
};
|
};
|
||||||
@ -430,7 +396,9 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let config = &engine_state.get_config().clone();
|
let config = &engine_state.get_config().clone();
|
||||||
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
||||||
|
let transient_prompt =
|
||||||
|
prompt_update::make_transient_prompt(config, engine_state, stack, &nu_prompt);
|
||||||
perf(
|
perf(
|
||||||
"update_prompt",
|
"update_prompt",
|
||||||
start_time,
|
start_time,
|
||||||
@ -443,12 +411,13 @@ pub fn evaluate_repl(
|
|||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
let input = line_editor.read_line(prompt);
|
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
||||||
|
let input = line_editor.read_line(&nu_prompt);
|
||||||
let shell_integration = config.shell_integration;
|
let shell_integration = config.shell_integration;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
Ok(Signal::Success(s)) => {
|
||||||
let hostname = sys.host_name();
|
let hostname = System::host_name();
|
||||||
let history_supports_meta =
|
let history_supports_meta =
|
||||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||||
@ -508,7 +477,10 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::DirectoryNotFound(tokens.0[0].span, None),
|
&ShellError::DirectoryNotFound {
|
||||||
|
dir: path.to_string_lossy().to_string(),
|
||||||
|
span: tokens.0[0].span,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let path = nu_path::canonicalize_with(path, &cwd)
|
let path = nu_path::canonicalize_with(path, &cwd)
|
||||||
@ -585,10 +557,6 @@ pub fn evaluate_repl(
|
|||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if engine_state.get_config().bracketed_paste {
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let _ = line_editor.enable_bracketed_paste();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let cmd_duration = start_time.elapsed();
|
let cmd_duration = start_time.elapsed();
|
||||||
|
|
||||||
@ -615,7 +583,16 @@ pub fn evaluate_repl(
|
|||||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||||
let path = cwd.as_string()?;
|
let path = cwd.as_string()?;
|
||||||
|
|
||||||
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||||
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
||||||
|
if stack.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
== Some(Value::test_string("vscode"))
|
||||||
|
{
|
||||||
|
// If we're in vscode, run their specific ansi escape sequence.
|
||||||
|
// This is helpful for ctrl+g to change directories in the terminal.
|
||||||
|
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
|
||||||
|
} else {
|
||||||
|
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||||
run_ansi_sequence(&format!(
|
run_ansi_sequence(&format!(
|
||||||
"\x1b]7;file://{}{}{}\x1b\\",
|
"\x1b]7;file://{}{}{}\x1b\\",
|
||||||
percent_encoding::utf8_percent_encode(
|
percent_encoding::utf8_percent_encode(
|
||||||
@ -628,6 +605,7 @@ pub fn evaluate_repl(
|
|||||||
percent_encoding::CONTROLS
|
percent_encoding::CONTROLS
|
||||||
)
|
)
|
||||||
))?;
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||||
@ -749,18 +727,19 @@ fn update_line_editor_history(
|
|||||||
Ok(line_editor)
|
Ok(line_editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> SetCursorStyle {
|
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
||||||
match shape {
|
match shape {
|
||||||
NuCursorShape::Block => SetCursorStyle::SteadyBlock,
|
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
||||||
NuCursorShape::UnderScore => SetCursorStyle::SteadyUnderScore,
|
NuCursorShape::UnderScore => Some(SetCursorStyle::SteadyUnderScore),
|
||||||
NuCursorShape::Line => SetCursorStyle::SteadyBar,
|
NuCursorShape::Line => Some(SetCursorStyle::SteadyBar),
|
||||||
NuCursorShape::BlinkBlock => SetCursorStyle::BlinkingBlock,
|
NuCursorShape::BlinkBlock => Some(SetCursorStyle::BlinkingBlock),
|
||||||
NuCursorShape::BlinkUnderScore => SetCursorStyle::BlinkingUnderScore,
|
NuCursorShape::BlinkUnderScore => Some(SetCursorStyle::BlinkingUnderScore),
|
||||||
NuCursorShape::BlinkLine => SetCursorStyle::BlinkingBar,
|
NuCursorShape::BlinkLine => Some(SetCursorStyle::BlinkingBar),
|
||||||
|
NuCursorShape::Inherit => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||||
let exit_code = stack
|
let exit_code = stack
|
||||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||||
.and_then(|e| e.as_i64().ok());
|
.and_then(|e| e.as_i64().ok());
|
||||||
@ -769,23 +748,21 @@ pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||||
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
|
io::stdout()
|
||||||
ShellError::GenericError(
|
.write_all(seq.as_bytes())
|
||||||
"Error writing ansi sequence".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error writing ansi sequence".into(),
|
||||||
Some(Span::unknown()),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(Span::unknown()),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
io::stdout().flush().map_err(|e| {
|
io::stdout().flush().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error flushing stdio".into(),
|
||||||
"Error flushing stdio".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(Span::unknown()),
|
||||||
Some(Span::unknown()),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -809,16 +786,29 @@ fn looks_like_path(orig: &str) -> bool {
|
|||||||
|| orig.starts_with('~')
|
|| orig.starts_with('~')
|
||||||
|| orig.starts_with('/')
|
|| orig.starts_with('/')
|
||||||
|| orig.starts_with('\\')
|
|| orig.starts_with('\\')
|
||||||
|
|| orig.ends_with(std::path::MAIN_SEPARATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn looks_like_path_windows_drive_path_works() {
|
fn looks_like_path_windows_drive_path_works() {
|
||||||
let on_windows = cfg!(windows);
|
assert!(looks_like_path("C:"));
|
||||||
assert_eq!(looks_like_path("C:"), on_windows);
|
assert!(looks_like_path("D:\\"));
|
||||||
assert_eq!(looks_like_path("D:\\"), on_windows);
|
assert!(looks_like_path("E:/"));
|
||||||
assert_eq!(looks_like_path("E:/"), on_windows);
|
assert!(looks_like_path("F:\\some_dir"));
|
||||||
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
|
assert!(looks_like_path("G:/some_dir"));
|
||||||
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn trailing_slash_looks_like_path() {
|
||||||
|
assert!(looks_like_path("foo\\"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
#[test]
|
||||||
|
fn trailing_slash_looks_like_path() {
|
||||||
|
assert!(looks_like_path("foo/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2,7 +2,7 @@ use log::trace;
|
|||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
|
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement, RecordItem};
|
||||||
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};
|
||||||
@ -17,10 +17,27 @@ impl Highlighter for NuHighlighter {
|
|||||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||||
trace!("highlighting: {}", line);
|
trace!("highlighting: {}", line);
|
||||||
|
|
||||||
|
let highlight_resolved_externals =
|
||||||
|
self.engine_state.get_config().highlight_resolved_externals;
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||||
let (shapes, global_span_offset) = {
|
let (shapes, global_span_offset) = {
|
||||||
let shapes = flatten_block(&working_set, &block);
|
let mut shapes = flatten_block(&working_set, &block);
|
||||||
|
// Highlighting externals has a config point because of concerns that using which to resolve
|
||||||
|
// externals may slow down things too much.
|
||||||
|
if highlight_resolved_externals {
|
||||||
|
for (span, shape) in shapes.iter_mut() {
|
||||||
|
if *shape == FlatShape::External {
|
||||||
|
let str_contents =
|
||||||
|
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||||
|
|
||||||
|
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||||
|
if which::which(str_word).ok().is_some() {
|
||||||
|
*shape = FlatShape::ExternalResolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
(shapes, self.engine_state.next_span_start())
|
(shapes, self.engine_state.next_span_start())
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,6 +108,7 @@ impl Highlighter for NuHighlighter {
|
|||||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||||
|
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||||
@ -234,11 +252,11 @@ fn find_matching_block_end_in_block(
|
|||||||
for e in &p.elements {
|
for e in &p.elements {
|
||||||
match e {
|
match e {
|
||||||
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::SameTargetRedirection { cmd: (_, e), .. }
|
| PipelineElement::SameTargetRedirection { cmd: (_, e), .. }
|
||||||
| PipelineElement::SeparateRedirection { out: (_, 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,
|
||||||
@ -311,10 +329,10 @@ fn find_matching_block_end_in_expr(
|
|||||||
Expr::ImportPattern(_) => None,
|
Expr::ImportPattern(_) => None,
|
||||||
Expr::Overlay(_) => None,
|
Expr::Overlay(_) => None,
|
||||||
Expr::Signature(_) => None,
|
Expr::Signature(_) => None,
|
||||||
Expr::MatchPattern(_) => None,
|
|
||||||
Expr::MatchBlock(_) => None,
|
Expr::MatchBlock(_) => None,
|
||||||
Expr::Nothing => None,
|
Expr::Nothing => None,
|
||||||
Expr::Garbage => None,
|
Expr::Garbage => None,
|
||||||
|
Expr::Spread(_) => None,
|
||||||
|
|
||||||
Expr::Table(hdr, rows) => {
|
Expr::Table(hdr, rows) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
@ -346,10 +364,17 @@ fn find_matching_block_end_in_expr(
|
|||||||
Some(expr_last)
|
Some(expr_last)
|
||||||
} else {
|
} else {
|
||||||
// cursor is inside record
|
// cursor is inside record
|
||||||
for (k, v) in exprs {
|
for expr in exprs {
|
||||||
|
match expr {
|
||||||
|
RecordItem::Pair(k, v) => {
|
||||||
find_in_expr_or_continue!(k);
|
find_in_expr_or_continue!(k);
|
||||||
find_in_expr_or_continue!(v);
|
find_in_expr_or_continue!(v);
|
||||||
}
|
}
|
||||||
|
RecordItem::Spread(_, record) => {
|
||||||
|
find_in_expr_or_continue!(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,6 +385,7 @@ fn find_matching_block_end_in_expr(
|
|||||||
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),
|
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||||
|
Argument::Spread(inner_expr) => Some(inner_expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(inner_expr) = opt_expr {
|
if let Some(inner_expr) = opt_expr {
|
||||||
|
@ -43,13 +43,13 @@ fn gather_env_vars(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::GenericError(
|
&ShellError::GenericError {
|
||||||
format!("Environment variable was not captured: {env_str}"),
|
error: format!("Environment variable was not captured: {env_str}"),
|
||||||
"".to_string(),
|
msg: "".into(),
|
||||||
None,
|
span: None,
|
||||||
Some(msg.into()),
|
help: Some(msg.into()),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,15 +75,15 @@ fn gather_env_vars(
|
|||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::GenericError(
|
&ShellError::GenericError {
|
||||||
"Current directory is not a valid utf-8 path".to_string(),
|
error: "Current directory is not a valid utf-8 path".into(),
|
||||||
"".to_string(),
|
msg: "".into(),
|
||||||
None,
|
span: None,
|
||||||
Some(format!(
|
help: Some(format!(
|
||||||
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
|
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
|
||||||
)),
|
)),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ fn gather_env_vars(
|
|||||||
let name = if let Some(Token {
|
let name = if let Some(Token {
|
||||||
contents: TokenContents::Item,
|
contents: TokenContents::Item,
|
||||||
span,
|
span,
|
||||||
}) = parts.get(0)
|
}) = parts.first()
|
||||||
{
|
{
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let bytes = working_set.get_span_contents(*span);
|
let bytes = working_set.get_span_contents(*span);
|
||||||
@ -220,6 +220,10 @@ pub fn eval_source(
|
|||||||
source,
|
source,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
|
report_error(&working_set, warning);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
set_last_exit_code(stack, 1);
|
set_last_exit_code(stack, 1);
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
pub mod support;
|
pub mod support;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
use nu_cli::NuCompleter;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use support::{completions_helpers::new_quote_engine, file, folder, match_suggestions, new_engine};
|
use support::{
|
||||||
|
completions_helpers::{new_partial_engine, new_quote_engine},
|
||||||
|
file, folder, match_suggestions, new_engine,
|
||||||
|
};
|
||||||
|
|
||||||
#[fixture]
|
#[fixture]
|
||||||
fn completer() -> NuCompleter {
|
fn completer() -> NuCompleter {
|
||||||
@ -54,6 +59,29 @@ fn extern_completer() -> NuCompleter {
|
|||||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn custom_completer() -> NuCompleter {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, mut engine, mut stack) = new_engine();
|
||||||
|
|
||||||
|
// Add record value as example
|
||||||
|
let record = r#"
|
||||||
|
let external_completer = {|spans|
|
||||||
|
$spans
|
||||||
|
}
|
||||||
|
|
||||||
|
$env.config.completions.external = {
|
||||||
|
enable: true
|
||||||
|
max_results: 100
|
||||||
|
completer: $external_completer
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
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();
|
||||||
@ -63,7 +91,7 @@ fn variables_dollar_sign_with_varialblecompletion() {
|
|||||||
let target_dir = "$ ";
|
let target_dir = "$ ";
|
||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
assert_eq!(7, suggestions.len());
|
assert_eq!(8, suggestions.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -116,15 +144,34 @@ fn dotnu_completions() {
|
|||||||
let completion_str = "source-env ".to_string();
|
let completion_str = "source-env ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
assert_eq!(2, suggestions.len());
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
||||||
|
|
||||||
// Test use completion
|
// Test use completion
|
||||||
let completion_str = "use ".to_string();
|
let completion_str = "use ".to_string();
|
||||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
assert_eq!(1, suggestions.len());
|
assert_eq!(2, suggestions.len());
|
||||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
||||||
|
|
||||||
|
// Test overlay use completion
|
||||||
|
let completion_str = "overlay use ".to_string();
|
||||||
|
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||||
|
|
||||||
|
assert_eq!(2, suggestions.len());
|
||||||
|
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -136,7 +183,7 @@ fn external_completer_trailing_space() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
assert_eq!("", suggestions.get(2).unwrap().value);
|
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||||
}
|
}
|
||||||
@ -148,7 +195,7 @@ fn external_completer_no_trailing_space() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(2, suggestions.len());
|
assert_eq!(2, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +206,7 @@ fn external_completer_pass_flags() {
|
|||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.first().unwrap().value);
|
||||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||||
assert_eq!("--", suggestions.get(2).unwrap().value);
|
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||||
}
|
}
|
||||||
@ -180,6 +227,7 @@ fn file_completions() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
folder(dir.join("another")),
|
folder(dir.join("another")),
|
||||||
file(dir.join("custom_completion.nu")),
|
file(dir.join("custom_completion.nu")),
|
||||||
|
folder(dir.join("directory_completion")),
|
||||||
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")),
|
||||||
@ -201,6 +249,87 @@ fn file_completions() {
|
|||||||
match_suggestions(expected_paths, suggestions);
|
match_suggestions(expected_paths, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_completions() {
|
||||||
|
// Create a new engine
|
||||||
|
let (dir, _, engine, stack) = new_partial_engine();
|
||||||
|
|
||||||
|
// Instantiate a new completer
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
// Test completions for a folder's name
|
||||||
|
let target_dir = format!("cd {}", file(dir.join("pa")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
folder(dir.join("partial_a")),
|
||||||
|
folder(dir.join("partial_b")),
|
||||||
|
folder(dir.join("partial_c")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completions for the files whose name begin with "h"
|
||||||
|
// and are present under directories whose names begin with "pa"
|
||||||
|
let dir_str = file(dir.join("pa").join("h"));
|
||||||
|
let target_dir = format!("cp {dir_str}");
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
file(dir.join("partial_a").join("hello")),
|
||||||
|
file(dir.join("partial_a").join("hola")),
|
||||||
|
file(dir.join("partial_b").join("hello_b")),
|
||||||
|
file(dir.join("partial_b").join("hi_b")),
|
||||||
|
file(dir.join("partial_c").join("hello_c")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completion for all files under directories whose names begin with "pa"
|
||||||
|
let dir_str = folder(dir.join("pa"));
|
||||||
|
let target_dir = format!("ls {dir_str}");
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
file(dir.join("partial_a").join("anotherfile")),
|
||||||
|
file(dir.join("partial_a").join("hello")),
|
||||||
|
file(dir.join("partial_a").join("hola")),
|
||||||
|
file(dir.join("partial_b").join("hello_b")),
|
||||||
|
file(dir.join("partial_b").join("hi_b")),
|
||||||
|
file(dir.join("partial_c").join("hello_c")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completion for a single file
|
||||||
|
let dir_str = file(dir.join("fi").join("so"));
|
||||||
|
let target_dir = format!("rm {dir_str}");
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
// Test completion where there is a sneaky `..` in the path
|
||||||
|
let dir_str = file(dir.join("par").join("..").join("fi").join("so"));
|
||||||
|
let target_dir = format!("rm {dir_str}");
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
// Create the expected values
|
||||||
|
let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_ls_with_filecompletion() {
|
fn command_ls_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
@ -214,6 +343,7 @@ fn command_ls_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -224,6 +354,7 @@ fn command_ls_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -246,6 +377,7 @@ fn command_open_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -256,6 +388,7 @@ fn command_open_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -279,6 +412,7 @@ fn command_rm_with_globcompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -289,6 +423,7 @@ fn command_rm_with_globcompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -312,6 +447,7 @@ fn command_cp_with_globcompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -322,6 +458,7 @@ fn command_cp_with_globcompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -345,6 +482,7 @@ fn command_save_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -355,6 +493,7 @@ fn command_save_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -378,6 +517,7 @@ fn command_touch_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -388,6 +528,7 @@ fn command_touch_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -411,6 +552,7 @@ fn command_watch_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -421,6 +563,7 @@ fn command_watch_with_filecompletion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -441,10 +584,26 @@ fn file_completion_quoted() {
|
|||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"`--help`".to_string(),
|
||||||
|
"`-42`".to_string(),
|
||||||
|
"`-inf`".to_string(),
|
||||||
|
"`4.2`".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(),
|
||||||
"`te(st).txt`".to_string(),
|
"`te(st).txt`".to_string(),
|
||||||
|
format!("`{}`", folder("test dir".into())),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
|
||||||
|
let dir: PathBuf = "test dir".into();
|
||||||
|
let target_dir = format!("open '{}'", folder(dir.clone()));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
format!("`{}`", file(dir.join("double quote"))),
|
||||||
|
format!("`{}`", file(dir.join("single quote"))),
|
||||||
];
|
];
|
||||||
|
|
||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
@ -500,6 +659,7 @@ fn folder_with_directorycompletions() {
|
|||||||
// 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("another")),
|
||||||
|
folder(dir.join("directory_completion")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.join("test_a")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.join("test_b")),
|
||||||
folder(dir.join(".hidden_folder")),
|
folder(dir.join(".hidden_folder")),
|
||||||
@ -693,7 +853,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
|||||||
// Change config adding the external completer
|
// Change config adding the external completer
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
config.external_completer = Some(latest_block_id);
|
config.external_completer = Some(latest_block_id);
|
||||||
engine_state.set_config(&config);
|
engine_state.set_config(config);
|
||||||
|
|
||||||
// Instantiate 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);
|
||||||
@ -714,6 +874,7 @@ fn unknown_command_completion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -724,6 +885,7 @@ fn unknown_command_completion() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -774,6 +936,7 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another\\".to_string(),
|
"another\\".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion\\".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a\\".to_string(),
|
"test_a\\".to_string(),
|
||||||
"test_b\\".to_string(),
|
"test_b\\".to_string(),
|
||||||
@ -784,6 +947,7 @@ fn filecompletions_triggers_after_cursor() {
|
|||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
"another/".to_string(),
|
"another/".to_string(),
|
||||||
"custom_completion.nu".to_string(),
|
"custom_completion.nu".to_string(),
|
||||||
|
"directory_completion/".to_string(),
|
||||||
"nushell".to_string(),
|
"nushell".to_string(),
|
||||||
"test_a/".to_string(),
|
"test_a/".to_string(),
|
||||||
"test_b/".to_string(),
|
"test_b/".to_string(),
|
||||||
@ -836,6 +1000,34 @@ fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
|||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) {
|
||||||
|
let suggestions = custom_completer.complete("cmd foo bar", 8);
|
||||||
|
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) {
|
||||||
|
let suggestions = custom_completer.complete("cmd foo bar", 8);
|
||||||
|
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) {
|
||||||
|
let suggestions = custom_completer.complete("cmd foo bar", 11);
|
||||||
|
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) {
|
||||||
|
let suggestions = custom_completer.complete("cmd foo bar ", 12);
|
||||||
|
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into(), "".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
#[ignore = "was reverted, still needs fixing"]
|
#[ignore = "was reverted, still needs fixing"]
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn alias_offset_bug_7648() {
|
fn alias_offset_bug_7648() {
|
||||||
|
@ -109,6 +109,42 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_partial_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||||
|
// Target folder inside assets
|
||||||
|
let dir = fs::fixtures().join("partial_completions");
|
||||||
|
let mut dir_str = dir
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap_or_default();
|
||||||
|
dir_str.push(SEP);
|
||||||
|
|
||||||
|
// Create a new engine with default context
|
||||||
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
|
// New stack
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
// Add pwd as env var
|
||||||
|
stack.add_env_var(
|
||||||
|
"PWD".to_string(),
|
||||||
|
Value::string(dir_str.clone(), nu_protocol::Span::new(0, dir_str.len())),
|
||||||
|
);
|
||||||
|
stack.add_env_var(
|
||||||
|
"TEST".to_string(),
|
||||||
|
Value::string(
|
||||||
|
"NUSHELL".to_string(),
|
||||||
|
nu_protocol::Span::new(0, dir_str.len()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge environment into the permanent state
|
||||||
|
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||||
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
|
(dir, dir_str, engine_state, stack)
|
||||||
|
}
|
||||||
|
|
||||||
// match a list of suggestions with the expected values
|
// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
let expected_len = expected.len();
|
let expected_len = expected.len();
|
||||||
|
@ -5,14 +5,21 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.85.0"
|
version = "0.88.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.85.0" }
|
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.85.0" }
|
nu-glob = { path = "../nu-glob", version = "0.88.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.85.0" }
|
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
||||||
nu-protocol = { version = "0.85.0", path = "../nu-protocol" }
|
nu-path = { path = "../nu-path", version = "0.88.2" }
|
||||||
indexmap = { version = "2.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
nu-utils = { path = "../nu-utils", version = "0.88.2" }
|
||||||
|
|
||||||
|
indexmap = "2.1"
|
||||||
|
miette = "5.10.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
||||||
|
rstest = "0.18.2"
|
||||||
|
21
crates/nu-cmd-base/LICENSE
Normal file
21
crates/nu-cmd-base/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 - 2023 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.
|
205
crates/nu-cmd-base/src/arg_glob.rs
Normal file
205
crates/nu-cmd-base/src/arg_glob.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// utilities for expanding globs in command arguments
|
||||||
|
|
||||||
|
use nu_glob::{glob_with_parent, MatchOptions, Paths};
|
||||||
|
use nu_protocol::{ShellError, Spanned};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
// standard glob options to use for filesystem command arguments
|
||||||
|
|
||||||
|
const GLOB_PARAMS: MatchOptions = MatchOptions {
|
||||||
|
case_sensitive: true,
|
||||||
|
require_literal_separator: false,
|
||||||
|
require_literal_leading_dot: false,
|
||||||
|
recursive_match_hidden_dir: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle an argument that could be a literal path or a glob.
|
||||||
|
// if literal path, return just that (whether user can access it or not).
|
||||||
|
// if glob, expand into matching paths, using GLOB_PARAMS options.
|
||||||
|
pub fn arg_glob(
|
||||||
|
pattern: &Spanned<String>, // alleged path or glob
|
||||||
|
cwd: &Path, // current working directory
|
||||||
|
) -> Result<Paths, ShellError> {
|
||||||
|
arg_glob_opt(pattern, cwd, GLOB_PARAMS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// variant of [arg_glob] that requires literal dot prefix in pattern to match dot-prefixed path.
|
||||||
|
pub fn arg_glob_leading_dot(pattern: &Spanned<String>, cwd: &Path) -> Result<Paths, ShellError> {
|
||||||
|
arg_glob_opt(
|
||||||
|
pattern,
|
||||||
|
cwd,
|
||||||
|
MatchOptions {
|
||||||
|
require_literal_leading_dot: true,
|
||||||
|
..GLOB_PARAMS
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_glob_opt(
|
||||||
|
pattern: &Spanned<String>,
|
||||||
|
cwd: &Path,
|
||||||
|
options: MatchOptions,
|
||||||
|
) -> Result<Paths, ShellError> {
|
||||||
|
// remove ansi coloring (?)
|
||||||
|
let pattern = {
|
||||||
|
Spanned {
|
||||||
|
item: nu_utils::strip_ansi_string_unlikely(pattern.item.clone()),
|
||||||
|
span: pattern.span,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if there's a file with same path as the pattern, just return that.
|
||||||
|
let pp = cwd.join(&pattern.item);
|
||||||
|
let md = fs::metadata(pp);
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match md {
|
||||||
|
Ok(_metadata) => {
|
||||||
|
return Ok(Paths::single(&PathBuf::from(pattern.item), cwd));
|
||||||
|
}
|
||||||
|
// file not found, but also "invalid chars in file" (e.g * on Windows). Fall through and glob
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// user wasn't referring to a specific thing in filesystem, try to glob it.
|
||||||
|
match glob_with_parent(&pattern.item, options, cwd) {
|
||||||
|
Ok(p) => Ok(p),
|
||||||
|
Err(pat_err) => Err(ShellError::InvalidGlobPattern {
|
||||||
|
msg: pat_err.msg.into(),
|
||||||
|
span: pattern.span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use nu_glob::GlobResult;
|
||||||
|
use nu_protocol::{Span, Spanned};
|
||||||
|
use nu_test_support::fs::Stub::EmptyFile;
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
fn spanned_string(str: &str) -> Spanned<String> {
|
||||||
|
Spanned {
|
||||||
|
item: str.to_string(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_something() {
|
||||||
|
let act = arg_glob(&spanned_string("*"), &PathBuf::from("."));
|
||||||
|
assert!(act.is_ok());
|
||||||
|
for f in act.expect("checked ok") {
|
||||||
|
match f {
|
||||||
|
Ok(p) => {
|
||||||
|
assert!(!p.to_str().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
Err(e) => panic!("unexpected error {:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_format_error() {
|
||||||
|
let act = arg_glob(&spanned_string(r#"ab]c[def"#), &PathBuf::from("."));
|
||||||
|
assert!(act.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("*", 4, "no dirs")]
|
||||||
|
#[case("**/*", 7, "incl dirs")]
|
||||||
|
fn glob_subdirs(#[case] pat: &str, #[case] exp_count: usize, #[case] case: &str) {
|
||||||
|
Playground::setup("glob_subdirs", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![
|
||||||
|
EmptyFile("yehuda.txt"),
|
||||||
|
EmptyFile("jttxt"),
|
||||||
|
EmptyFile("andres.txt"),
|
||||||
|
]);
|
||||||
|
sandbox.mkdir(".children");
|
||||||
|
sandbox.within(".children").with_files(vec![
|
||||||
|
EmptyFile("timothy.txt"),
|
||||||
|
EmptyFile("tiffany.txt"),
|
||||||
|
EmptyFile("trish.txt"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let p: Vec<GlobResult> = arg_glob(&spanned_string(pat), &dirs.test)
|
||||||
|
.expect("no error")
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
exp_count,
|
||||||
|
p.iter().filter(|i| i.is_ok()).count(),
|
||||||
|
" case: {case} ",
|
||||||
|
);
|
||||||
|
|
||||||
|
// expected behavior -- that directories are included in results (if name matches pattern)
|
||||||
|
let t = p
|
||||||
|
.iter()
|
||||||
|
.any(|i| i.as_ref().unwrap().to_string_lossy().contains(".children"));
|
||||||
|
assert!(t, "check for dir, case {case}");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("yehuda.txt", true, 1, "matches literal path")]
|
||||||
|
#[case("*", false, 3, "matches glob")]
|
||||||
|
#[case(r#"bad[glob.foo"#, true, 1, "matches literal, would be bad glob pat")]
|
||||||
|
fn exact_vs_glob(
|
||||||
|
#[case] pat: &str,
|
||||||
|
#[case] exp_matches_input: bool,
|
||||||
|
#[case] exp_count: usize,
|
||||||
|
#[case] case: &str,
|
||||||
|
) {
|
||||||
|
Playground::setup("exact_vs_glob", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![
|
||||||
|
EmptyFile("yehuda.txt"),
|
||||||
|
EmptyFile("jttxt"),
|
||||||
|
EmptyFile("bad[glob.foo"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = arg_glob(&spanned_string(pat), &dirs.test)
|
||||||
|
.expect("no error")
|
||||||
|
.collect::<Vec<GlobResult>>();
|
||||||
|
|
||||||
|
eprintln!("res: {:?}", res);
|
||||||
|
if exp_matches_input {
|
||||||
|
assert_eq!(
|
||||||
|
exp_count,
|
||||||
|
res.len(),
|
||||||
|
" case {case}: matches input, but count not 1? "
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&res[0].as_ref().unwrap().to_string_lossy(),
|
||||||
|
pat, // todo: is it OK for glob to return relative paths (not to current cwd, but to arg cwd of arg_glob)?
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(exp_count, res.len(), " case: {}: matched glob", case);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(r#"realbad[glob.foo"#, true, 1, "error, bad glob")]
|
||||||
|
fn exact_vs_bad_glob(
|
||||||
|
// if path doesn't exist but pattern is not valid glob, should get error.
|
||||||
|
#[case] pat: &str,
|
||||||
|
#[case] _exp_matches_input: bool,
|
||||||
|
#[case] _exp_count: usize,
|
||||||
|
#[case] _tag: &str,
|
||||||
|
) {
|
||||||
|
Playground::setup("exact_vs_bad_glob", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![
|
||||||
|
EmptyFile("yehuda.txt"),
|
||||||
|
EmptyFile("jttxt"),
|
||||||
|
EmptyFile("bad[glob.foo"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = arg_glob(&spanned_string(pat), &dirs.test);
|
||||||
|
assert!(res
|
||||||
|
.expect_err("expected error")
|
||||||
|
.to_string()
|
||||||
|
.contains("Invalid glob pattern"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
|
|||||||
let mut seen: IndexSet<String> = indexset! {};
|
let mut seen: IndexSet<String> = indexset! {};
|
||||||
for value in values {
|
for value in values {
|
||||||
let data_descriptors = match value {
|
let data_descriptors = match value {
|
||||||
Value::Record { val, .. } => val.cols.clone(),
|
Value::Record { val, .. } => val.columns().cloned().collect(),
|
||||||
_ => vec!["".to_string()],
|
_ => vec!["".to_string()],
|
||||||
};
|
};
|
||||||
for desc in data_descriptors {
|
for desc in data_descriptors {
|
||||||
|
@ -2,7 +2,6 @@ use crate::util::get_guaranteed_cwd;
|
|||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::ast::PathMember;
|
|
||||||
use nu_protocol::cli_error::{report_error, report_error_new};
|
use nu_protocol::cli_error::{report_error, report_error_new};
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId};
|
use nu_protocol::{BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId};
|
||||||
@ -62,28 +61,8 @@ pub fn eval_hook(
|
|||||||
value: &Value,
|
value: &Value,
|
||||||
hook_name: &str,
|
hook_name: &str,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let value_span = value.span();
|
|
||||||
|
|
||||||
// Hooks can optionally be a record in this form:
|
|
||||||
// {
|
|
||||||
// condition: {|before, after| ... } # block that evaluates to true/false
|
|
||||||
// code: # block or a string
|
|
||||||
// }
|
|
||||||
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
|
||||||
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
|
||||||
let condition_path = PathMember::String {
|
|
||||||
val: "condition".to_string(),
|
|
||||||
span: value_span,
|
|
||||||
optional: false,
|
|
||||||
};
|
|
||||||
let mut output = PipelineData::empty();
|
let mut output = PipelineData::empty();
|
||||||
|
|
||||||
let code_path = PathMember::String {
|
|
||||||
val: "code".to_string(),
|
|
||||||
span: value_span,
|
|
||||||
optional: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
@ -111,11 +90,11 @@ pub fn eval_hook(
|
|||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"valid source code".into(),
|
expected: "valid source code".into(),
|
||||||
"source code with syntax errors".into(),
|
value: "source code with syntax errors".into(),
|
||||||
span,
|
span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
(output, working_set.render(), vars)
|
(output, working_set.render(), vars)
|
||||||
@ -161,13 +140,17 @@ pub fn eval_hook(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { .. } => {
|
Value::Record { val, .. } => {
|
||||||
let do_run_hook = if let Ok(condition) =
|
// Hooks can optionally be a record in this form:
|
||||||
value.clone().follow_cell_path(&[condition_path], false)
|
// {
|
||||||
{
|
// condition: {|before, after| ... } # block that evaluates to true/false
|
||||||
|
// code: # block or a string
|
||||||
|
// }
|
||||||
|
// The condition block will be run to check whether the main hook (in `code`) should be run.
|
||||||
|
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||||
|
let do_run_hook = if let Some(condition) = val.get("condition") {
|
||||||
let other_span = condition.span();
|
let other_span = condition.span();
|
||||||
match condition {
|
if let Ok(block_id) = condition.as_block() {
|
||||||
Value::Block { val: block_id, .. } | Value::Closure { val: block_id, .. } => {
|
|
||||||
match run_hook_block(
|
match run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -177,30 +160,27 @@ pub fn eval_hook(
|
|||||||
other_span,
|
other_span,
|
||||||
) {
|
) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
if let PipelineData::Value(Value::Bool { val, .. }, ..) = pipeline_data
|
||||||
pipeline_data
|
|
||||||
{
|
{
|
||||||
val
|
val
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"boolean output".to_string(),
|
expected: "boolean output".to_string(),
|
||||||
"other PipelineData variant".to_string(),
|
value: "other PipelineData variant".to_string(),
|
||||||
other_span,
|
span: other_span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
other => {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
expected: "block".to_string(),
|
||||||
"block".to_string(),
|
value: format!("{}", condition.get_type()),
|
||||||
format!("{}", other.get_type()),
|
span: other_span,
|
||||||
other_span,
|
});
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// always run the hook
|
// always run the hook
|
||||||
@ -208,7 +188,13 @@ pub fn eval_hook(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if do_run_hook {
|
if do_run_hook {
|
||||||
let follow = value.clone().follow_cell_path(&[code_path], false)?;
|
let Some(follow) = val.get("code") else {
|
||||||
|
return Err(ShellError::CantFindColumn {
|
||||||
|
col_name: "code".into(),
|
||||||
|
span,
|
||||||
|
src_span: span,
|
||||||
|
});
|
||||||
|
};
|
||||||
let source_span = follow.span();
|
let source_span = follow.span();
|
||||||
match follow {
|
match follow {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
@ -236,11 +222,11 @@ pub fn eval_hook(
|
|||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
|
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"valid source code".into(),
|
expected: "valid source code".into(),
|
||||||
"source code with syntax errors".into(),
|
value: "source code with syntax errors".into(),
|
||||||
source_span,
|
span: source_span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
(output, working_set.render(), vars)
|
(output, working_set.render(), vars)
|
||||||
@ -274,28 +260,28 @@ pub fn eval_hook(
|
|||||||
run_hook_block(
|
run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
*block_id,
|
||||||
input,
|
input,
|
||||||
arguments,
|
arguments,
|
||||||
source_span,
|
source_span,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Value::Closure { val: block_id, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
run_hook_block(
|
run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
val.block_id,
|
||||||
input,
|
input,
|
||||||
arguments,
|
arguments,
|
||||||
source_span,
|
source_span,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"block or string".to_string(),
|
expected: "block or string".to_string(),
|
||||||
format!("{}", other.get_type()),
|
value: format!("{}", other.get_type()),
|
||||||
source_span,
|
span: source_span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,15 +289,15 @@ pub fn eval_hook(
|
|||||||
Value::Block { val: block_id, .. } => {
|
Value::Block { val: block_id, .. } => {
|
||||||
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
||||||
}
|
}
|
||||||
Value::Closure { val: block_id, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
output = run_hook_block(engine_state, stack, val.block_id, input, arguments, span)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
"string, block, record, or list of commands".into(),
|
expected: "string, block, record, or list of commands".into(),
|
||||||
format!("{}", other.get_type()),
|
value: format!("{}", other.get_type()),
|
||||||
other.span(),
|
span: other.span(),
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
mod arg_glob;
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
pub mod hook;
|
pub mod hook;
|
||||||
pub mod input_handler;
|
pub mod input_handler;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
pub use arg_glob::arg_glob;
|
||||||
|
pub use arg_glob::arg_glob_leading_dot;
|
||||||
|
@ -55,3 +55,73 @@ pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
|||||||
|
|
||||||
Ok((start, end))
|
Ok((start, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
|
||||||
|
For more help: (https://nushell.sh/book/configuration.html#configurations-with-built-in-commands)";
|
||||||
|
|
||||||
|
fn get_editor_commandline(
|
||||||
|
value: &Value,
|
||||||
|
var_name: &str,
|
||||||
|
) -> Result<(String, Vec<String>), ShellError> {
|
||||||
|
match value {
|
||||||
|
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
||||||
|
Value::List { vals, .. } if !vals.is_empty() => {
|
||||||
|
let mut editor_cmd = vals.iter().map(|l| l.as_string());
|
||||||
|
match editor_cmd.next().transpose()? {
|
||||||
|
Some(editor) if !editor.is_empty() => {
|
||||||
|
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
||||||
|
Ok((editor, params))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::GenericError {
|
||||||
|
error: "Editor executable is missing".into(),
|
||||||
|
msg: "Set the first element to an executable".into(),
|
||||||
|
span: Some(value.span()),
|
||||||
|
help: Some(HELP_MSG.into()),
|
||||||
|
inner: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String { .. } | Value::List { .. } => Err(ShellError::GenericError {
|
||||||
|
error: format!("{var_name} should be a non-empty string or list<String>"),
|
||||||
|
msg: "Specify an executable here".into(),
|
||||||
|
span: Some(value.span()),
|
||||||
|
help: Some(HELP_MSG.into()),
|
||||||
|
inner: vec![],
|
||||||
|
}),
|
||||||
|
x => Err(ShellError::CantConvert {
|
||||||
|
to_type: "string or list<string>".into(),
|
||||||
|
from_type: x.get_type().to_string(),
|
||||||
|
span: value.span(),
|
||||||
|
help: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_editor(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<(String, Vec<String>), ShellError> {
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
let env_vars = stack.get_env_vars(engine_state);
|
||||||
|
|
||||||
|
if let Ok(buff_editor) =
|
||||||
|
get_editor_commandline(&config.buffer_editor, "$env.config.buffer_editor")
|
||||||
|
{
|
||||||
|
Ok(buff_editor)
|
||||||
|
} else if let Some(value) = env_vars.get("EDITOR") {
|
||||||
|
get_editor_commandline(value, "$env.EDITOR")
|
||||||
|
} else if let Some(value) = env_vars.get("VISUAL") {
|
||||||
|
get_editor_commandline(value, "$env.VISUAL")
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "No editor configured".into(),
|
||||||
|
msg:
|
||||||
|
"Please specify one via `$env.config.buffer_editor` or `$env.EDITOR`/`$env.VISUAL`"
|
||||||
|
.into(),
|
||||||
|
span: Some(span),
|
||||||
|
help: Some(HELP_MSG.into()),
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-dataframe"
|
name = "nu-cmd-dataframe"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||||
version = "0.85.0"
|
version = "0.88.2"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -13,18 +13,22 @@ version = "0.85.0"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.85.0" }
|
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.85.0" }
|
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.85.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
||||||
fancy-regex = "0.11"
|
chrono-tz = "0.8"
|
||||||
indexmap = { version = "2.0" }
|
fancy-regex = "0.12"
|
||||||
|
indexmap = { version = "2.1" }
|
||||||
num = { version = "0.4", optional = true }
|
num = { version = "0.4", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
sqlparser = { version = "0.36.1", optional = true }
|
sqlparser = { version = "0.39", optional = true }
|
||||||
polars-io = { version = "0.32", features = ["avro"], optional = true }
|
polars-io = { version = "0.35", features = ["avro"], optional = true }
|
||||||
|
polars-arrow = { version = "0.35", optional = true }
|
||||||
|
polars-ops = { version = "0.35", optional = true }
|
||||||
|
polars-plan = { version = "0.35", optional = true }
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
features = [
|
features = [
|
||||||
@ -38,7 +42,11 @@ features = [
|
|||||||
"dtype-categorical",
|
"dtype-categorical",
|
||||||
"dtype-datetime",
|
"dtype-datetime",
|
||||||
"dtype-struct",
|
"dtype-struct",
|
||||||
"dynamic_groupby",
|
"dtype-i8",
|
||||||
|
"dtype-i16",
|
||||||
|
"dtype-u8",
|
||||||
|
"dtype-u16",
|
||||||
|
"dynamic_group_by",
|
||||||
"ipc",
|
"ipc",
|
||||||
"is_in",
|
"is_in",
|
||||||
"json",
|
"json",
|
||||||
@ -54,12 +62,12 @@ features = [
|
|||||||
"to_dummies",
|
"to_dummies",
|
||||||
]
|
]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.32"
|
version = "0.35"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dataframe = ["num", "polars", "polars-io", "sqlparser"]
|
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "sqlparser"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.85.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.85.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
||||||
|
@ -68,25 +68,23 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let new_df = col_string
|
let new_df = col_string
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Empty names list".into(),
|
||||||
"Empty names list".into(),
|
msg: "No column names were found".into(),
|
||||||
"No column names were found".into(),
|
span: Some(col_span),
|
||||||
Some(col_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.and_then(|col| {
|
.and_then(|col| {
|
||||||
df.as_ref().drop(&col.item).map_err(|e| {
|
df.as_ref()
|
||||||
ShellError::GenericError(
|
.drop(&col.item)
|
||||||
"Error dropping column".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error dropping column".into(),
|
||||||
Some(col.span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(col.span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -96,14 +94,14 @@ fn command(
|
|||||||
.iter()
|
.iter()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.try_fold(new_df, |new_df, col| {
|
.try_fold(new_df, |new_df, col| {
|
||||||
new_df.drop(&col.item).map_err(|e| {
|
new_df
|
||||||
ShellError::GenericError(
|
.drop(&col.item)
|
||||||
"Error dropping column".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error dropping column".into(),
|
||||||
Some(col.span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(col.span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
|
@ -100,14 +100,12 @@ fn command(
|
|||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.unique(subset_slice, keep_strategy, None)
|
.unique(subset_slice, keep_strategy, None)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error dropping duplicates".into(),
|
||||||
"Error dropping duplicates".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(col_span),
|
||||||
Some(col_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -115,14 +115,12 @@ fn command(
|
|||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.drop_nulls(subset_slice)
|
.drop_nulls(subset_slice)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error dropping nulls".into(),
|
||||||
"Error dropping nulls".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(col_span),
|
||||||
Some(col_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use super::super::values::NuDataFrame;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Type,
|
||||||
};
|
};
|
||||||
use polars::prelude::DataFrameOps;
|
use polars::{prelude::*, series::Series};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Dummies;
|
pub struct Dummies;
|
||||||
@ -34,24 +34,15 @@ impl Command for Dummies {
|
|||||||
description: "Create new dataframe with dummy variables from a dataframe",
|
description: "Create new dataframe with dummy variables from a dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dummies",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dummies",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_series(
|
||||||
Column::new(
|
vec![
|
||||||
"a_1".to_string(),
|
Series::new("a_1", &[1_u8, 0]),
|
||||||
vec![Value::test_int(1), Value::test_int(0)],
|
Series::new("a_3", &[0_u8, 1]),
|
||||||
),
|
Series::new("b_2", &[1_u8, 0]),
|
||||||
Column::new(
|
Series::new("b_4", &[0_u8, 1]),
|
||||||
"a_3".to_string(),
|
],
|
||||||
vec![Value::test_int(0), Value::test_int(1)],
|
Span::test_data(),
|
||||||
),
|
)
|
||||||
Column::new(
|
|
||||||
"b_2".to_string(),
|
|
||||||
vec![Value::test_int(1), Value::test_int(0)],
|
|
||||||
),
|
|
||||||
Column::new(
|
|
||||||
"b_4".to_string(),
|
|
||||||
vec![Value::test_int(0), Value::test_int(1)],
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -60,38 +51,14 @@ impl Command for Dummies {
|
|||||||
description: "Create new dataframe with dummy variables from a series",
|
description: "Create new dataframe with dummy variables from a series",
|
||||||
example: "[1 2 2 3 3] | dfr into-df | dfr dummies",
|
example: "[1 2 2 3 3] | dfr into-df | dfr dummies",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_series(
|
||||||
Column::new(
|
|
||||||
"0_1".to_string(),
|
|
||||||
vec![
|
vec![
|
||||||
Value::test_int(1),
|
Series::new("0_1", &[1_u8, 0, 0, 0, 0]),
|
||||||
Value::test_int(0),
|
Series::new("0_2", &[0_u8, 1, 1, 0, 0]),
|
||||||
Value::test_int(0),
|
Series::new("0_3", &[0_u8, 0, 0, 1, 1]),
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(0),
|
|
||||||
],
|
],
|
||||||
),
|
Span::test_data(),
|
||||||
Column::new(
|
)
|
||||||
"0_2".to_string(),
|
|
||||||
vec![
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(0),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column::new(
|
|
||||||
"0_3".to_string(),
|
|
||||||
vec![
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(0),
|
|
||||||
Value::test_int(1),
|
|
||||||
Value::test_int(1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -121,14 +88,12 @@ fn command(
|
|||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.to_dummies(None, drop_first)
|
.to_dummies(None, drop_first)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error calculating dummies".into(),
|
||||||
"Error calculating dummies".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: Some("The only allowed column types for dummies are String or Int".into()),
|
||||||
Some("The only allowed column types for dummies are String or Int".into()),
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -105,26 +105,22 @@ fn command_eager(
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let mask = NuDataFrame::try_from_value(mask_value)?.as_series(mask_span)?;
|
let mask = NuDataFrame::try_from_value(mask_value)?.as_series(mask_span)?;
|
||||||
let mask = mask.bool().map_err(|e| {
|
let mask = mask.bool().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to bool".into(),
|
||||||
"Error casting to bool".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(mask_span),
|
||||||
Some(mask_span),
|
help: Some("Perhaps you want to use a series with booleans as mask".into()),
|
||||||
Some("Perhaps you want to use a series with booleans as mask".into()),
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.filter(mask)
|
.filter(mask)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error filtering dataframe".into(),
|
||||||
"Error filtering dataframe".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: Some("The only allowed column types for dummies are String or Int".into()),
|
||||||
Some("The only allowed column types for dummies are String or Int".into()),
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -70,14 +70,12 @@ fn command(
|
|||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.select(col_string)
|
.select(col_string)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error selecting columns".into(),
|
||||||
"Error selecting columns".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(col_span),
|
||||||
Some(col_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -152,37 +152,33 @@ fn command(
|
|||||||
let mut res = df
|
let mut res = df
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.melt(&id_col_string, &val_col_string)
|
.melt(&id_col_string, &val_col_string)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error calculating melt".into(),
|
||||||
"Error calculating melt".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(name) = &variable_name {
|
if let Some(name) = &variable_name {
|
||||||
res.rename("variable", &name.item).map_err(|e| {
|
res.rename("variable", &name.item)
|
||||||
ShellError::GenericError(
|
.map_err(|e| ShellError::GenericError {
|
||||||
"Error renaming column".into(),
|
error: "Error renaming column".into(),
|
||||||
e.to_string(),
|
msg: e.to_string(),
|
||||||
Some(name.span),
|
span: Some(name.span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = &value_name {
|
if let Some(name) = &value_name {
|
||||||
res.rename("value", &name.item).map_err(|e| {
|
res.rename("value", &name.item)
|
||||||
ShellError::GenericError(
|
.map_err(|e| ShellError::GenericError {
|
||||||
"Error renaming column".into(),
|
error: "Error renaming column".into(),
|
||||||
e.to_string(),
|
msg: e.to_string(),
|
||||||
Some(name.span),
|
span: Some(name.span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,50 +194,50 @@ fn check_column_datatypes<T: AsRef<str>>(
|
|||||||
col_span: Span,
|
col_span: Span,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
if cols.is_empty() {
|
if cols.is_empty() {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Merge error".into(),
|
error: "Merge error".into(),
|
||||||
"empty column list".into(),
|
msg: "empty column list".into(),
|
||||||
Some(col_span),
|
span: Some(col_span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking if they are same type
|
// Checking if they are same type
|
||||||
if cols.len() > 1 {
|
if cols.len() > 1 {
|
||||||
for w in cols.windows(2) {
|
for w in cols.windows(2) {
|
||||||
let l_series = df.column(w[0].as_ref()).map_err(|e| {
|
let l_series = df
|
||||||
ShellError::GenericError(
|
.column(w[0].as_ref())
|
||||||
"Error selecting columns".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error selecting columns".into(),
|
||||||
Some(col_span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(col_span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let r_series = df.column(w[1].as_ref()).map_err(|e| {
|
let r_series = df
|
||||||
ShellError::GenericError(
|
.column(w[1].as_ref())
|
||||||
"Error selecting columns".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error selecting columns".into(),
|
||||||
Some(col_span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(col_span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if l_series.dtype() != r_series.dtype() {
|
if l_series.dtype() != r_series.dtype() {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Merge error".into(),
|
error: "Merge error".into(),
|
||||||
"found different column types in list".into(),
|
msg: "found different column types in list".into(),
|
||||||
Some(col_span),
|
span: Some(col_span),
|
||||||
Some(format!(
|
help: Some(format!(
|
||||||
"datatypes {} and {} are incompatible",
|
"datatypes {} and {} are incompatible",
|
||||||
l_series.dtype(),
|
l_series.dtype(),
|
||||||
r_series.dtype()
|
r_series.dtype()
|
||||||
)),
|
)),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,15 +121,15 @@ fn command(
|
|||||||
"json" => from_json(engine_state, stack, call),
|
"json" => from_json(engine_state, stack, call),
|
||||||
"jsonl" => from_jsonl(engine_state, stack, call),
|
"jsonl" => from_jsonl(engine_state, stack, call),
|
||||||
"avro" => from_avro(engine_state, stack, call),
|
"avro" => from_avro(engine_state, stack, call),
|
||||||
_ => Err(ShellError::FileNotFoundCustom(
|
_ => Err(ShellError::FileNotFoundCustom {
|
||||||
format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
msg: format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
||||||
blamed,
|
span: blamed,
|
||||||
)),
|
}),
|
||||||
},
|
},
|
||||||
None => Err(ShellError::FileNotFoundCustom(
|
None => Err(ShellError::FileNotFoundCustom {
|
||||||
"File without extension".into(),
|
msg: "File without extension".into(),
|
||||||
file.span,
|
span: file.span,
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
.map(|value| PipelineData::Value(value, None))
|
.map(|value| PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
@ -150,17 +150,16 @@ fn from_parquet(
|
|||||||
low_memory: false,
|
low_memory: false,
|
||||||
cloud_options: None,
|
cloud_options: None,
|
||||||
use_statistics: false,
|
use_statistics: false,
|
||||||
|
hive_partitioning: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
|
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Parquet reader error".into(),
|
||||||
"Parquet reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -169,14 +168,12 @@ fn from_parquet(
|
|||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||||
|
|
||||||
let r = File::open(&file.item).map_err(|e| {
|
let r = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error opening file".into(),
|
||||||
"Error opening file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let reader = ParquetReader::new(r);
|
let reader = ParquetReader::new(r);
|
||||||
|
|
||||||
@ -187,14 +184,12 @@ fn from_parquet(
|
|||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Parquet reader error".into(),
|
||||||
"Parquet reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -210,14 +205,12 @@ fn from_avro(
|
|||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||||
|
|
||||||
let r = File::open(&file.item).map_err(|e| {
|
let r = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error opening file".into(),
|
||||||
"Error opening file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let reader = AvroReader::new(r);
|
let reader = AvroReader::new(r);
|
||||||
|
|
||||||
@ -228,14 +221,12 @@ fn from_avro(
|
|||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Avro reader error".into(),
|
||||||
"Avro reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -258,14 +249,12 @@ fn from_ipc(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "IPC reader error".into(),
|
||||||
"IPC reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -274,14 +263,12 @@ fn from_ipc(
|
|||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||||
|
|
||||||
let r = File::open(&file.item).map_err(|e| {
|
let r = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error opening file".into(),
|
||||||
"Error opening file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let reader = IpcReader::new(r);
|
let reader = IpcReader::new(r);
|
||||||
|
|
||||||
@ -292,14 +279,12 @@ fn from_ipc(
|
|||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "IPC reader error".into(),
|
||||||
"IPC reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -313,14 +298,12 @@ fn from_json(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let file = File::open(&file.item).map_err(|e| {
|
let file = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error opening file".into(),
|
||||||
"Error opening file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let buf_reader = BufReader::new(file);
|
let buf_reader = BufReader::new(file);
|
||||||
@ -328,14 +311,12 @@ fn from_json(
|
|||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Json reader error".into(),
|
||||||
"Json reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -349,14 +330,12 @@ fn from_jsonl(
|
|||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let file = File::open(&file.item).map_err(|e| {
|
let file = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error opening file".into(),
|
||||||
"Error opening file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let buf_reader = BufReader::new(file);
|
let buf_reader = BufReader::new(file);
|
||||||
@ -366,14 +345,12 @@ fn from_jsonl(
|
|||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Json lines reader error".into(),
|
||||||
"Json lines reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -399,19 +376,19 @@ fn from_csv(
|
|||||||
None => csv_reader,
|
None => csv_reader,
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
if d.item.len() != 1 {
|
if d.item.len() != 1 {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Incorrect delimiter".into(),
|
error: "Incorrect delimiter".into(),
|
||||||
"Delimiter has to be one character".into(),
|
msg: "Delimiter has to be one character".into(),
|
||||||
Some(d.span),
|
span: Some(d.span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
} else {
|
} else {
|
||||||
let delimiter = match d.item.chars().next() {
|
let delimiter = match d.item.chars().next() {
|
||||||
Some(d) => d as u8,
|
Some(d) => d as u8,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
csv_reader.with_delimiter(delimiter)
|
csv_reader.with_separator(delimiter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -430,14 +407,12 @@ fn from_csv(
|
|||||||
|
|
||||||
let df: NuLazyFrame = csv_reader
|
let df: NuLazyFrame = csv_reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Parquet reader error".into(),
|
||||||
"Parquet reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
@ -445,14 +420,12 @@ fn from_csv(
|
|||||||
} else {
|
} else {
|
||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let csv_reader = CsvReader::from_path(&file.item)
|
let csv_reader = CsvReader::from_path(&file.item)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error creating CSV reader".into(),
|
||||||
"Error creating CSV reader".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file.span),
|
||||||
Some(file.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.with_encoding(CsvEncoding::LossyUtf8);
|
.with_encoding(CsvEncoding::LossyUtf8);
|
||||||
|
|
||||||
@ -460,19 +433,19 @@ fn from_csv(
|
|||||||
None => csv_reader,
|
None => csv_reader,
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
if d.item.len() != 1 {
|
if d.item.len() != 1 {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Incorrect delimiter".into(),
|
error: "Incorrect delimiter".into(),
|
||||||
"Delimiter has to be one character".into(),
|
msg: "Delimiter has to be one character".into(),
|
||||||
Some(d.span),
|
span: Some(d.span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
} else {
|
} else {
|
||||||
let delimiter = match d.item.chars().next() {
|
let delimiter = match d.item.chars().next() {
|
||||||
Some(d) => d as u8,
|
Some(d) => d as u8,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
csv_reader.with_delimiter(delimiter)
|
csv_reader.with_separator(delimiter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -496,14 +469,12 @@ fn from_csv(
|
|||||||
|
|
||||||
let df: NuDataFrame = csv_reader
|
let df: NuDataFrame = csv_reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Parquet reader error".into(),
|
||||||
"Parquet reader error".into(),
|
msg: format!("{e:?}"),
|
||||||
format!("{e:?}"),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
@ -76,14 +76,14 @@ fn command(
|
|||||||
|
|
||||||
let mut ctx = SQLContext::new();
|
let mut ctx = SQLContext::new();
|
||||||
ctx.register("df", &df.df);
|
ctx.register("df", &df.df);
|
||||||
let df_sql = ctx.execute(&sql_query).map_err(|e| {
|
let df_sql = ctx
|
||||||
ShellError::GenericError(
|
.execute(&sql_query)
|
||||||
"Dataframe Error".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Dataframe Error".into(),
|
||||||
Some(call.head),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(call.head),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
let lazy = NuLazyFrame::new(false, df_sql);
|
let lazy = NuLazyFrame::new(false, df_sql);
|
||||||
|
|
||||||
|
@ -130,14 +130,14 @@ fn command_eager(
|
|||||||
let new_names = extract_strings(new_names)?;
|
let new_names = extract_strings(new_names)?;
|
||||||
|
|
||||||
for (from, to) in columns.iter().zip(new_names.iter()) {
|
for (from, to) in columns.iter().zip(new_names.iter()) {
|
||||||
df.as_mut().rename(from, to).map_err(|e| {
|
df.as_mut()
|
||||||
ShellError::GenericError(
|
.rename(from, to)
|
||||||
"Error renaming".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error renaming".into(),
|
||||||
Some(call.head),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(call.head),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ use nu_protocol::{
|
|||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||||
};
|
};
|
||||||
|
use polars::prelude::NamedFrom;
|
||||||
|
use polars::series::Series;
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
use super::super::values::NuDataFrame;
|
||||||
|
|
||||||
@ -52,12 +54,13 @@ impl Command for SampleDF {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Sample rows from dataframe",
|
description: "Sample rows from dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr sample -n 1",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr sample --n-rows 1",
|
||||||
result: None, // No expected value because sampling is random
|
result: None, // No expected value because sampling is random
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shows sample row using fraction and replace",
|
description: "Shows sample row using fraction and replace",
|
||||||
example: "[[a b]; [1 2] [3 4] [5 6]] | dfr into-df | dfr sample -f 0.5 -e",
|
example:
|
||||||
|
"[[a b]; [1 2] [3 4] [5 6]] | dfr into-df | dfr sample --fraction 0.5 --replace",
|
||||||
result: None, // No expected value because sampling is random
|
result: None, // No expected value because sampling is random
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -80,7 +83,7 @@ fn command(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let rows: Option<Spanned<usize>> = call.get_flag(engine_state, stack, "n-rows")?;
|
let rows: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "n-rows")?;
|
||||||
let fraction: Option<Spanned<f64>> = call.get_flag(engine_state, stack, "fraction")?;
|
let fraction: Option<Spanned<f64>> = call.get_flag(engine_state, stack, "fraction")?;
|
||||||
let seed: Option<u64> = call
|
let seed: Option<u64> = call
|
||||||
.get_flag::<i64>(engine_state, stack, "seed")?
|
.get_flag::<i64>(engine_state, stack, "seed")?
|
||||||
@ -93,42 +96,38 @@ fn command(
|
|||||||
match (rows, fraction) {
|
match (rows, fraction) {
|
||||||
(Some(rows), None) => df
|
(Some(rows), None) => df
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.sample_n(rows.item, replace, shuffle, seed)
|
.sample_n(&Series::new("s", &[rows.item]), replace, shuffle, seed)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error creating sample".into(),
|
||||||
"Error creating sample".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(rows.span),
|
||||||
Some(rows.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
(None, Some(frac)) => df
|
(None, Some(frac)) => df
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.sample_frac(frac.item, replace, shuffle, seed)
|
.sample_frac(&Series::new("frac", &[frac.item]), replace, shuffle, seed)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error creating sample".into(),
|
||||||
"Error creating sample".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(frac.span),
|
||||||
Some(frac.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
}),
|
||||||
)
|
(Some(_), Some(_)) => Err(ShellError::GenericError {
|
||||||
|
error: "Incompatible flags".into(),
|
||||||
|
msg: "Only one selection criterion allowed".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
}),
|
||||||
|
(None, None) => Err(ShellError::GenericError {
|
||||||
|
error: "No selection".into(),
|
||||||
|
msg: "No selection criterion was found".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: Some("Perhaps you want to use the flag -n or -f".into()),
|
||||||
|
inner: vec![],
|
||||||
}),
|
}),
|
||||||
(Some(_), Some(_)) => Err(ShellError::GenericError(
|
|
||||||
"Incompatible flags".into(),
|
|
||||||
"Only one selection criterion allowed".into(),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
(None, None) => Err(ShellError::GenericError(
|
|
||||||
"No selection".into(),
|
|
||||||
"No selection criterion was found".into(),
|
|
||||||
Some(call.head),
|
|
||||||
Some("Perhaps you want to use the flag -n or -f".into()),
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ use crate::dataframe::eager::sql_expr::parse_sql_expr;
|
|||||||
use polars::error::{ErrString, PolarsError};
|
use polars::error::{ErrString, PolarsError};
|
||||||
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
|
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
|
||||||
use sqlparser::ast::{
|
use sqlparser::ast::{
|
||||||
Expr as SqlExpr, Select, SelectItem, SetExpr, Statement, TableFactor, Value as SQLValue,
|
Expr as SqlExpr, GroupByExpr, Select, SelectItem, SetExpr, Statement, TableFactor,
|
||||||
|
Value as SQLValue,
|
||||||
};
|
};
|
||||||
use sqlparser::dialect::GenericDialect;
|
use sqlparser::dialect::GenericDialect;
|
||||||
use sqlparser::parser::Parser;
|
use sqlparser::parser::Parser;
|
||||||
@ -29,7 +30,7 @@ impl SQLContext {
|
|||||||
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
||||||
// Determine involved dataframe
|
// Determine involved dataframe
|
||||||
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
|
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
|
||||||
let tbl = select_stmt.from.get(0).ok_or_else(|| {
|
let tbl = select_stmt.from.first().ok_or_else(|| {
|
||||||
PolarsError::ComputeError(ErrString::from("No table found in select statement"))
|
PolarsError::ComputeError(ErrString::from("No table found in select statement"))
|
||||||
})?;
|
})?;
|
||||||
let mut alias_map = HashMap::new();
|
let mut alias_map = HashMap::new();
|
||||||
@ -37,7 +38,7 @@ impl SQLContext {
|
|||||||
TableFactor::Table { name, alias, .. } => {
|
TableFactor::Table { name, alias, .. } => {
|
||||||
let tbl_name = name
|
let tbl_name = name
|
||||||
.0
|
.0
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
PolarsError::ComputeError(ErrString::from(
|
PolarsError::ComputeError(ErrString::from(
|
||||||
"No table found in select statement",
|
"No table found in select statement",
|
||||||
@ -96,8 +97,13 @@ impl SQLContext {
|
|||||||
.collect::<Result<Vec<_>, PolarsError>>()?;
|
.collect::<Result<Vec<_>, PolarsError>>()?;
|
||||||
// Check for group by
|
// Check for group by
|
||||||
// After projection since there might be number.
|
// After projection since there might be number.
|
||||||
let group_by = select_stmt
|
let group_by = match &select_stmt.group_by {
|
||||||
.group_by
|
GroupByExpr::All =>
|
||||||
|
Err(
|
||||||
|
PolarsError::ComputeError("Group-By Error: Only positive number or expression are supported, not all".into())
|
||||||
|
)?,
|
||||||
|
GroupByExpr::Expressions(expressions) => expressions
|
||||||
|
}
|
||||||
.iter()
|
.iter()
|
||||||
.map(
|
.map(
|
||||||
|e|match e {
|
|e|match e {
|
||||||
@ -147,7 +153,7 @@ impl SQLContext {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(agg_pj, (proj_p, expr))| (expr.clone(), (proj_p, agg_pj + group_by.len())))
|
.map(|(agg_pj, (proj_p, expr))| (expr.clone(), (proj_p, agg_pj + group_by.len())))
|
||||||
.unzip();
|
.unzip();
|
||||||
let agg_df = df.groupby(group_by).agg(agg_projection);
|
let agg_df = df.group_by(group_by).agg(agg_projection);
|
||||||
let mut final_proj_pos = groupby_pos
|
let mut final_proj_pos = groupby_pos
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(agg_proj_pos)
|
.chain(agg_proj_pos)
|
||||||
@ -182,7 +188,7 @@ impl SQLContext {
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let ast = ast
|
let ast = ast
|
||||||
.get(0)
|
.first()
|
||||||
.ok_or_else(|| PolarsError::ComputeError(ErrString::from("No statement found")))?;
|
.ok_or_else(|| PolarsError::ComputeError(ErrString::from("No statement found")))?;
|
||||||
Ok(match ast {
|
Ok(match ast {
|
||||||
Statement::Query(query) => {
|
Statement::Query(query) => {
|
||||||
|
@ -2,8 +2,8 @@ use polars::error::PolarsError;
|
|||||||
use polars::prelude::{col, lit, DataType, Expr, LiteralValue, PolarsResult as Result, TimeUnit};
|
use polars::prelude::{col, lit, DataType, Expr, LiteralValue, PolarsResult as Result, TimeUnit};
|
||||||
|
|
||||||
use sqlparser::ast::{
|
use sqlparser::ast::{
|
||||||
BinaryOperator as SQLBinaryOperator, DataType as SQLDataType, Expr as SqlExpr,
|
ArrayElemTypeDef, BinaryOperator as SQLBinaryOperator, DataType as SQLDataType,
|
||||||
Function as SQLFunction, Value as SqlValue, WindowType,
|
Expr as SqlExpr, Function as SQLFunction, Value as SqlValue, WindowType,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
||||||
@ -13,7 +13,7 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
|||||||
| SQLDataType::Uuid
|
| SQLDataType::Uuid
|
||||||
| SQLDataType::Clob(_)
|
| SQLDataType::Clob(_)
|
||||||
| SQLDataType::Text
|
| SQLDataType::Text
|
||||||
| SQLDataType::String => DataType::Utf8,
|
| SQLDataType::String(_) => DataType::Utf8,
|
||||||
SQLDataType::Float(_) => DataType::Float32,
|
SQLDataType::Float(_) => DataType::Float32,
|
||||||
SQLDataType::Real => DataType::Float32,
|
SQLDataType::Real => DataType::Float32,
|
||||||
SQLDataType::Double => DataType::Float64,
|
SQLDataType::Double => DataType::Float64,
|
||||||
@ -31,9 +31,12 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
|||||||
SQLDataType::Time(_, _) => DataType::Time,
|
SQLDataType::Time(_, _) => DataType::Time,
|
||||||
SQLDataType::Timestamp(_, _) => DataType::Datetime(TimeUnit::Microseconds, None),
|
SQLDataType::Timestamp(_, _) => DataType::Datetime(TimeUnit::Microseconds, None),
|
||||||
SQLDataType::Interval => DataType::Duration(TimeUnit::Microseconds),
|
SQLDataType::Interval => DataType::Duration(TimeUnit::Microseconds),
|
||||||
SQLDataType::Array(inner_type) => match inner_type {
|
SQLDataType::Array(array_type_def) => match array_type_def {
|
||||||
Some(inner_type) => DataType::List(Box::new(map_sql_polars_datatype(inner_type)?)),
|
ArrayElemTypeDef::AngleBracket(inner_type)
|
||||||
None => {
|
| ArrayElemTypeDef::SquareBracket(inner_type) => {
|
||||||
|
DataType::List(Box::new(map_sql_polars_datatype(inner_type)?))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
return Err(PolarsError::ComputeError(
|
return Err(PolarsError::ComputeError(
|
||||||
"SQL Datatype Array(None) was not supported in polars-sql yet!".into(),
|
"SQL Datatype Array(None) was not supported in polars-sql yet!".into(),
|
||||||
))
|
))
|
||||||
@ -114,7 +117,11 @@ pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
|
|||||||
binary_op_(left, right, op)?
|
binary_op_(left, right, op)?
|
||||||
}
|
}
|
||||||
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
|
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
|
||||||
SqlExpr::Cast { expr, data_type } => cast_(parse_sql_expr(expr)?, data_type)?,
|
SqlExpr::Cast {
|
||||||
|
expr,
|
||||||
|
data_type,
|
||||||
|
format: _,
|
||||||
|
} => cast_(parse_sql_expr(expr)?, data_type)?,
|
||||||
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
|
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
|
||||||
SqlExpr::Value(value) => literal_expr(value)?,
|
SqlExpr::Value(value) => literal_expr(value)?,
|
||||||
_ => {
|
_ => {
|
||||||
@ -152,7 +159,7 @@ fn apply_window_spec(expr: Expr, window_type: Option<&WindowType>) -> Result<Exp
|
|||||||
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
||||||
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
||||||
// Function name mostly do not have name space, so it mostly take the first args
|
// Function name mostly do not have name space, so it mostly take the first args
|
||||||
let function_name = sql_function.name.0[0].value.to_lowercase();
|
let function_name = sql_function.name.0[0].value.to_ascii_lowercase();
|
||||||
let args = sql_function
|
let args = sql_function
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -127,23 +127,23 @@ fn command(
|
|||||||
if (&0.0..=&1.0).contains(&val) {
|
if (&0.0..=&1.0).contains(&val) {
|
||||||
Ok(*val)
|
Ok(*val)
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::GenericError(
|
Err(ShellError::GenericError {
|
||||||
"Incorrect value for quantile".to_string(),
|
error: "Incorrect value for quantile".into(),
|
||||||
"value should be between 0 and 1".to_string(),
|
msg: "value should be between 0 and 1".into(),
|
||||||
Some(span),
|
span: Some(span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => Err(*error.clone()),
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
_ => Err(ShellError::GenericError(
|
_ => Err(ShellError::GenericError {
|
||||||
"Incorrect value for quantile".to_string(),
|
error: "Incorrect value for quantile".into(),
|
||||||
"value should be a float".to_string(),
|
msg: "value should be a float".into(),
|
||||||
Some(span),
|
span: Some(span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<f64>, ShellError>>()
|
.collect::<Result<Vec<f64>, ShellError>>()
|
||||||
@ -250,14 +250,12 @@ fn command(
|
|||||||
let res = head.chain(tail).collect::<Vec<Series>>();
|
let res = head.chain(tail).collect::<Vec<Series>>();
|
||||||
|
|
||||||
DataFrame::new(res)
|
DataFrame::new(res)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Dataframe Error".into(),
|
||||||
"Dataframe Error".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
}
|
}
|
||||||
|
@ -97,47 +97,41 @@ fn command(
|
|||||||
let index = NuDataFrame::try_from_value(index_value)?.as_series(index_span)?;
|
let index = NuDataFrame::try_from_value(index_value)?.as_series(index_span)?;
|
||||||
|
|
||||||
let casted = match index.dtype() {
|
let casted = match index.dtype() {
|
||||||
DataType::UInt32 | DataType::UInt64 | DataType::Int32 | DataType::Int64 => {
|
DataType::UInt32 | DataType::UInt64 | DataType::Int32 | DataType::Int64 => index
|
||||||
index.cast(&DataType::UInt32).map_err(|e| {
|
.cast(&DataType::UInt32)
|
||||||
ShellError::GenericError(
|
.map_err(|e| ShellError::GenericError {
|
||||||
"Error casting index list".into(),
|
error: "Error casting index list".into(),
|
||||||
e.to_string(),
|
msg: e.to_string(),
|
||||||
Some(index_span),
|
span: Some(index_span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
)
|
}),
|
||||||
})
|
_ => Err(ShellError::GenericError {
|
||||||
}
|
error: "Incorrect type".into(),
|
||||||
_ => Err(ShellError::GenericError(
|
msg: "Series with incorrect type".into(),
|
||||||
"Incorrect type".into(),
|
span: Some(call.head),
|
||||||
"Series with incorrect type".into(),
|
help: Some("Consider using a Series with type int type".into()),
|
||||||
Some(call.head),
|
inner: vec![],
|
||||||
Some("Consider using a Series with type int type".into()),
|
}),
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let indices = casted.u32().map_err(|e| {
|
let indices = casted.u32().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting index list".into(),
|
||||||
"Error casting index list".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(index_span),
|
||||||
Some(index_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
NuDataFrame::try_from_pipeline(input, call.head).and_then(|df| {
|
NuDataFrame::try_from_pipeline(input, call.head).and_then(|df| {
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.take(indices)
|
.take(indices)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error taking values".into(),
|
||||||
"Error taking values".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
.map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None))
|
||||||
})
|
})
|
||||||
|
@ -58,24 +58,22 @@ fn command(
|
|||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let mut file = File::create(&file_name.item).map_err(|e| {
|
let mut file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error with file name".into(),
|
||||||
"Error with file name".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
IpcWriter::new(&mut file).finish(df.as_mut()).map_err(|e| {
|
IpcWriter::new(&mut file)
|
||||||
ShellError::GenericError(
|
.finish(df.as_mut())
|
||||||
"Error saving file".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error saving file".into(),
|
||||||
Some(file_name.span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(file_name.span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
@ -85,27 +85,23 @@ fn command(
|
|||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let file = File::create(&file_name.item).map_err(|e| {
|
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error with file name".into(),
|
||||||
"Error with file name".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
AvroWriter::new(file)
|
AvroWriter::new(file)
|
||||||
.with_compression(compression)
|
.with_compression(compression)
|
||||||
.finish(df.as_mut())
|
.finish(df.as_mut())
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error saving file".into(),
|
||||||
"Error saving file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
@ -45,7 +45,7 @@ impl Command for ToCSV {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Saves dataframe to CSV file using other delimiter",
|
description: "Saves dataframe to CSV file using other delimiter",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv -d '|'",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv --delimiter '|'",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -74,54 +74,52 @@ fn command(
|
|||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let mut file = File::create(&file_name.item).map_err(|e| {
|
let mut file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error with file name".into(),
|
||||||
"Error with file name".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let writer = CsvWriter::new(&mut file);
|
let writer = CsvWriter::new(&mut file);
|
||||||
|
|
||||||
let writer = if no_header {
|
let writer = if no_header {
|
||||||
writer.has_header(false)
|
writer.include_header(false)
|
||||||
} else {
|
} else {
|
||||||
writer.has_header(true)
|
writer.include_header(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut writer = match delimiter {
|
let mut writer = match delimiter {
|
||||||
None => writer,
|
None => writer,
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
if d.item.len() != 1 {
|
if d.item.len() != 1 {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Incorrect delimiter".into(),
|
error: "Incorrect delimiter".into(),
|
||||||
"Delimiter has to be one char".into(),
|
msg: "Delimiter has to be one char".into(),
|
||||||
Some(d.span),
|
span: Some(d.span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
} else {
|
} else {
|
||||||
let delimiter = match d.item.chars().next() {
|
let delimiter = match d.item.chars().next() {
|
||||||
Some(d) => d as u8,
|
Some(d) => d as u8,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
writer.with_delimiter(delimiter)
|
writer.with_separator(delimiter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
writer.finish(df.as_mut()).map_err(|e| {
|
writer
|
||||||
ShellError::GenericError(
|
.finish(df.as_mut())
|
||||||
"Error writing to file".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error writing to file".into(),
|
||||||
Some(file_name.span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(file_name.span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
@ -58,27 +58,23 @@ fn command(
|
|||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let file = File::create(&file_name.item).map_err(|e| {
|
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error with file name".into(),
|
||||||
"Error with file name".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let buf_writer = BufWriter::new(file);
|
let buf_writer = BufWriter::new(file);
|
||||||
|
|
||||||
JsonWriter::new(buf_writer)
|
JsonWriter::new(buf_writer)
|
||||||
.finish(df.as_mut())
|
.finish(df.as_mut())
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error saving file".into(),
|
||||||
"Error saving file".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
record, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dataframe::values::NuExpression;
|
use crate::dataframe::values::NuExpression;
|
||||||
@ -39,18 +39,20 @@ impl Command for ToNu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
let cols = vec!["index".into(), "a".into(), "b".into()];
|
let rec_1 = Value::test_record(record! {
|
||||||
let rec_1 = Value::test_record(Record {
|
"index" => Value::test_int(0),
|
||||||
cols: cols.clone(),
|
"a" => Value::test_int(1),
|
||||||
vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
|
"b" => Value::test_int(2),
|
||||||
});
|
});
|
||||||
let rec_2 = Value::test_record(Record {
|
let rec_2 = Value::test_record(record! {
|
||||||
cols: cols.clone(),
|
"index" => Value::test_int(1),
|
||||||
vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)],
|
"a" => Value::test_int(3),
|
||||||
|
"b" => Value::test_int(4),
|
||||||
});
|
});
|
||||||
let rec_3 = Value::test_record(Record {
|
let rec_3 = Value::test_record(record! {
|
||||||
cols,
|
"index" => Value::test_int(2),
|
||||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
|
"a" => Value::test_int(3),
|
||||||
|
"b" => Value::test_int(4),
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
@ -61,15 +63,15 @@ impl Command for ToNu {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Shows tail rows from dataframe",
|
description: "Shows tail rows from dataframe",
|
||||||
example: "[[a b]; [1 2] [5 6] [3 4]] | dfr into-df | dfr into-nu -t -n 1",
|
example: "[[a b]; [1 2] [5 6] [3 4]] | dfr into-df | dfr into-nu --tail --rows 1",
|
||||||
result: Some(Value::list(vec![rec_3], Span::test_data())),
|
result: Some(Value::list(vec![rec_3], Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert a col expression into a nushell value",
|
description: "Convert a col expression into a nushell value",
|
||||||
example: "dfr col a | dfr into-nu",
|
example: "dfr col a | dfr into-nu",
|
||||||
result: Some(Value::test_record(Record {
|
result: Some(Value::test_record(record! {
|
||||||
cols: vec!["expr".into(), "value".into()],
|
"expr" => Value::test_string("column"),
|
||||||
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
"value" => Value::test_string("a"),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -58,24 +58,22 @@ fn command(
|
|||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let file = File::create(&file_name.item).map_err(|e| {
|
let file = File::create(&file_name.item).map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error with file name".into(),
|
||||||
"Error with file name".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(file_name.span),
|
||||||
Some(file_name.span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
ParquetWriter::new(file).finish(df.as_mut()).map_err(|e| {
|
ParquetWriter::new(file)
|
||||||
ShellError::GenericError(
|
.finish(df.as_mut())
|
||||||
"Error saving file".into(),
|
.map_err(|e| ShellError::GenericError {
|
||||||
e.to_string(),
|
error: "Error saving file".into(),
|
||||||
Some(file_name.span),
|
msg: e.to_string(),
|
||||||
None,
|
span: Some(file_name.span),
|
||||||
Vec::new(),
|
help: None,
|
||||||
)
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
@ -151,14 +151,12 @@ fn command_eager(
|
|||||||
|
|
||||||
df.as_mut()
|
df.as_mut()
|
||||||
.with_column(series)
|
.with_column(series)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error adding column to dataframe".into(),
|
||||||
"Error adding column to dataframe".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(column_span),
|
||||||
Some(column_span),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|df| {
|
.map(|df| {
|
||||||
PipelineData::Value(
|
PipelineData::Value(
|
||||||
|
@ -4,7 +4,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
record, Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -38,20 +38,12 @@ impl Command for ExprAlias {
|
|||||||
description: "Creates and alias expression",
|
description: "Creates and alias expression",
|
||||||
example: "dfr col a | dfr as new_a | dfr into-nu",
|
example: "dfr col a | dfr as new_a | dfr into-nu",
|
||||||
result: {
|
result: {
|
||||||
let cols = vec!["expr".into(), "value".into()];
|
let record = Value::test_record(record! {
|
||||||
let expr = Value::test_string("column");
|
"expr" => Value::test_record(record! {
|
||||||
let value = Value::test_string("a");
|
"expr" => Value::test_string("column"),
|
||||||
let expr = Value::test_record(Record {
|
"value" => Value::test_string("a"),
|
||||||
cols,
|
}),
|
||||||
vals: vec![expr, value],
|
"alias" => Value::test_string("new_a"),
|
||||||
});
|
|
||||||
|
|
||||||
let cols = vec!["expr".into(), "alias".into()];
|
|
||||||
let value = Value::test_string("new_a");
|
|
||||||
|
|
||||||
let record = Value::test_record(Record {
|
|
||||||
cols,
|
|
||||||
vals: vec![expr, value],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(record)
|
Some(record)
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
record, Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use polars::prelude::col;
|
use polars::prelude::col;
|
||||||
|
|
||||||
@ -34,9 +34,9 @@ impl Command for ExprCol {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Creates a named column expression and converts it to a nu object",
|
description: "Creates a named column expression and converts it to a nu object",
|
||||||
example: "dfr col a | dfr into-nu",
|
example: "dfr col a | dfr into-nu",
|
||||||
result: Some(Value::test_record(Record {
|
result: Some(Value::test_record(record! {
|
||||||
cols: vec!["expr".into(), "value".into()],
|
"expr" => Value::test_string("column"),
|
||||||
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
"value" => Value::test_string("a"),
|
||||||
})),
|
})),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -124,12 +124,12 @@ impl Command for ExprDatePart {
|
|||||||
"microsecond" => expr_dt.microsecond(),
|
"microsecond" => expr_dt.microsecond(),
|
||||||
"nanosecond" => expr_dt.nanosecond(),
|
"nanosecond" => expr_dt.nanosecond(),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput {
|
||||||
format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
|
msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
|
||||||
"value originates from here".to_string(),
|
input: "value originates from here".to_string(),
|
||||||
call.head,
|
msg_span: call.head,
|
||||||
part.span,
|
input_span: part.span,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}.into();
|
}.into();
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ macro_rules! lazy_expr_command {
|
|||||||
expr_command!(
|
expr_command!(
|
||||||
ExprList,
|
ExprList,
|
||||||
"dfr implode",
|
"dfr implode",
|
||||||
"Aggregates a group to a Series",
|
"Aggregates a group to a Series.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "",
|
description: "",
|
||||||
example: "",
|
example: "",
|
||||||
@ -334,7 +334,7 @@ expr_command!(
|
|||||||
expr_command!(
|
expr_command!(
|
||||||
ExprAggGroups,
|
ExprAggGroups,
|
||||||
"dfr agg-groups",
|
"dfr agg-groups",
|
||||||
"creates an agg_groups expression",
|
"Creates an agg_groups expression.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "",
|
description: "",
|
||||||
example: "",
|
example: "",
|
||||||
@ -349,7 +349,7 @@ expr_command!(
|
|||||||
expr_command!(
|
expr_command!(
|
||||||
ExprCount,
|
ExprCount,
|
||||||
"dfr count",
|
"dfr count",
|
||||||
"creates a count expression",
|
"Creates a count expression.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "",
|
description: "",
|
||||||
example: "",
|
example: "",
|
||||||
@ -364,7 +364,7 @@ expr_command!(
|
|||||||
expr_command!(
|
expr_command!(
|
||||||
ExprNot,
|
ExprNot,
|
||||||
"dfr expr-not",
|
"dfr expr-not",
|
||||||
"creates a not expression",
|
"Creates a not expression.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Creates a not expression",
|
description: "Creates a not expression",
|
||||||
example: "(dfr col a) > 2) | dfr expr-not",
|
example: "(dfr col a) > 2) | dfr expr-not",
|
||||||
@ -379,7 +379,7 @@ expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMax,
|
ExprMax,
|
||||||
"dfr max",
|
"dfr max",
|
||||||
"Creates a max expression or aggregates columns to their max value",
|
"Creates a max expression or aggregates columns to their max value.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Max value from columns in a dataframe",
|
description: "Max value from columns in a dataframe",
|
||||||
@ -424,7 +424,7 @@ lazy_expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMin,
|
ExprMin,
|
||||||
"dfr min",
|
"dfr min",
|
||||||
"Creates a min expression or aggregates columns to their min value",
|
"Creates a min expression or aggregates columns to their min value.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Min value from columns in a dataframe",
|
description: "Min value from columns in a dataframe",
|
||||||
@ -469,7 +469,7 @@ lazy_expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprSum,
|
ExprSum,
|
||||||
"dfr sum",
|
"dfr sum",
|
||||||
"Creates a sum expression for an aggregation or aggregates columns to their sum value",
|
"Creates a sum expression for an aggregation or aggregates columns to their sum value.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Sums all columns in a dataframe",
|
description: "Sums all columns in a dataframe",
|
||||||
@ -514,7 +514,7 @@ lazy_expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMean,
|
ExprMean,
|
||||||
"dfr mean",
|
"dfr mean",
|
||||||
"Creates a mean expression for an aggregation or aggregates columns to their mean value",
|
"Creates a mean expression for an aggregation or aggregates columns to their mean value.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Mean value from columns in a dataframe",
|
description: "Mean value from columns in a dataframe",
|
||||||
@ -559,7 +559,7 @@ lazy_expr_command!(
|
|||||||
expr_command!(
|
expr_command!(
|
||||||
ExprMedian,
|
ExprMedian,
|
||||||
"dfr median",
|
"dfr median",
|
||||||
"Creates a median expression for an aggregation",
|
"Creates a median expression for an aggregation.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Median aggregation for a group-by",
|
description: "Median aggregation for a group-by",
|
||||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||||
@ -590,7 +590,7 @@ expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprStd,
|
ExprStd,
|
||||||
"dfr std",
|
"dfr std",
|
||||||
"Creates a std expression for an aggregation of std value from columns in a dataframe",
|
"Creates a std expression for an aggregation of std value from columns in a dataframe.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Std value from columns in a dataframe",
|
description: "Std value from columns in a dataframe",
|
||||||
@ -636,7 +636,7 @@ lazy_expr_command!(
|
|||||||
lazy_expr_command!(
|
lazy_expr_command!(
|
||||||
ExprVar,
|
ExprVar,
|
||||||
"dfr var",
|
"dfr var",
|
||||||
"Create a var expression for an aggregation",
|
"Create a var expression for an aggregation.",
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
|
@ -3,7 +3,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
record, Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -33,9 +33,9 @@ impl Command for ExprLit {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Created a literal expression and converts it to a nu object",
|
description: "Created a literal expression and converts it to a nu object",
|
||||||
example: "dfr lit 2 | dfr into-nu",
|
example: "dfr lit 2 | dfr into-nu",
|
||||||
result: Some(Value::test_record(Record {
|
result: Some(Value::test_record(record! {
|
||||||
cols: vec!["expr".into(), "value".into()],
|
"expr" => Value::test_string("literal"),
|
||||||
vals: vec![Value::test_string("literal"), Value::test_string("2")],
|
"value" => Value::test_string("2"),
|
||||||
})),
|
})),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ impl Command for ExprOtherwise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"completes a when expression."
|
"Completes a when expression."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -125,13 +125,13 @@ impl Command for LazyAggregate {
|
|||||||
let dtype = schema.get(name.as_str());
|
let dtype = schema.get(name.as_str());
|
||||||
|
|
||||||
if matches!(dtype, Some(DataType::Object(..))) {
|
if matches!(dtype, Some(DataType::Object(..))) {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Object type column not supported for aggregation".into(),
|
error: "Object type column not supported for aggregation".into(),
|
||||||
format!("Column '{name}' is type Object"),
|
msg: format!("Column '{name}' is type Object"),
|
||||||
Some(call.head),
|
span: Some(call.head),
|
||||||
Some("Aggregations cannot be performed on Object type columns. Use dtype command to check column types".into()),
|
help: Some("Aggregations cannot be performed on Object type columns. Use dtype command to check column types".into()),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||||||
| Expr::Slice { input: expr, .. }
|
| Expr::Slice { input: expr, .. }
|
||||||
| Expr::Cast { expr, .. }
|
| Expr::Cast { expr, .. }
|
||||||
| Expr::Sort { expr, .. }
|
| Expr::Sort { expr, .. }
|
||||||
| Expr::Take { expr, .. }
|
| Expr::Gather { expr, .. }
|
||||||
| Expr::SortBy { expr, .. }
|
| Expr::SortBy { expr, .. }
|
||||||
| Expr::Exclude(expr, _)
|
| Expr::Exclude(expr, _)
|
||||||
| Expr::Alias(expr, _)
|
| Expr::Alias(expr, _)
|
||||||
@ -189,6 +189,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||||||
| Expr::RenameAlias { .. }
|
| Expr::RenameAlias { .. }
|
||||||
| Expr::Count
|
| Expr::Count
|
||||||
| Expr::Nth(_)
|
| Expr::Nth(_)
|
||||||
|
| Expr::SubPlan(_, _)
|
||||||
| Expr::Selector(_) => None,
|
| Expr::Selector(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ impl Command for LazyFetch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"collects the lazyframe to the selected rows."
|
"Collects the lazyframe to the selected rows."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -67,14 +67,12 @@ impl Command for LazyFetch {
|
|||||||
let eager: NuDataFrame = lazy
|
let eager: NuDataFrame = lazy
|
||||||
.into_polars()
|
.into_polars()
|
||||||
.fetch(rows as usize)
|
.fetch(rows as usize)
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error fetching rows".into(),
|
||||||
"Error fetching rows".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ impl Command for LazyFlatten {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"An alias for dfr explode"
|
"An alias for dfr explode."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -131,7 +131,7 @@ impl Command for ToLazyGroupBy {
|
|||||||
let group_by = NuLazyGroupBy {
|
let group_by = NuLazyGroupBy {
|
||||||
schema: lazy.schema.clone(),
|
schema: lazy.schema.clone(),
|
||||||
from_eager: lazy.from_eager,
|
from_eager: lazy.from_eager,
|
||||||
group_by: Some(lazy.into_polars().groupby(&expressions)),
|
group_by: Some(lazy.into_polars().group_by(&expressions)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(PipelineData::Value(group_by.into_value(call.head), None))
|
Ok(PipelineData::Value(group_by.into_value(call.head), None))
|
||||||
|
@ -121,7 +121,7 @@ lazy_command!(
|
|||||||
"dfr reverse",
|
"dfr reverse",
|
||||||
"Reverses the LazyFrame",
|
"Reverses the LazyFrame",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Reverses the dataframe",
|
description: "Reverses the dataframe.",
|
||||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse",
|
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(vec![
|
||||||
@ -147,7 +147,7 @@ lazy_command!(
|
|||||||
lazy_command!(
|
lazy_command!(
|
||||||
LazyCache,
|
LazyCache,
|
||||||
"dfr cache",
|
"dfr cache",
|
||||||
"Caches operations in a new LazyFrame",
|
"Caches operations in a new LazyFrame.",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Caches the result into a new LazyFrame",
|
description: "Caches the result into a new LazyFrame",
|
||||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse | dfr cache",
|
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse | dfr cache",
|
||||||
|
@ -16,7 +16,7 @@ impl Command for LazySortBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"sorts a lazy dataframe based on expression(s)."
|
"Sorts a lazy dataframe based on expression(s)."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -118,13 +118,13 @@ impl Command for LazySortBy {
|
|||||||
.get_flag::<Value>(engine_state, stack, "reverse")?
|
.get_flag::<Value>(engine_state, stack, "reverse")?
|
||||||
.expect("already checked and it exists")
|
.expect("already checked and it exists")
|
||||||
.span();
|
.span();
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Incorrect list size".into(),
|
error: "Incorrect list size".into(),
|
||||||
"Size doesn't match expression list".into(),
|
msg: "Size doesn't match expression list".into(),
|
||||||
Some(span),
|
span: Some(span),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
} else {
|
} else {
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ mod eager;
|
|||||||
mod expressions;
|
mod expressions;
|
||||||
mod lazy;
|
mod lazy;
|
||||||
mod series;
|
mod series;
|
||||||
|
mod stub;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod values;
|
mod values;
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ use nu_protocol::engine::{EngineState, StateWorkingSet};
|
|||||||
pub fn add_dataframe_context(mut engine_state: EngineState) -> EngineState {
|
pub fn add_dataframe_context(mut engine_state: EngineState) -> EngineState {
|
||||||
let delta = {
|
let delta = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
working_set.add_decl(Box::new(stub::Dfr));
|
||||||
add_series_decls(&mut working_set);
|
add_series_decls(&mut working_set);
|
||||||
add_eager_decls(&mut working_set);
|
add_eager_decls(&mut working_set);
|
||||||
add_expressions(&mut working_set);
|
add_expressions(&mut working_set);
|
||||||
|
@ -78,14 +78,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
let bool = series.bool().map_err(|_| {
|
let bool = series.bool().map_err(|_| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error converting to bool".into(),
|
||||||
"Error converting to bool".into(),
|
msg: "all-false only works with series of type bool".into(),
|
||||||
"all-false only works with series of type bool".into(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = Value::bool(!bool.any(), call.head);
|
let value = Value::bool(!bool.any(), call.head);
|
||||||
|
@ -78,14 +78,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
let bool = series.bool().map_err(|_| {
|
let bool = series.bool().map_err(|_| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error converting to bool".into(),
|
||||||
"Error converting to bool".into(),
|
msg: "all-false only works with series of type bool".into(),
|
||||||
"all-false only works with series of type bool".into(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = Value::bool(bool.all(), call.head);
|
let value = Value::bool(bool.all(), call.head);
|
||||||
|
@ -8,6 +8,7 @@ use nu_protocol::{
|
|||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use polars::prelude::{DataType, IntoSeries};
|
use polars::prelude::{DataType, IntoSeries};
|
||||||
|
use polars_ops::prelude::{cum_max, cum_min, cum_sum};
|
||||||
|
|
||||||
enum CumType {
|
enum CumType {
|
||||||
Min,
|
Min,
|
||||||
@ -21,13 +22,13 @@ impl CumType {
|
|||||||
"min" => Ok(Self::Min),
|
"min" => Ok(Self::Min),
|
||||||
"max" => Ok(Self::Max),
|
"max" => Ok(Self::Max),
|
||||||
"sum" => Ok(Self::Sum),
|
"sum" => Ok(Self::Sum),
|
||||||
_ => Err(ShellError::GenericError(
|
_ => Err(ShellError::GenericError {
|
||||||
"Wrong operation".into(),
|
error: "Wrong operation".into(),
|
||||||
"Operation not valid for cumulative".into(),
|
msg: "Operation not valid for cumulative".into(),
|
||||||
Some(span),
|
span: Some(span),
|
||||||
Some("Allowed values: max, min, sum".into()),
|
help: Some("Allowed values: max, min, sum".into()),
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
)),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,21 +109,28 @@ fn command(
|
|||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
if let DataType::Object(_) = series.dtype() {
|
if let DataType::Object(_) = series.dtype() {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError {
|
||||||
"Found object series".into(),
|
error: "Found object series".into(),
|
||||||
"Series of type object cannot be used for cumulative operation".into(),
|
msg: "Series of type object cannot be used for cumulative operation".into(),
|
||||||
Some(call.head),
|
span: Some(call.head),
|
||||||
None,
|
help: None,
|
||||||
Vec::new(),
|
inner: vec![],
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let cum_type = CumType::from_str(&cum_type.item, cum_type.span)?;
|
let cum_type = CumType::from_str(&cum_type.item, cum_type.span)?;
|
||||||
let mut res = match cum_type {
|
let mut res = match cum_type {
|
||||||
CumType::Max => series.cummax(reverse),
|
CumType::Max => cum_max(&series, reverse),
|
||||||
CumType::Min => series.cummin(reverse),
|
CumType::Min => cum_min(&series, reverse),
|
||||||
CumType::Sum => series.cumsum(reverse),
|
CumType::Sum => cum_sum(&series, reverse),
|
||||||
};
|
}
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: "Error creating cumulative".into(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
let name = format!("{}_{}", series.name(), cum_type.to_str());
|
let name = format!("{}_{}", series.name(), cum_type.to_str());
|
||||||
res.rename(&name);
|
res.rename(&name);
|
||||||
|
@ -68,14 +68,12 @@ fn command(
|
|||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
let casted = series.utf8().map_err(|e| {
|
let casted = series.utf8().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to string".into(),
|
||||||
"Error casting to string".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = if not_exact {
|
let res = if not_exact {
|
||||||
@ -85,14 +83,12 @@ fn command(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut res = res
|
let mut res = res
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error creating datetime".into(),
|
||||||
"Error creating datetime".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into_series();
|
.into_series();
|
||||||
|
|
||||||
|
@ -132,14 +132,12 @@ fn command(
|
|||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
let casted = series.utf8().map_err(|e| {
|
let casted = series.utf8().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to string".into(),
|
||||||
"Error casting to string".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = if not_exact {
|
let res = if not_exact {
|
||||||
@ -148,7 +146,7 @@ fn command(
|
|||||||
TimeUnit::Nanoseconds,
|
TimeUnit::Nanoseconds,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
None,
|
&Default::default(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
casted.as_datetime(
|
casted.as_datetime(
|
||||||
@ -157,19 +155,17 @@ fn command(
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
None,
|
&Default::default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = res
|
let mut res = res
|
||||||
.map_err(|e| {
|
.map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error creating datetime".into(),
|
||||||
"Error creating datetime".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.into_series();
|
.into_series();
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetDay {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns day from a date",
|
description: "Returns day from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-day"#,
|
$df | dfr get-day"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.day().into_series();
|
let res = casted.day().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetHour {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns hour from a date",
|
description: "Returns hour from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-hour"#,
|
$df | dfr get-hour"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.hour().into_series();
|
let res = casted.hour().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetMinute {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns minute from a date",
|
description: "Returns minute from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-minute"#,
|
$df | dfr get-minute"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.minute().into_series();
|
let res = casted.minute().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetMonth {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns month from a date",
|
description: "Returns month from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-month"#,
|
$df | dfr get-month"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.month().into_series();
|
let res = casted.month().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetNanosecond {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns nanosecond from a date",
|
description: "Returns nanosecond from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-nanosecond"#,
|
$df | dfr get-nanosecond"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.nanosecond().into_series();
|
let res = casted.nanosecond().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetOrdinal {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns ordinal from a date",
|
description: "Returns ordinal from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-ordinal"#,
|
$df | dfr get-ordinal"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.ordinal().into_series();
|
let res = casted.ordinal().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetSecond {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns second from a date",
|
description: "Returns second from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-second"#,
|
$df | dfr get-second"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.second().into_series();
|
let res = casted.second().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetWeek {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns week from a date",
|
description: "Returns week from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-week"#,
|
$df | dfr get-week"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.week().into_series();
|
let res = casted.week().into_series();
|
||||||
|
@ -31,7 +31,7 @@ impl Command for GetWeekDay {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Returns weekday from a date",
|
description: "Returns weekday from a date",
|
||||||
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC');
|
example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
|
||||||
let df = ([$dt $dt] | dfr into-df);
|
let df = ([$dt $dt] | dfr into-df);
|
||||||
$df | dfr get-weekday"#,
|
$df | dfr get-weekday"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -65,14 +65,12 @@ fn command(
|
|||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
let series = df.as_series(call.head)?;
|
let series = df.as_series(call.head)?;
|
||||||
|
|
||||||
let casted = series.datetime().map_err(|e| {
|
let casted = series.datetime().map_err(|e| ShellError::GenericError {
|
||||||
ShellError::GenericError(
|
error: "Error casting to datetime type".into(),
|
||||||
"Error casting to datetime type".into(),
|
msg: e.to_string(),
|
||||||
e.to_string(),
|
span: Some(call.head),
|
||||||
Some(call.head),
|
help: None,
|
||||||
None,
|
inner: vec![],
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = casted.weekday().into_series();
|
let res = casted.weekday().into_series();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user