mirror of
https://github.com/nushell/nushell.git
synced 2025-07-08 10:27:47 +02:00
Compare commits
411 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
a6f62e05ae | |||
f8939de14f | |||
01ade02ac1 | |||
39d93b536a | |||
32f67557af | |||
f05eed8e8d | |||
f0a265dbee | |||
bc7736bc99 | |||
19d732f313 | |||
a9a82de5c4 | |||
9074015d1c | |||
2b77544e58 | |||
5ee74b6ab5 | |||
3a04bd9154 | |||
2c176a7f14 | |||
026e18399e | |||
bbf0b45c59 | |||
5bd7300cd5 | |||
ffb5051f6c | |||
ce4ea16c08 | |||
48c94c75fc | |||
73d3708006 | |||
bbea7da669 | |||
7f39609d9a | |||
a14e9e0a2e | |||
3e14dc3eb8 | |||
ba6d8ad261 | |||
2a08865851 | |||
0a3bfe7f73 | |||
451a9c64d3 | |||
88d79c84cd | |||
abcb0877e2 | |||
9e1e2a4320 | |||
d53b0a99d0 | |||
1fb4f9e455 | |||
6e9b6f22c9 | |||
e90b099622 | |||
84c10de864 | |||
d618b60d9e | |||
c761f7f844 | |||
7b89fab327 | |||
eddff46155 | |||
baa50ec9b2 | |||
513186c390 | |||
0c37463bfa | |||
94fc33bbee | |||
ce378a68a6 | |||
fa40740e77 | |||
762fdb98ac | |||
5f795b1aec | |||
6811700b90 | |||
248aca7a44 | |||
17abbdf6e0 | |||
40eca52ed5 | |||
7907dda8f7 | |||
8501024546 | |||
21d30d1e4d | |||
eeaa65c8af | |||
6754b8534e | |||
2ffff959fc | |||
fed4233db4 | |||
2f47263380 | |||
4d5386635e | |||
872eb2c3df | |||
e62a77a885 | |||
9bca63ebef | |||
ae54dc862c | |||
5e951b2be9 | |||
f78d57a703 | |||
f021be623e | |||
b6189879e3 | |||
c7c6445b03 | |||
535aec0648 | |||
664dd291eb | |||
a9216deaa4 | |||
99caad7d60 | |||
7486850357 | |||
bb06661d24 | |||
6a374182a7 | |||
f433b3102f | |||
456e2a8ee3 | |||
1ee3bf784c | |||
54394fe9af | |||
7a728340de | |||
5f1e8a6af8 | |||
e566a073dc | |||
9a4dad6ca1 | |||
eca9f461da | |||
08aaa9494c | |||
352f913c39 | |||
aeeb5dd405 | |||
278bf7ffa9 | |||
b15c824932 | |||
5ad3bfa31b | |||
e5145358eb | |||
3a20fbfe94 | |||
dac32557cd | |||
fedd879b2e | |||
a46c21cffb | |||
6cdfee3573 | |||
af79eb2943 | |||
e9d4730099 | |||
844f541719 | |||
d28f728787 | |||
f35808cb89 | |||
7d6b23ee2f | |||
e68ae4c8d1 | |||
faad6ca355 | |||
c77c1bd297 | |||
5b4b4446b7 | |||
93f20b406e | |||
02318cf3a7 | |||
35fc387505 | |||
fd4ba0443d | |||
b943cbedff | |||
3fd1a26ec0 | |||
3f2c76df28 | |||
7d3312e96e | |||
c59d9dc306 | |||
1f06f8405c | |||
38f454d5ab | |||
487f1a97ea | |||
0f05475e2e | |||
cc805f3f01 | |||
5ac5b90aed | |||
3d73287ea4 | |||
7ebdced256 | |||
ad12018199 | |||
27dcc3ecc3 | |||
e25a795cf6 | |||
16c15e83a3 | |||
cea67cb30b | |||
1e3e034021 | |||
8da27a1a09 | |||
030e749fe7 | |||
a785e64bc9 | |||
d4eeef4bd1 | |||
c8a07d477f | |||
af82eeca72 | |||
3d698b74d8 | |||
d2abb8603a | |||
894e0f7658 | |||
a5087c4966 | |||
ac4ab452d4 | |||
5378727049 | |||
0786ddddbd | |||
7617084ca3 | |||
28941f1a06 | |||
43ceb3edec | |||
bffd8e4dd2 | |||
66023f6243 | |||
10fc32e3ef | |||
3148acd3a4 | |||
e6ce8a89be | |||
74f8081290 | |||
2ae1de2470 | |||
028a327ce8 | |||
318862aad6 | |||
9f4510f2e1 | |||
98c7ab96b6 | |||
13114e972b | |||
4a1b3e26ef | |||
1e3248dfe8 | |||
2aa4cd5cc5 | |||
fb908df17d | |||
cdf09abcc0 | |||
fe2c498a81 | |||
fe7122280d | |||
2e0fb7c1a6 | |||
f33b60c001 | |||
7e48607820 | |||
68a821c84a | |||
c5e59efa4d | |||
ec5b9b9f37 | |||
e88a51e930 | |||
35f8d8548a | |||
7a123d3eb1 | |||
3ed45c7ba8 | |||
8b160f9850 | |||
696b2cda4a | |||
435348aa61 | |||
0a5f41abc2 | |||
2b97bc54c5 | |||
ad49c17eba | |||
f1e88d95c1 | |||
6eac9bfd0f | |||
5d94b16d71 | |||
3bd46fe27a | |||
7b1c7debcb | |||
e77a0a48aa | |||
aa37572ddc | |||
a0cecf7658 | |||
23170ff368 | |||
f9ffd9ae29 | |||
d5fa7b8a55 | |||
f94df58486 | |||
6f5bd62a97 | |||
bb3cc9e153 | |||
202dfdaee2 | |||
0674d4960b | |||
85c2035016 | |||
02be83efbf | |||
b964347895 | |||
56ed1eb807 | |||
570175f95d | |||
839010b89d | |||
7694d09d14 | |||
4accc67843 | |||
c070e2d6f7 | |||
d302d63030 | |||
a2e117f8b0 | |||
b1974fae39 | |||
7248de1167 | |||
a9582e1c62 | |||
bb6335830a | |||
c8f3799c20 | |||
bd3a61a2f7 | |||
077643cadf | |||
fa2d9a8a58 | |||
58f98a4260 | |||
066790552a | |||
dcb1a1996c | |||
6b4d06d8a7 | |||
f615038938 | |||
71b74a284a | |||
2b431f994f | |||
7e096e61d7 | |||
9d7a1097f2 | |||
a98b3124c5 | |||
572698bf3e | |||
7162289d77 | |||
14bf25da14 | |||
a455e2e5eb | |||
840b4b854b | |||
ec4941c8ac | |||
dd86f14a5a | |||
63103580d2 | |||
d25df9c00b | |||
778a00efa1 | |||
fea822792f | |||
f6033ac5af | |||
583ef8674e | |||
94bec72079 | |||
28ed21864d | |||
e16ce1df36 | |||
87abfee268 | |||
ba0f069c31 | |||
154856066f | |||
f91713b714 | |||
f1bf485b2a | |||
955de76116 | |||
bf5bd3ff10 | |||
6ac3351fd1 | |||
b6dafa6e67 | |||
152a541696 | |||
ff8c3aa356 | |||
99ed8e42a3 | |||
6a7a23e3fa | |||
9448225690 | |||
28b99bfaf7 | |||
8403fff345 | |||
aa08e81370 | |||
831d1da256 | |||
3481c7e242 | |||
78e29af423 | |||
3ef5e90b64 | |||
6aa30132aa | |||
5d2ef0faf1 | |||
b2e191f836 | |||
d9230a76f3 | |||
f8d325dbfe | |||
88a890c11f | |||
5a28371b18 |
@ -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"]
|
1
.typos.toml → .github/.typos.toml
vendored
1
.typos.toml → .github/.typos.toml
vendored
@ -11,3 +11,4 @@ Plasticos = "Plasticos"
|
|||||||
IIF = "IIF"
|
IIF = "IIF"
|
||||||
numer = "numer"
|
numer = "numer"
|
||||||
ratatui = "ratatui"
|
ratatui = "ratatui"
|
||||||
|
doas = "doas"
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -24,8 +24,8 @@ Don't forget to add tests that cover your changes.
|
|||||||
Make sure you've run and fixed any issues with these commands:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style
|
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
|
||||||
- `cargo test --workspace` to check that all tests pass
|
- `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
|
||||||
- `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library
|
- `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
|
25
.github/workflows/audit.yml
vendored
Normal file
25
.github/workflows/audit.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Security audit
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
CLICOLOR: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security_audit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Prevent sudden announcement of a new advisory from failing ci:
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: rustsec/audit-check@v1.4.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@ -7,9 +7,9 @@ on:
|
|||||||
name: continuous-integration
|
name: continuous-integration
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NUSHELL_CARGO_TARGET: ci
|
NUSHELL_CARGO_PROFILE: ci
|
||||||
NU_LOG_LEVEL: DEBUG
|
NU_LOG_LEVEL: DEBUG
|
||||||
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err"
|
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt-clippy:
|
fmt-clippy:
|
||||||
@ -37,7 +37,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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.5.0
|
||||||
@ -50,6 +50,10 @@ jobs:
|
|||||||
- 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
|
||||||
|
|
||||||
|
# In tests we don't have to deny unwrap
|
||||||
|
- name: Clippy of tests
|
||||||
|
run: cargo clippy --tests --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@ -76,7 +80,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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.5.0
|
||||||
@ -97,7 +101,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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.5.0
|
||||||
@ -115,21 +119,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
- run: python -m pip install tox
|
|
||||||
|
|
||||||
- name: Install virtualenv
|
- name: Install virtualenv
|
||||||
run: git clone https://github.com/pypa/virtualenv.git
|
run: pip install virtualenv
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test Nushell in virtualenv
|
- name: Test Nushell in virtualenv
|
||||||
run: |
|
run: nu scripts/test_virtualenv.nu
|
||||||
cd virtualenv
|
|
||||||
# if we encounter problems with bleeding edge tests pin to the latest tag
|
|
||||||
# git checkout $(git describe --tags | cut -d - -f 1)
|
|
||||||
# We need to disable failing on coverage levels.
|
|
||||||
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
|
|
||||||
mv patchproject.toml pyproject.toml
|
|
||||||
tox -e ${{ matrix.py }} -- -k nushell
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
@ -141,7 +136,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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.5.0
|
||||||
|
150
.github/workflows/nightly-build.yml
vendored
150
.github/workflows/nightly-build.yml
vendored
@ -13,7 +13,7 @@ on:
|
|||||||
- nightly # Just for test purpose only with the nightly repo
|
- nightly # Just for test purpose only with the nightly repo
|
||||||
# This schedule will run only from the default branch
|
# This schedule will run only from the default branch
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '15 1 * * *' # run at 01:15 AM UTC
|
- cron: '15 0 * * *' # run at 00:15 AM UTC
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@ -27,7 +27,7 @@ jobs:
|
|||||||
# if: github.repository == 'nushell/nightly'
|
# if: github.repository == 'nushell/nightly'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
@ -36,10 +36,10 @@ jobs:
|
|||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3.6
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.81.0
|
version: 0.85.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -53,22 +53,21 @@ jobs:
|
|||||||
# We can't push if no user name and email are configured
|
# We can't push if no user name and email are configured
|
||||||
git config user.name 'hustcer'
|
git config user.name 'hustcer'
|
||||||
git config user.email 'hustcer@outlook.com'
|
git config user.email 'hustcer@outlook.com'
|
||||||
git fetch origin main
|
git pull origin main
|
||||||
git remote add src https://github.com/nushell/nushell.git
|
git remote add src https://github.com/nushell/nushell.git
|
||||||
git fetch src main
|
git fetch src main
|
||||||
# git pull --rebase src main
|
|
||||||
# All the changes will be overwritten by the upstream main branch
|
# All the changes will be overwritten by the upstream main branch
|
||||||
git reset --hard src/main
|
git reset --hard src/main
|
||||||
git push origin main -f
|
git push origin main -f
|
||||||
let sha_short = (git rev-parse --short src/main | str trim | str substring 0..7)
|
let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..7)
|
||||||
let tag_name = $'nightly-($sha_short)'
|
let tag_name = $'nightly-($sha_short)'
|
||||||
if (git ls-remote --tags origin $tag_name | is-empty) {
|
if (git ls-remote --tags origin $tag_name | is-empty) {
|
||||||
git tag -a $tag_name -m $'Nightly build from ($sha_short)'
|
git tag -a $tag_name -m $'Nightly build from ($sha_short)'
|
||||||
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
|
||||||
@ -118,17 +117,18 @@ 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-20.04
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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: |
|
||||||
@ -136,13 +136,11 @@ jobs:
|
|||||||
|
|
||||||
- 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.5.0
|
||||||
with:
|
|
||||||
rustflags: ''
|
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3.6
|
||||||
with:
|
with:
|
||||||
version: 0.81.0
|
version: 0.85.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -150,6 +148,7 @@ 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 }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
@ -176,7 +175,118 @@ jobs:
|
|||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
# Create a release only in nushell/nightly repo
|
# Create a release only in nushell/nightly repo
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.13
|
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.5.0
|
||||||
|
|
||||||
|
- name: Setup Nushell
|
||||||
|
uses: hustcer/setup-nu@v3.6
|
||||||
|
with:
|
||||||
|
version: 0.85.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 }}
|
||||||
|
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') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
draft: false
|
draft: false
|
||||||
@ -200,14 +310,14 @@ jobs:
|
|||||||
- name: Waiting for Release
|
- name: Waiting for Release
|
||||||
run: sleep 1800
|
run: sleep 1800
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3.6
|
||||||
with:
|
with:
|
||||||
version: 0.81.0
|
version: 0.85.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
33
.github/workflows/release-pkg.nu
vendored
33
.github/workflows/release-pkg.nu
vendored
@ -51,11 +51,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 }
|
||||||
@ -141,7 +156,7 @@ cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
|||||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||||
|
|
||||||
let files = (ls | get name)
|
let files = (ls | get name)
|
||||||
let dest = $'($bin)-($version)-($target)'
|
let dest = 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
|
||||||
@ -172,18 +187,22 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
|
|||||||
cp -r $'($dist)/*' target/release/
|
cp -r $'($dist)/*' target/release/
|
||||||
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
|
||||||
print $'archive: ---> ($wixRelease)';
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
let archive = ($wixRelease | str replace --all '\' '/')
|
||||||
|
print $'archive: ---> ($archive)';
|
||||||
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||||
let archive = $'($dist)/($releaseStem).zip'
|
let archive = $'($dist)/($releaseStem).zip'
|
||||||
7z a $archive *
|
7z a $archive *
|
||||||
print $'archive: ---> ($archive)';
|
|
||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | is-empty) {
|
if not ($pkg | is-empty) {
|
||||||
echo $"archive=($pkg | get 0)" | save --append $env.GITHUB_OUTPUT
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
|
let archive = ($pkg | get 0 | str replace --all '\' '/')
|
||||||
|
print $'archive: ---> ($archive)'
|
||||||
|
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
107
.github/workflows/release.yml
vendored
107
.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,15 +64,15 @@ 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-20.04
|
||||||
target_rustflags: '--exclude=nu-cmd-dataframe'
|
target_rustflags: ''
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Update Rust Toolchain Target
|
- name: Update Rust Toolchain Target
|
||||||
run: |
|
run: |
|
||||||
@ -80,13 +80,11 @@ jobs:
|
|||||||
|
|
||||||
- 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.5.0
|
||||||
with:
|
|
||||||
rustflags: ''
|
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3.6
|
||||||
with:
|
with:
|
||||||
version: 0.81.0
|
version: 0.85.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -94,6 +92,7 @@ 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 }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
@ -102,7 +101,95 @@ jobs:
|
|||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.13
|
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.5.0
|
||||||
|
|
||||||
|
- name: Setup Nushell
|
||||||
|
uses: hustcer/setup-nu@v3.6
|
||||||
|
with:
|
||||||
|
version: 0.85.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 }}
|
||||||
|
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/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
6
.github/workflows/typos.yml
vendored
6
.github/workflows/typos.yml
vendored
@ -7,7 +7,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions Repository
|
- name: Checkout Actions Repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@master
|
uses: crate-ci/typos@v1.16.19
|
||||||
|
with:
|
||||||
|
config: ./.github/.typos.toml
|
||||||
|
5
.github/workflows/winget-submission.yml
vendored
5
.github/workflows/winget-submission.yml
vendored
@ -14,12 +14,15 @@ jobs:
|
|||||||
|
|
||||||
winget:
|
winget:
|
||||||
name: Publish winget package
|
name: Publish winget package
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
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 }}
|
||||||
|
@ -2,7 +2,20 @@
|
|||||||
|
|
||||||
Welcome to Nushell and thank you for considering contributing!
|
Welcome to Nushell and thank you for considering contributing!
|
||||||
|
|
||||||
## Review Process
|
## Table of contents
|
||||||
|
- [Proposing design changes](#proposing-design-changes)
|
||||||
|
- [Developing](#developing)
|
||||||
|
- [Setup](#setup)
|
||||||
|
- [Tests](#tests)
|
||||||
|
- [Useful commands](#useful-commands)
|
||||||
|
- [Debugging tips](#debugging-tips)
|
||||||
|
- [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)
|
||||||
|
|
||||||
|
## 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.
|
||||||
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
||||||
@ -33,7 +46,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`
|
||||||
@ -41,10 +54,13 @@ Tests can be found in different places:
|
|||||||
* command examples
|
* command examples
|
||||||
* crate-specific tests
|
* crate-specific tests
|
||||||
|
|
||||||
The most comprehensive test suite we have is the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
Most of the tests are built upon the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||||
|
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
- Build and run Nushell:
|
- Build and run Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -59,7 +75,7 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
|||||||
- Run Clippy on Nushell:
|
- Run Clippy on Nushell:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err
|
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used
|
||||||
```
|
```
|
||||||
or via the `toolkit.nu` command:
|
or via the `toolkit.nu` command:
|
||||||
```shell
|
```shell
|
||||||
@ -221,6 +237,51 @@ 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.)
|
||||||
|
|
||||||
### License
|
## 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
|
||||||
|
|
||||||
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.
|
||||||
|
1727
Cargo.lock
generated
1727
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
66
Cargo.toml
66
Cargo.toml
@ -11,7 +11,7 @@ license = "MIT"
|
|||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.83.0"
|
version = "0.86.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -46,35 +46,34 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.83.0" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.86.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.83.0" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.86.0" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.83.0" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.86.0" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.83.0" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.86.0" }
|
||||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.83.0", optional = true }
|
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.86.0", features = ["dataframe"], optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.83.0", optional = true }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.86.0", optional = true }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.83.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.86.0" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.83.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.86.0" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.83.0" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.86.0" }
|
||||||
nu-json = { path = "./crates/nu-json", version = "0.83.0" }
|
nu-json = { path = "./crates/nu-json", version = "0.86.0" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.83.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.86.0" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.83.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.86.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.83.0" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.86.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.83.0" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.86.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.83.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.86.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.83.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.86.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.83.0" }
|
nu-table = { path = "./crates/nu-table", version = "0.86.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.83.0" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.86.0" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.83.0" }
|
nu-std = { path = "./crates/nu-std", version = "0.86.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.83.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.86.0" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.25.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
mimalloc = { version = "0.1.37", default-features = false, optional = true}
|
crossterm = "0.27"
|
||||||
|
|
||||||
crossterm = "0.26"
|
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||||
|
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
@ -88,22 +87,21 @@ signal-hook = { version = "0.3", default-features = false }
|
|||||||
winresource = "0.1"
|
winresource = "0.1"
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dependencies]
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
nix = { version = "0.26", default-features = false, features = [
|
nix = { version = "0.27", default-features = false, features = [
|
||||||
"signal",
|
"signal",
|
||||||
"process",
|
"process",
|
||||||
"fs",
|
"fs",
|
||||||
"term",
|
"term",
|
||||||
] }
|
] }
|
||||||
is-terminal = "0.4.8"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.83.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.86.0" }
|
||||||
tempfile = "3.7"
|
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
criterion = "0.5"
|
criterion = "0.5"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
|
rstest = { version = "0.18", default-features = false }
|
||||||
serial_test = "2.0"
|
serial_test = "2.0"
|
||||||
rstest = { version = "0.17", default-features = false }
|
tempfile = "3.8"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = [
|
plugin = [
|
||||||
@ -114,12 +112,13 @@ plugin = [
|
|||||||
"nu-protocol/plugin",
|
"nu-protocol/plugin",
|
||||||
"nu-engine/plugin",
|
"nu-engine/plugin",
|
||||||
]
|
]
|
||||||
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
default = ["plugin", "which-support", "trash-support", "sqlite", "mimalloc"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
wasi = ["nu-cmd-lang/wasi"]
|
wasi = ["nu-cmd-lang/wasi"]
|
||||||
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
||||||
|
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
|
||||||
|
# otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
||||||
|
|
||||||
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
||||||
@ -165,8 +164,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`
|
||||||
|
15
Cross.toml
15
Cross.toml
@ -1,9 +1,18 @@
|
|||||||
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||||
# Run cross-rs like this:
|
# Run cross-rs like this:
|
||||||
# cross build --target aarch64-unknown-linux-musl --release
|
# cross build --target aarch64-unknown-linux-gnu --release
|
||||||
|
# or
|
||||||
|
# cross build --target aarch64-unknown-linux-musl --release --features=static-link-openssl
|
||||||
|
|
||||||
[target.aarch64-unknown-linux-gnu]
|
[target.aarch64-unknown-linux-gnu]
|
||||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
pre-build = [
|
||||||
|
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
|
"apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH clang"
|
||||||
|
]
|
||||||
|
|
||||||
|
# NOTE: for musl you will need to build with --features=static-link-openssl
|
||||||
[target.aarch64-unknown-linux-musl]
|
[target.aarch64-unknown-linux-musl]
|
||||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
pre-build = [
|
||||||
|
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||||
|
"apt-get update && apt-get install --assume-yes clang"
|
||||||
|
]
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
[](https://twitter.com/nu_shell)
|
[](https://twitter.com/nu_shell)
|
||||||
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
||||||
[](https://github.com/nushell/nushell/graphs/contributors)
|
[](https://github.com/nushell/nushell/graphs/contributors)
|
||||||
[](https://codecov.io/gh/nushell/nushell)
|
|
||||||
|
|
||||||
A new type of shell.
|
A new type of shell.
|
||||||
|
|
||||||
@ -220,13 +219,15 @@ Please submit an issue or PR to be added to this list.
|
|||||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||||
- [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)
|
||||||
|
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||||
|
|
||||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=600" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -114,15 +114,13 @@ fn eval_benchmarks(c: &mut Criterion) {
|
|||||||
|
|
||||||
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||||
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
||||||
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
|
let record = Value::test_record(
|
||||||
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
|
(0..col_cnt)
|
||||||
|
.map(|x| (format!("col_{x}"), Value::test_int(x as i64)))
|
||||||
Value::List {
|
|
||||||
vals: (0..row_cnt)
|
|
||||||
.map(|_| Value::test_record(columns.clone(), vals.clone()))
|
|
||||||
.collect(),
|
.collect(),
|
||||||
span: Span::test_data(),
|
);
|
||||||
}
|
|
||||||
|
Value::list(vec![record; row_cnt], Span::test_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encoding_benchmarks(c: &mut Criterion) {
|
fn encoding_benchmarks(c: &mut Criterion) {
|
||||||
|
17
codecov.yml
17
codecov.yml
@ -1,17 +0,0 @@
|
|||||||
coverage:
|
|
||||||
status:
|
|
||||||
project:
|
|
||||||
default:
|
|
||||||
target: 55%
|
|
||||||
threshold: 2%
|
|
||||||
patch:
|
|
||||||
default:
|
|
||||||
informational: true
|
|
||||||
|
|
||||||
comment:
|
|
||||||
layout: reach, diff, files
|
|
||||||
behavior: default
|
|
||||||
require_base: yes
|
|
||||||
require_head: yes
|
|
||||||
after_n_builds: 1 # Disabled windows else: 2
|
|
||||||
|
|
@ -5,40 +5,41 @@ 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.83.0"
|
version = "0.86.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.86.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.83.0" }
|
nu-command = { path = "../nu-command", version = "0.86.0" }
|
||||||
rstest = { version = "0.17.0", default-features = false }
|
nu-test-support = { path = "../nu-test-support", version = "0.86.0" }
|
||||||
|
rstest = { version = "0.18.1", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.86.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.83.0" }
|
nu-engine = { path = "../nu-engine", version = "0.86.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.83.0" }
|
nu-path = { path = "../nu-path", version = "0.86.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.83.0" }
|
nu-parser = { path = "../nu-parser", version = "0.86.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.83.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.86.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.83.0" }
|
nu-utils = { path = "../nu-utils", version = "0.86.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.83.0" }
|
nu-color-config = { path = "../nu-color-config", version = "0.86.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.83.0" }
|
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.25.0", features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
||||||
crossterm = "0.26"
|
crossterm = "0.27"
|
||||||
fancy-regex = "0.11"
|
fancy-regex = "0.11"
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = "0.3"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
is-terminal = "0.4.8"
|
|
||||||
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"
|
||||||
|
pathdiff = "0.2"
|
||||||
sysinfo = "0.29"
|
sysinfo = "0.29"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = []
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::{
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
ast::Call,
|
||||||
use nu_protocol::Category;
|
engine::{Command, EngineState, Stack},
|
||||||
use nu_protocol::IntoPipelineData;
|
Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -25,6 +25,11 @@ impl Command for Commandline {
|
|||||||
"Set or get the current cursor position",
|
"Set or get the current cursor position",
|
||||||
Some('c'),
|
Some('c'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"cursor-end",
|
||||||
|
"Set the current cursor position to the end of the buffer",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"append",
|
"append",
|
||||||
"appends the string to the end of the buffer",
|
"appends the string to the end of the buffer",
|
||||||
@ -84,9 +89,9 @@ impl Command for Commandline {
|
|||||||
return Err(ShellError::CantConvert {
|
return Err(ShellError::CantConvert {
|
||||||
to_type: "int".to_string(),
|
to_type: "int".to_string(),
|
||||||
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"#
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -102,27 +107,22 @@ impl Command for Commandline {
|
|||||||
repl.buffer = cmd.as_string()?;
|
repl.buffer = cmd.as_string()?;
|
||||||
repl.cursor_pos = repl.buffer.len();
|
repl.cursor_pos = repl.buffer.len();
|
||||||
}
|
}
|
||||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
} else {
|
} else {
|
||||||
let repl = engine_state.repl_state.lock().expect("repl state mutex");
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
if call.has_flag("cursor") {
|
if call.has_flag("cursor-end") {
|
||||||
|
repl.cursor_pos = repl.buffer.graphemes(true).count();
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
} else if call.has_flag("cursor") {
|
||||||
let char_pos = repl
|
let char_pos = repl
|
||||||
.buffer
|
.buffer
|
||||||
.grapheme_indices(true)
|
.grapheme_indices(true)
|
||||||
.chain(std::iter::once((repl.buffer.len(), "")))
|
.chain(std::iter::once((repl.buffer.len(), "")))
|
||||||
.position(|(i, _c)| i == repl.cursor_pos)
|
.position(|(i, _c)| i == repl.cursor_pos)
|
||||||
.expect("Cursor position isn't on a grapheme boundary");
|
.expect("Cursor position isn't on a grapheme boundary");
|
||||||
Ok(Value::String {
|
Ok(Value::string(char_pos.to_string(), call.head).into_pipeline_data())
|
||||||
val: char_pos.to_string(),
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::String {
|
Ok(Value::string(repl.buffer.to_string(), call.head).into_pipeline_data())
|
||||||
val: repl.buffer.to_string(),
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
|
record, Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData,
|
||||||
Signature, Span, Type, Value,
|
ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
||||||
@ -70,12 +70,14 @@ impl Command for History {
|
|||||||
} else {
|
} else {
|
||||||
let history_reader: Option<Box<dyn ReedlineHistory>> =
|
let history_reader: Option<Box<dyn ReedlineHistory>> =
|
||||||
match engine_state.config.history_file_format {
|
match engine_state.config.history_file_format {
|
||||||
HistoryFileFormat::Sqlite => SqliteBackedHistory::with_file(history_path)
|
HistoryFileFormat::Sqlite => {
|
||||||
|
SqliteBackedHistory::with_file(history_path, None, None)
|
||||||
.map(|inner| {
|
.map(|inner| {
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
boxed
|
boxed
|
||||||
})
|
})
|
||||||
.ok(),
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
||||||
engine_state.config.max_history_size as usize,
|
engine_state.config.max_history_size as usize,
|
||||||
@ -95,19 +97,14 @@ impl Command for History {
|
|||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
.map(move |entries| {
|
.map(move |entries| {
|
||||||
entries
|
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||||
.into_iter()
|
Value::record(
|
||||||
.enumerate()
|
record! {
|
||||||
.map(move |(idx, entry)| Value::Record {
|
"command" => Value::string(entry.command_line, head),
|
||||||
cols: vec!["command".to_string(), "index".to_string()],
|
"index" => Value::int(idx as i64, head),
|
||||||
vals: vec![
|
|
||||||
Value::String {
|
|
||||||
val: entry.command_line,
|
|
||||||
span: head,
|
|
||||||
},
|
},
|
||||||
Value::int(idx as i64, head),
|
head,
|
||||||
],
|
)
|
||||||
span: head,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound(head))?
|
.ok_or(ShellError::FileNotFound(head))?
|
||||||
@ -144,7 +141,7 @@ impl Command for History {
|
|||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "history | wrap cmd | where cmd =~ cargo",
|
example: "history | where command =~ cargo | get command",
|
||||||
description: "Search all the commands from history that contains 'cargo'",
|
description: "Search all the commands from history that contains 'cargo'",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
@ -156,8 +153,8 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
//1. Format all the values
|
//1. Format all the values
|
||||||
//2. Create a record of either short or long columns and values
|
//2. Create a record of either short or long columns and values
|
||||||
|
|
||||||
let item_id_value = Value::Int {
|
let item_id_value = Value::int(
|
||||||
val: match entry.id {
|
match entry.id {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let ids = id.to_string();
|
let ids = id.to_string();
|
||||||
match ids.parse::<i64>() {
|
match ids.parse::<i64>() {
|
||||||
@ -167,21 +164,18 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
}
|
}
|
||||||
None => 0i64,
|
None => 0i64,
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let start_timestamp_value = Value::String {
|
let start_timestamp_value = Value::string(
|
||||||
val: match entry.start_timestamp {
|
match entry.start_timestamp {
|
||||||
Some(time) => time.to_string(),
|
Some(time) => time.to_string(),
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let command_value = Value::String {
|
let command_value = Value::string(entry.command_line, head);
|
||||||
val: entry.command_line,
|
let session_id_value = Value::int(
|
||||||
span: head,
|
match entry.session_id {
|
||||||
};
|
|
||||||
let session_id_value = Value::Int {
|
|
||||||
val: match entry.session_id {
|
|
||||||
Some(sid) => {
|
Some(sid) => {
|
||||||
let sids = sid.to_string();
|
let sids = sid.to_string();
|
||||||
match sids.parse::<i64>() {
|
match sids.parse::<i64>() {
|
||||||
@ -191,74 +185,56 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
}
|
}
|
||||||
None => 0i64,
|
None => 0i64,
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let hostname_value = Value::String {
|
let hostname_value = Value::string(
|
||||||
val: match entry.hostname {
|
match entry.hostname {
|
||||||
Some(host) => host,
|
Some(host) => host,
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let cwd_value = Value::String {
|
let cwd_value = Value::string(
|
||||||
val: match entry.cwd {
|
match entry.cwd {
|
||||||
Some(cwd) => cwd,
|
Some(cwd) => cwd,
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let duration_value = Value::Duration {
|
let duration_value = Value::duration(
|
||||||
val: match entry.duration {
|
match entry.duration {
|
||||||
Some(d) => d.as_nanos().try_into().unwrap_or(0),
|
Some(d) => d.as_nanos().try_into().unwrap_or(0),
|
||||||
None => 0,
|
None => 0,
|
||||||
},
|
},
|
||||||
span: head,
|
head,
|
||||||
};
|
);
|
||||||
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
|
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
|
||||||
let index_value = Value::int(idx as i64, head);
|
let index_value = Value::int(idx as i64, head);
|
||||||
if long {
|
if long {
|
||||||
Value::Record {
|
Value::record(
|
||||||
cols: vec![
|
record! {
|
||||||
"item_id".into(),
|
"item_id" => item_id_value,
|
||||||
"start_timestamp".into(),
|
"start_timestamp" => start_timestamp_value,
|
||||||
"command".to_string(),
|
"command" => command_value,
|
||||||
"session_id".into(),
|
"session_id" => session_id_value,
|
||||||
"hostname".into(),
|
"hostname" => hostname_value,
|
||||||
"cwd".into(),
|
"cwd" => cwd_value,
|
||||||
"duration".into(),
|
"duration" => duration_value,
|
||||||
"exit_status".into(),
|
"exit_status" => exit_status_value,
|
||||||
"idx".to_string(),
|
"idx" => index_value,
|
||||||
],
|
},
|
||||||
vals: vec![
|
head,
|
||||||
item_id_value,
|
)
|
||||||
start_timestamp_value,
|
|
||||||
command_value,
|
|
||||||
session_id_value,
|
|
||||||
hostname_value,
|
|
||||||
cwd_value,
|
|
||||||
duration_value,
|
|
||||||
exit_status_value,
|
|
||||||
index_value,
|
|
||||||
],
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Value::Record {
|
Value::record(
|
||||||
cols: vec![
|
record! {
|
||||||
"start_timestamp".into(),
|
"start_timestamp" => start_timestamp_value,
|
||||||
"command".to_string(),
|
"command" => command_value,
|
||||||
"cwd".into(),
|
"cwd" => cwd_value,
|
||||||
"duration".into(),
|
"duration" => duration_value,
|
||||||
"exit_status".into(),
|
"exit_status" => exit_status_value,
|
||||||
],
|
},
|
||||||
vals: vec![
|
head,
|
||||||
start_timestamp_value,
|
)
|
||||||
command_value,
|
|
||||||
cwd_value,
|
|
||||||
duration_value,
|
|
||||||
exit_status_value,
|
|
||||||
],
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,10 @@ impl Command for Keybindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
r#"You must use one of the following subcommands. Using this command as-is will only produce this help message.
|
||||||
|
|
||||||
|
For more information on input and keybindings, check:
|
||||||
|
https://www.nushell.sh/book/line_editor.html"#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -38,16 +41,16 @@ impl Command for Keybindings {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(Value::String {
|
Ok(Value::string(
|
||||||
val: get_full_help(
|
get_full_help(
|
||||||
&Keybindings.signature(),
|
&Keybindings.signature(),
|
||||||
&Keybindings.examples(),
|
&Keybindings.examples(),
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
self.is_parser_keyword(),
|
self.is_parser_keyword(),
|
||||||
),
|
),
|
||||||
span: call.head,
|
call.head,
|
||||||
}
|
)
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
};
|
};
|
||||||
use reedline::get_reedline_default_keybindings;
|
use reedline::get_reedline_default_keybindings;
|
||||||
|
|
||||||
@ -41,43 +41,18 @@ impl Command for KeybindingsDefault {
|
|||||||
let records = get_reedline_default_keybindings()
|
let records = get_reedline_default_keybindings()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mode, modifier, code, event)| {
|
.map(|(mode, modifier, code, event)| {
|
||||||
let mode = Value::String {
|
Value::record(
|
||||||
val: mode,
|
record! {
|
||||||
span: call.head,
|
"mode" => Value::string(mode, call.head),
|
||||||
};
|
"modifier" => Value::string(modifier, call.head),
|
||||||
|
"code" => Value::string(code, call.head),
|
||||||
let modifier = Value::String {
|
"event" => Value::string(event, call.head),
|
||||||
val: modifier,
|
},
|
||||||
span: call.head,
|
call.head,
|
||||||
};
|
)
|
||||||
|
|
||||||
let code = Value::String {
|
|
||||||
val: code,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
let event = Value::String {
|
|
||||||
val: event,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::Record {
|
|
||||||
cols: vec![
|
|
||||||
"mode".to_string(),
|
|
||||||
"modifier".to_string(),
|
|
||||||
"code".to_string(),
|
|
||||||
"event".to_string(),
|
|
||||||
],
|
|
||||||
vals: vec![mode, modifier, code, event],
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Value::List {
|
Ok(Value::list(records, call.head).into_pipeline_data())
|
||||||
vals: records,
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
|
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
|
||||||
@ -62,23 +63,19 @@ impl Command for KeybindingsList {
|
|||||||
let all_options = ["modifiers", "keycodes", "edits", "modes", "events"];
|
let all_options = ["modifiers", "keycodes", "edits", "modes", "events"];
|
||||||
all_options
|
all_options
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|argument| get_records(argument, &call.head))
|
.flat_map(|argument| get_records(argument, call.head))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
call.named_iter()
|
call.named_iter()
|
||||||
.flat_map(|(argument, _, _)| get_records(argument.item.as_str(), &call.head))
|
.flat_map(|(argument, _, _)| get_records(argument.item.as_str(), call.head))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Value::List {
|
Ok(Value::list(records, call.head).into_pipeline_data())
|
||||||
vals: records,
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_records(entry_type: &str, span: &Span) -> Vec<Value> {
|
fn get_records(entry_type: &str, span: Span) -> Vec<Value> {
|
||||||
let values = match entry_type {
|
let values = match entry_type {
|
||||||
"modifiers" => get_reedline_keybinding_modifiers().sorted(),
|
"modifiers" => get_reedline_keybinding_modifiers().sorted(),
|
||||||
"keycodes" => get_reedline_keycodes().sorted(),
|
"keycodes" => get_reedline_keycodes().sorted(),
|
||||||
@ -95,16 +92,14 @@ fn get_records(entry_type: &str, span: &Span) -> Vec<Value> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_to_record(edit: &str, entry_type: &str, span: &Span) -> Value {
|
fn convert_to_record(edit: &str, entry_type: &str, span: Span) -> Value {
|
||||||
let entry_type = Value::string(entry_type, *span);
|
Value::record(
|
||||||
|
record! {
|
||||||
let name = Value::string(edit, *span);
|
"type" => Value::string(entry_type, span),
|
||||||
|
"name" => Value::string(edit, span),
|
||||||
Value::Record {
|
},
|
||||||
cols: vec!["type".to_string(), "name".to_string()],
|
span,
|
||||||
vals: vec![entry_type, name],
|
)
|
||||||
span: *span,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to sort a vec and return a vec
|
// Helper to sort a vec and return a vec
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
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;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
@ -19,6 +21,10 @@ impl Command for KeybindingsListen {
|
|||||||
"Get input from the user."
|
"Get input from the user."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"This is an internal debugging tool. For better output, try `input listen --types [key]`"
|
||||||
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.category(Category::Platform)
|
.category(Category::Platform)
|
||||||
@ -64,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 {
|
||||||
@ -78,9 +110,8 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
let v = print_events_helper(event)?;
|
let v = print_events_helper(event)?;
|
||||||
// Print out the record
|
// Print out the record
|
||||||
let o = match v {
|
let o = match v {
|
||||||
Value::Record { cols, vals, .. } => cols
|
Value::Record { val, .. } => val
|
||||||
.iter()
|
.iter()
|
||||||
.zip(vals.iter())
|
|
||||||
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
|
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
@ -91,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()))
|
||||||
@ -111,54 +150,29 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
|
|||||||
{
|
{
|
||||||
match code {
|
match code {
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
let record = Value::Record {
|
let record = record! {
|
||||||
cols: vec![
|
"char" => Value::string(format!("{c}"), Span::unknown()),
|
||||||
"char".into(),
|
"code" => Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
|
||||||
"code".into(),
|
"modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
|
||||||
"modifier".into(),
|
"flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
||||||
"flags".into(),
|
"kind" => Value::string(format!("{kind:?}"), Span::unknown()),
|
||||||
"kind".into(),
|
"state" => Value::string(format!("{state:?}"), Span::unknown()),
|
||||||
"state".into(),
|
|
||||||
],
|
|
||||||
vals: vec![
|
|
||||||
Value::string(format!("{c}"), Span::unknown()),
|
|
||||||
Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
|
|
||||||
Value::string(format!("{modifiers:?}"), Span::unknown()),
|
|
||||||
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
|
||||||
Value::string(format!("{kind:?}"), Span::unknown()),
|
|
||||||
Value::string(format!("{state:?}"), Span::unknown()),
|
|
||||||
],
|
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
};
|
||||||
Ok(record)
|
Ok(Value::record(record, Span::unknown()))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let record = Value::Record {
|
let record = record! {
|
||||||
cols: vec![
|
"code" => Value::string(format!("{code:?}"), Span::unknown()),
|
||||||
"code".into(),
|
"modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
|
||||||
"modifier".into(),
|
"flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
||||||
"flags".into(),
|
"kind" => Value::string(format!("{kind:?}"), Span::unknown()),
|
||||||
"kind".into(),
|
"state" => Value::string(format!("{state:?}"), Span::unknown()),
|
||||||
"state".into(),
|
|
||||||
],
|
|
||||||
vals: vec![
|
|
||||||
Value::string(format!("{code:?}"), Span::unknown()),
|
|
||||||
Value::string(format!("{modifiers:?}"), Span::unknown()),
|
|
||||||
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
|
|
||||||
Value::string(format!("{kind:?}"), Span::unknown()),
|
|
||||||
Value::string(format!("{state:?}"), Span::unknown()),
|
|
||||||
],
|
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
};
|
||||||
Ok(record)
|
Ok(Value::record(record, Span::unknown()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let record = Value::Record {
|
let record = record! { "event" => Value::string(format!("{event:?}"), Span::unknown()) };
|
||||||
cols: vec!["event".into()],
|
Ok(Value::record(record, Span::unknown()))
|
||||||
vals: vec![Value::string(format!("{event:?}"), Span::unknown())],
|
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
|
||||||
Ok(record)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
@ -263,7 +263,7 @@ mod command_completions_tests {
|
|||||||
(" hello sud", 1),
|
(" hello sud", 1),
|
||||||
];
|
];
|
||||||
for (idx, ele) in commands.iter().enumerate() {
|
for (idx, ele) in commands.iter().enumerate() {
|
||||||
let index = find_non_whitespace_index(&Vec::from(ele.0.as_bytes()), 0);
|
let index = find_non_whitespace_index(ele.0.as_bytes(), 0);
|
||||||
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,20 +67,20 @@ impl NuCompleter {
|
|||||||
) -> Option<Vec<Suggestion>> {
|
) -> Option<Vec<Suggestion>> {
|
||||||
let stack = self.stack.clone();
|
let stack = self.stack.clone();
|
||||||
let block = self.engine_state.get_block(block_id);
|
let block = self.engine_state.get_block(block_id);
|
||||||
let mut callee_stack = stack.gather_captures(&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.get(0) {
|
||||||
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,
|
||||||
Value::List {
|
Value::list(
|
||||||
vals: spans
|
spans
|
||||||
.iter()
|
.iter()
|
||||||
.map(|it| Value::string(it, Span::unknown()))
|
.map(|it| Value::string(it, Span::unknown()))
|
||||||
.collect(),
|
.collect(),
|
||||||
span: Span::unknown(),
|
Span::unknown(),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ impl NuCompleter {
|
|||||||
match result {
|
match result {
|
||||||
Ok(pd) => {
|
Ok(pd) => {
|
||||||
let value = pd.into_value(span);
|
let value = pd.into_value(span);
|
||||||
if let Value::List { vals, span: _ } = value {
|
if let Value::List { vals, .. } = value {
|
||||||
let result =
|
let result =
|
||||||
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
||||||
|
|
||||||
@ -136,7 +136,9 @@ impl NuCompleter {
|
|||||||
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 == &String::from("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();
|
||||||
@ -454,7 +456,7 @@ pub fn map_value_completions<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match for record values
|
// Match for record values
|
||||||
if let Ok((cols, vals)) = x.as_record() {
|
if let Ok(record) = x.as_record() {
|
||||||
let mut suggestion = Suggestion {
|
let mut suggestion = Suggestion {
|
||||||
value: String::from(""), // Initialize with empty string
|
value: String::from(""), // Initialize with empty string
|
||||||
description: None,
|
description: None,
|
||||||
@ -467,7 +469,7 @@ pub fn map_value_completions<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Iterate the cols looking for `value` and `description`
|
// Iterate the cols looking for `value` and `description`
|
||||||
cols.iter().zip(vals).for_each(|it| {
|
record.iter().for_each(|it| {
|
||||||
// Match `value` column
|
// Match `value` column
|
||||||
if it.0 == "value" {
|
if it.0 == "value" {
|
||||||
// Convert the value to string
|
// Convert the value to string
|
||||||
|
160
crates/nu-cli/src/completions/completion_common.rs
Normal file
160
crates/nu-cli/src/completions/completion_common.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use crate::completions::{matches, CompletionOptions};
|
||||||
|
use nu_path::home_dir;
|
||||||
|
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))
|
||||||
|
} 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(['\'', '"', ' ', '#']);
|
||||||
|
if filename_contaminated || dirname_contaminated || path.parse::<f64>().is_ok() {
|
||||||
|
format!("`{path}`")
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,12 @@
|
|||||||
use crate::completions::{matches, Completer, CompletionOptions};
|
use crate::completions::{completion_common::complete_item, 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>,
|
||||||
@ -33,11 +28,15 @@ impl Completer for DirectoryCompletion {
|
|||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = self.engine_state.current_work_dir();
|
|
||||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
let output: Vec<_> = directory_completion(span, &partial, &cwd, options)
|
let output: Vec<_> = directory_completion(
|
||||||
|
span,
|
||||||
|
&partial,
|
||||||
|
&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,
|
||||||
@ -63,7 +62,12 @@ impl Completer for DirectoryCompletion {
|
|||||||
|
|
||||||
match self.get_sort_by() {
|
match self.get_sort_by() {
|
||||||
SortBy::Ascending => {
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
sorted_items.sort_by(|a, b| {
|
||||||
|
// Ignore trailing slashes in folder names when sorting
|
||||||
|
a.value
|
||||||
|
.trim_end_matches(SEP)
|
||||||
|
.cmp(b.value.trim_end_matches(SEP))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
SortBy::LevenshteinDistance => {
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
@ -106,60 +110,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, 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());
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions};
|
use crate::completions::{completion_common::complete_item, 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::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>,
|
||||||
@ -32,9 +28,13 @@ impl Completer for FileCompletion {
|
|||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let cwd = self.engine_state.current_work_dir();
|
|
||||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
let output: Vec<_> = file_path_completion(
|
||||||
|
span,
|
||||||
|
&prefix,
|
||||||
|
&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,
|
||||||
@ -60,7 +60,12 @@ impl Completer for FileCompletion {
|
|||||||
|
|
||||||
match self.get_sort_by() {
|
match self.get_sort_by() {
|
||||||
SortBy::Ascending => {
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
sorted_items.sort_by(|a, b| {
|
||||||
|
// Ignore trailing slashes in folder names when sorting
|
||||||
|
a.value
|
||||||
|
.trim_end_matches(SEP)
|
||||||
|
.cmp(b.value.trim_end_matches(SEP))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
SortBy::LevenshteinDistance => {
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
@ -97,84 +102,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,23 +121,3 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
|||||||
|
|
||||||
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;
|
||||||
|
@ -235,13 +235,9 @@ fn nested_suggestions(
|
|||||||
let value = recursive_value(val, sublevels);
|
let value = recursive_value(val, sublevels);
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
Value::Record {
|
Value::Record { val, .. } => {
|
||||||
cols,
|
|
||||||
vals: _,
|
|
||||||
span: _,
|
|
||||||
} => {
|
|
||||||
// Add all the columns as completion
|
// Add all the columns as completion
|
||||||
for item in cols {
|
for item in val.cols {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: item,
|
value: item,
|
||||||
description: None,
|
description: None,
|
||||||
@ -267,7 +263,7 @@ fn nested_suggestions(
|
|||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
Value::List { vals, span: _ } => {
|
Value::List { vals, .. } => {
|
||||||
for column_name in get_columns(vals.as_slice()) {
|
for column_name in get_columns(vals.as_slice()) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: column_name,
|
value: column_name,
|
||||||
@ -288,13 +284,10 @@ fn nested_suggestions(
|
|||||||
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||||
// Go to next sublevel
|
// Go to next sublevel
|
||||||
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
|
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
|
||||||
|
let span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record {
|
Value::Record { val, .. } => {
|
||||||
cols,
|
for item in val {
|
||||||
vals,
|
|
||||||
span: _,
|
|
||||||
} => {
|
|
||||||
for item in cols.into_iter().zip(vals) {
|
|
||||||
// Check if index matches with sublevel
|
// Check if index matches with sublevel
|
||||||
if item.0.as_bytes().to_vec() == next_sublevel {
|
if item.0.as_bytes().to_vec() == next_sublevel {
|
||||||
// If matches try to fetch recursively the next
|
// If matches try to fetch recursively the next
|
||||||
@ -303,11 +296,9 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::Nothing {
|
return Value::nothing(span);
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Value::LazyRecord { val, span: _ } => {
|
Value::LazyRecord { val, .. } => {
|
||||||
for col in val.column_names() {
|
for col in val.column_names() {
|
||||||
if col.as_bytes().to_vec() == next_sublevel {
|
if col.as_bytes().to_vec() == next_sublevel {
|
||||||
return recursive_value(
|
return recursive_value(
|
||||||
@ -318,15 +309,13 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::Nothing {
|
return Value::nothing(span);
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Value::List { vals, span } => {
|
Value::List { vals, .. } => {
|
||||||
for col in get_columns(vals.as_slice()) {
|
for col in get_columns(vals.as_slice()) {
|
||||||
if col.as_bytes().to_vec() == next_sublevel {
|
if col.as_bytes().to_vec() == next_sublevel {
|
||||||
return recursive_value(
|
return recursive_value(
|
||||||
Value::List { vals, span }
|
Value::list(vals, span)
|
||||||
.get_data_by_key(&col)
|
.get_data_by_key(&col)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
sublevels.into_iter().skip(1).collect(),
|
sublevels.into_iter().skip(1).collect(),
|
||||||
@ -335,9 +324,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::Nothing {
|
return Value::nothing(span);
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
_ => return val,
|
_ => return val,
|
||||||
}
|
}
|
||||||
|
@ -2,6 +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;
|
||||||
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;
|
||||||
@ -98,23 +99,71 @@ pub fn evaluate_file(
|
|||||||
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let source_filename = file_path
|
||||||
|
.file_name()
|
||||||
|
.expect("internal error: script missing filename");
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
trace!("parsing file: {}", file_path_str);
|
trace!("parsing file: {}", file_path_str);
|
||||||
let _ = parse(&mut working_set, Some(file_path_str), &file, false);
|
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||||
|
|
||||||
if working_set.find_decl(b"main").is_some() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
let args = format!("main {}", args.join(" "));
|
report_error(&working_set, err);
|
||||||
|
|
||||||
if !eval_source(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
&file,
|
|
||||||
file_path_str,
|
|
||||||
PipelineData::empty(),
|
|
||||||
true,
|
|
||||||
) {
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for block in &mut working_set.delta.blocks {
|
||||||
|
if block.signature.name == "main" {
|
||||||
|
block.signature.name = source_filename.to_string_lossy().to_string();
|
||||||
|
} else if block.signature.name.starts_with("main ") {
|
||||||
|
block.signature.name =
|
||||||
|
source_filename.to_string_lossy().to_string() + " " + &block.signature.name[5..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = engine_state.merge_delta(working_set.delta);
|
||||||
|
|
||||||
|
if engine_state.find_decl(b"main", &[]).is_some() {
|
||||||
|
let args = format!("main {}", args.join(" "));
|
||||||
|
|
||||||
|
let pipeline_data = eval_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
&block,
|
||||||
|
PipelineData::empty(),
|
||||||
|
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| {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &e);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = pipeline_data.print(engine_state, stack, true, false);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(err) => {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Ok(exit_code) => {
|
||||||
|
if exit_code != 0 {
|
||||||
|
std::process::exit(exit_code as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !eval_source(
|
if !eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -148,7 +197,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);
|
||||||
|
|
||||||
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
if let PipelineData::Value(Value::Error { error, .. }, ..) = &pipeline_data {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &**error);
|
report_error(&working_set, &**error);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@ -195,7 +244,7 @@ pub(crate) fn print_table_or_error(
|
|||||||
|
|
||||||
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||||
for item in pipeline_data {
|
for item in pipeline_data {
|
||||||
if let Value::Error { error } = item {
|
if let Value::Error { error, .. } = item {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &*error);
|
report_error(&working_set, &*error);
|
||||||
|
@ -646,7 +646,10 @@ impl Menu for DescriptionMenu {
|
|||||||
|lb| {
|
|lb| {
|
||||||
lb.replace_range(start..end, replacement);
|
lb.replace_range(start..end, replacement);
|
||||||
let mut offset = lb.insertion_point();
|
let mut offset = lb.insertion_point();
|
||||||
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
offset += lb
|
||||||
|
.len()
|
||||||
|
.saturating_sub(end.saturating_sub(start))
|
||||||
|
.saturating_sub(start);
|
||||||
lb.set_insertion_point(offset);
|
lb.set_insertion_point(offset);
|
||||||
},
|
},
|
||||||
UndoBehavior::CreateUndoPoint,
|
UndoBehavior::CreateUndoPoint,
|
||||||
|
@ -57,7 +57,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)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -48,14 +48,9 @@ impl Command for NuHighlight {
|
|||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let highlights = highlighter.highlight(&line, line.len());
|
let highlights = highlighter.highlight(&line, line.len());
|
||||||
|
|
||||||
Value::String {
|
Value::string(highlights.render_simple(), head)
|
||||||
val: highlights.render_simple(),
|
|
||||||
span: head,
|
|
||||||
}
|
}
|
||||||
}
|
Err(err) => Value::error(err, head),
|
||||||
Err(err) => Value::Error {
|
|
||||||
error: Box::new(err),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ctrlc,
|
ctrlc,
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,11 @@ impl Command for Print {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("print")
|
Signature::build("print")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![
|
||||||
|
(Type::Nothing, Type::Nothing),
|
||||||
|
(Type::Any, Type::Nothing),
|
||||||
|
])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||||
.switch(
|
.switch(
|
||||||
"no-newline",
|
"no-newline",
|
||||||
|
@ -109,8 +109,7 @@ impl Prompt for NushellPrompt {
|
|||||||
let prompt = default
|
let prompt = default
|
||||||
.render_prompt_left()
|
.render_prompt_left()
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace('\n', "\r\n")
|
.replace('\n', "\r\n");
|
||||||
+ " ";
|
|
||||||
|
|
||||||
prompt.into()
|
prompt.into()
|
||||||
}
|
}
|
||||||
@ -144,11 +143,11 @@ impl Prompt for NushellPrompt {
|
|||||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||||
Some(indicator) => indicator,
|
Some(indicator) => indicator,
|
||||||
None => ": ",
|
None => "> ",
|
||||||
},
|
},
|
||||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||||
Some(indicator) => indicator,
|
Some(indicator) => indicator,
|
||||||
None => "> ",
|
None => ": ",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -7,6 +7,8 @@ use nu_protocol::{
|
|||||||
Config, PipelineData, Value,
|
Config, PipelineData, Value,
|
||||||
};
|
};
|
||||||
use reedline::Prompt;
|
use reedline::Prompt;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
// Name of environment variable where the prompt could be stored
|
// Name of environment variable where the prompt could be stored
|
||||||
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||||
@ -15,6 +17,15 @@ 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\\";
|
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||||
@ -145,3 +156,119 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TransientPrompt {
|
||||||
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try getting `$env.TRANSIENT_PROMPT_<X>`, and get `$env.PROMPT_<X>` if that fails
|
||||||
|
fn get_transient_prompt_string(
|
||||||
|
transient_prompt: &str,
|
||||||
|
prompt: &str,
|
||||||
|
config: &Config,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Option<String> {
|
||||||
|
get_prompt_string(transient_prompt, config, engine_state, stack)
|
||||||
|
.or_else(|| get_prompt_string(prompt, config, engine_state, stack))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prompt for TransientPrompt {
|
||||||
|
fn render_prompt_left(&self) -> Cow<str> {
|
||||||
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
let config = &self.engine_state.get_config().clone();
|
||||||
|
let mut stack = self.stack.clone();
|
||||||
|
nu_prompt.update_prompt_left(get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_COMMAND,
|
||||||
|
PROMPT_COMMAND,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
));
|
||||||
|
nu_prompt.render_prompt_left().to_string().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_right(&self) -> Cow<str> {
|
||||||
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
let config = &self.engine_state.get_config().clone();
|
||||||
|
let mut stack = self.stack.clone();
|
||||||
|
nu_prompt.update_prompt_right(
|
||||||
|
get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_COMMAND_RIGHT,
|
||||||
|
PROMPT_COMMAND_RIGHT,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
),
|
||||||
|
config.render_right_prompt_on_last_line,
|
||||||
|
);
|
||||||
|
nu_prompt.render_prompt_right().to_string().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_indicator(&self, prompt_mode: reedline::PromptEditMode) -> Cow<str> {
|
||||||
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
let config = &self.engine_state.get_config().clone();
|
||||||
|
let mut stack = self.stack.clone();
|
||||||
|
nu_prompt.update_prompt_indicator(get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_INDICATOR,
|
||||||
|
PROMPT_INDICATOR,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
));
|
||||||
|
nu_prompt.update_prompt_vi_insert(get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
|
||||||
|
PROMPT_INDICATOR_VI_INSERT,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
));
|
||||||
|
nu_prompt.update_prompt_vi_normal(get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
|
||||||
|
PROMPT_INDICATOR_VI_NORMAL,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
));
|
||||||
|
nu_prompt
|
||||||
|
.render_prompt_indicator(prompt_mode)
|
||||||
|
.to_string()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||||
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
let config = &self.engine_state.get_config().clone();
|
||||||
|
let mut stack = self.stack.clone();
|
||||||
|
nu_prompt.update_prompt_multiline(get_transient_prompt_string(
|
||||||
|
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
|
||||||
|
PROMPT_MULTILINE_INDICATOR,
|
||||||
|
config,
|
||||||
|
&self.engine_state,
|
||||||
|
&mut stack,
|
||||||
|
));
|
||||||
|
nu_prompt
|
||||||
|
.render_prompt_multiline_indicator()
|
||||||
|
.to_string()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_history_search_indicator(
|
||||||
|
&self,
|
||||||
|
history_search: reedline::PromptHistorySearch,
|
||||||
|
) -> Cow<str> {
|
||||||
|
NushellPrompt::new()
|
||||||
|
.render_prompt_history_search_indicator(history_search)
|
||||||
|
.to_string()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the transient prompt
|
||||||
|
pub(crate) fn transient_prompt(engine_state: Arc<EngineState>, stack: &Stack) -> Box<dyn Prompt> {
|
||||||
|
Box::new(TransientPrompt {
|
||||||
|
engine_state,
|
||||||
|
stack: stack.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -7,7 +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, ShellError, Span, Value,
|
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, Record, 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,
|
||||||
@ -130,8 +131,9 @@ fn add_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
let span = menu.menu_type.span();
|
||||||
let layout = extract_value("layout", cols, vals, span)?.into_string("", config);
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
|
let layout = extract_value("layout", val, span)?.into_string("", config);
|
||||||
|
|
||||||
match layout.as_str() {
|
match layout.as_str() {
|
||||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
@ -140,22 +142,22 @@ fn add_menu(
|
|||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"columnar, list or description".to_string(),
|
"columnar, list or description".to_string(),
|
||||||
menu.menu_type.into_abbreviated_string(config),
|
menu.menu_type.into_abbreviated_string(config),
|
||||||
menu.menu_type.span()?,
|
menu.menu_type.span(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::UnsupportedConfigValue(
|
Err(ShellError::UnsupportedConfigValue(
|
||||||
"only record type".to_string(),
|
"only record type".to_string(),
|
||||||
menu.menu_type.into_abbreviated_string(config),
|
menu.menu_type.into_abbreviated_string(config),
|
||||||
menu.menu_type.span()?,
|
menu.menu_type.span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! add_style {
|
macro_rules! add_style {
|
||||||
// first arm match add!(1,2), add!(2,3) etc
|
// first arm match add!(1,2), add!(2,3) etc
|
||||||
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
($name:expr, $record: expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||||
$menu = match extract_value($name, $cols, $vals, $span) {
|
$menu = match extract_value($name, $record, $span) {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let style = match text {
|
let style = match text {
|
||||||
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||||
@ -177,11 +179,12 @@ pub(crate) fn add_columnar_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let span = menu.menu_type.span();
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.into_string("", config);
|
||||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
columnar_menu = match extract_value("columns", cols, vals, span) {
|
columnar_menu = match extract_value("columns", val, span) {
|
||||||
Ok(columns) => {
|
Ok(columns) => {
|
||||||
let columns = columns.as_int()?;
|
let columns = columns.as_int()?;
|
||||||
columnar_menu.with_columns(columns as u16)
|
columnar_menu.with_columns(columns as u16)
|
||||||
@ -189,7 +192,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
Err(_) => columnar_menu,
|
Err(_) => columnar_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
columnar_menu = match extract_value("col_width", cols, vals, span) {
|
columnar_menu = match extract_value("col_width", val, span) {
|
||||||
Ok(col_width) => {
|
Ok(col_width) => {
|
||||||
let col_width = col_width.as_int()?;
|
let col_width = col_width.as_int()?;
|
||||||
columnar_menu.with_column_width(Some(col_width as usize))
|
columnar_menu.with_column_width(Some(col_width as usize))
|
||||||
@ -197,7 +200,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
Err(_) => columnar_menu.with_column_width(None),
|
Err(_) => columnar_menu.with_column_width(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
columnar_menu = match extract_value("col_padding", cols, vals, span) {
|
columnar_menu = match extract_value("col_padding", val, span) {
|
||||||
Ok(col_padding) => {
|
Ok(col_padding) => {
|
||||||
let col_padding = col_padding.as_int()?;
|
let col_padding = col_padding.as_int()?;
|
||||||
columnar_menu.with_column_padding(col_padding as usize)
|
columnar_menu.with_column_padding(col_padding as usize)
|
||||||
@ -206,11 +209,11 @@ pub(crate) fn add_columnar_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.style {
|
let span = menu.style.span();
|
||||||
|
if let Value::Record { val, .. } = &menu.style {
|
||||||
add_style!(
|
add_style!(
|
||||||
"text",
|
"text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
columnar_menu,
|
columnar_menu,
|
||||||
@ -218,8 +221,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"selected_text",
|
"selected_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
columnar_menu,
|
columnar_menu,
|
||||||
@ -227,8 +229,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"description_text",
|
"description_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
columnar_menu,
|
columnar_menu,
|
||||||
@ -242,18 +243,15 @@ pub(crate) fn add_columnar_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
let span = menu.source.span();
|
||||||
match &menu.source {
|
match &menu.source {
|
||||||
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 {
|
Value::Closure { val, captures, .. } => {
|
||||||
val,
|
|
||||||
captures,
|
|
||||||
span,
|
|
||||||
} => {
|
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
*val,
|
||||||
*span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(captures),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
@ -266,7 +264,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or omitted value".to_string(),
|
"block or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span()?,
|
span,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,8 +280,9 @@ pub(crate) fn add_list_menu(
|
|||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.into_string("", config);
|
||||||
let mut list_menu = ListMenu::default().with_name(&name);
|
let mut list_menu = ListMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
let span = menu.menu_type.span();
|
||||||
list_menu = match extract_value("page_size", cols, vals, span) {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
|
list_menu = match extract_value("page_size", val, span) {
|
||||||
Ok(page_size) => {
|
Ok(page_size) => {
|
||||||
let page_size = page_size.as_int()?;
|
let page_size = page_size.as_int()?;
|
||||||
list_menu.with_page_size(page_size as usize)
|
list_menu.with_page_size(page_size as usize)
|
||||||
@ -292,11 +291,11 @@ pub(crate) fn add_list_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.style {
|
let span = menu.style.span();
|
||||||
|
if let Value::Record { val, .. } = &menu.style {
|
||||||
add_style!(
|
add_style!(
|
||||||
"text",
|
"text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
list_menu,
|
list_menu,
|
||||||
@ -304,8 +303,7 @@ pub(crate) fn add_list_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"selected_text",
|
"selected_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
list_menu,
|
list_menu,
|
||||||
@ -313,8 +311,7 @@ pub(crate) fn add_list_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"description_text",
|
"description_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
list_menu,
|
list_menu,
|
||||||
@ -328,18 +325,15 @@ pub(crate) fn add_list_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
let span = menu.source.span();
|
||||||
match &menu.source {
|
match &menu.source {
|
||||||
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 {
|
Value::Closure { val, captures, .. } => {
|
||||||
val,
|
|
||||||
captures,
|
|
||||||
span,
|
|
||||||
} => {
|
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
*val,
|
||||||
*span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(captures),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
@ -352,7 +346,7 @@ pub(crate) fn add_list_menu(
|
|||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or omitted value".to_string(),
|
"block or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span()?,
|
menu.source.span(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,8 +362,9 @@ pub(crate) fn add_description_menu(
|
|||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.into_string("", config);
|
||||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
let span = menu.menu_type.span();
|
||||||
description_menu = match extract_value("columns", cols, vals, span) {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
|
description_menu = match extract_value("columns", val, span) {
|
||||||
Ok(columns) => {
|
Ok(columns) => {
|
||||||
let columns = columns.as_int()?;
|
let columns = columns.as_int()?;
|
||||||
description_menu.with_columns(columns as u16)
|
description_menu.with_columns(columns as u16)
|
||||||
@ -377,7 +372,7 @@ pub(crate) fn add_description_menu(
|
|||||||
Err(_) => description_menu,
|
Err(_) => description_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
description_menu = match extract_value("col_width", cols, vals, span) {
|
description_menu = match extract_value("col_width", val, span) {
|
||||||
Ok(col_width) => {
|
Ok(col_width) => {
|
||||||
let col_width = col_width.as_int()?;
|
let col_width = col_width.as_int()?;
|
||||||
description_menu.with_column_width(Some(col_width as usize))
|
description_menu.with_column_width(Some(col_width as usize))
|
||||||
@ -385,7 +380,7 @@ pub(crate) fn add_description_menu(
|
|||||||
Err(_) => description_menu.with_column_width(None),
|
Err(_) => description_menu.with_column_width(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
description_menu = match extract_value("col_padding", cols, vals, span) {
|
description_menu = match extract_value("col_padding", val, span) {
|
||||||
Ok(col_padding) => {
|
Ok(col_padding) => {
|
||||||
let col_padding = col_padding.as_int()?;
|
let col_padding = col_padding.as_int()?;
|
||||||
description_menu.with_column_padding(col_padding as usize)
|
description_menu.with_column_padding(col_padding as usize)
|
||||||
@ -393,7 +388,7 @@ pub(crate) fn add_description_menu(
|
|||||||
Err(_) => description_menu,
|
Err(_) => description_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
description_menu = match extract_value("selection_rows", cols, vals, span) {
|
description_menu = match extract_value("selection_rows", val, span) {
|
||||||
Ok(selection_rows) => {
|
Ok(selection_rows) => {
|
||||||
let selection_rows = selection_rows.as_int()?;
|
let selection_rows = selection_rows.as_int()?;
|
||||||
description_menu.with_selection_rows(selection_rows as u16)
|
description_menu.with_selection_rows(selection_rows as u16)
|
||||||
@ -401,7 +396,7 @@ pub(crate) fn add_description_menu(
|
|||||||
Err(_) => description_menu,
|
Err(_) => description_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
description_menu = match extract_value("description_rows", cols, vals, span) {
|
description_menu = match extract_value("description_rows", val, span) {
|
||||||
Ok(description_rows) => {
|
Ok(description_rows) => {
|
||||||
let description_rows = description_rows.as_int()?;
|
let description_rows = description_rows.as_int()?;
|
||||||
description_menu.with_description_rows(description_rows as usize)
|
description_menu.with_description_rows(description_rows as usize)
|
||||||
@ -410,11 +405,11 @@ pub(crate) fn add_description_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::Record { cols, vals, span } = &menu.style {
|
let span = menu.style.span();
|
||||||
|
if let Value::Record { val, .. } = &menu.style {
|
||||||
add_style!(
|
add_style!(
|
||||||
"text",
|
"text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
description_menu,
|
description_menu,
|
||||||
@ -422,8 +417,7 @@ pub(crate) fn add_description_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"selected_text",
|
"selected_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
description_menu,
|
description_menu,
|
||||||
@ -431,8 +425,7 @@ pub(crate) fn add_description_menu(
|
|||||||
);
|
);
|
||||||
add_style!(
|
add_style!(
|
||||||
"description_text",
|
"description_text",
|
||||||
cols,
|
val,
|
||||||
vals,
|
|
||||||
span,
|
span,
|
||||||
config,
|
config,
|
||||||
description_menu,
|
description_menu,
|
||||||
@ -446,6 +439,7 @@ pub(crate) fn add_description_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
let span = menu.source.span();
|
||||||
match &menu.source {
|
match &menu.source {
|
||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
let completer = Box::new(NuHelpCompleter::new(engine_state));
|
let completer = Box::new(NuHelpCompleter::new(engine_state));
|
||||||
@ -454,14 +448,10 @@ pub(crate) fn add_description_menu(
|
|||||||
completer,
|
completer,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Value::Closure {
|
Value::Closure { val, captures, .. } => {
|
||||||
val,
|
|
||||||
captures,
|
|
||||||
span,
|
|
||||||
} => {
|
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuMenuCompleter::new(
|
||||||
*val,
|
*val,
|
||||||
*span,
|
span,
|
||||||
stack.captures_to_stack(captures),
|
stack.captures_to_stack(captures),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
@ -474,7 +464,7 @@ pub(crate) fn add_description_menu(
|
|||||||
_ => Err(ShellError::UnsupportedConfigValue(
|
_ => Err(ShellError::UnsupportedConfigValue(
|
||||||
"closure or omitted value".to_string(),
|
"closure or omitted value".to_string(),
|
||||||
menu.source.into_abbreviated_string(config),
|
menu.source.into_abbreviated_string(config),
|
||||||
menu.source.span()?,
|
menu.source.span(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,6 +476,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|||||||
KeyCode::Tab,
|
KeyCode::Tab,
|
||||||
ReedlineEvent::UntilFound(vec![
|
ReedlineEvent::UntilFound(vec![
|
||||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||||
|
ReedlineEvent::MenuNext,
|
||||||
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@ -523,6 +514,12 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|||||||
KeyCode::F(1),
|
KeyCode::F(1),
|
||||||
ReedlineEvent::Menu("help_menu".to_string()),
|
ReedlineEvent::Menu("help_menu".to_string()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
keybindings.add_binding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char('q'),
|
||||||
|
ReedlineEvent::SearchHistory,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum KeybindingsMode {
|
pub enum KeybindingsMode {
|
||||||
@ -577,15 +574,16 @@ fn add_keybinding(
|
|||||||
insert_keybindings: &mut Keybindings,
|
insert_keybindings: &mut Keybindings,
|
||||||
normal_keybindings: &mut Keybindings,
|
normal_keybindings: &mut Keybindings,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
let span = mode.span();
|
||||||
match &mode {
|
match &mode {
|
||||||
Value::String { val, span } => match val.as_str() {
|
Value::String { val, .. } => match val.as_str() {
|
||||||
"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(),
|
"emacs, vi_insert or vi_normal".to_string(),
|
||||||
m.to_string(),
|
m.to_string(),
|
||||||
*span,
|
span,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -605,7 +603,7 @@ fn add_keybinding(
|
|||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue(
|
||||||
"string or list of strings".to_string(),
|
"string or list of strings".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
v.into_abbreviated_string(config),
|
||||||
v.span()?,
|
v.span(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,7 +633,7 @@ fn add_parsed_keybinding(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"CONTROL, SHIFT, ALT or NONE".to_string(),
|
"CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||||
keybinding.modifier.into_abbreviated_string(config),
|
keybinding.modifier.into_abbreviated_string(config),
|
||||||
keybinding.modifier.span()?,
|
keybinding.modifier.span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -659,7 +657,7 @@ fn add_parsed_keybinding(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"char_<CHAR: unicode codepoint>".to_string(),
|
"char_<CHAR: unicode codepoint>".to_string(),
|
||||||
c.to_string(),
|
c.to_string(),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -686,7 +684,7 @@ fn add_parsed_keybinding(
|
|||||||
.ok_or(ShellError::UnsupportedConfigValue(
|
.ok_or(ShellError::UnsupportedConfigValue(
|
||||||
"(f1|f2|...|f20)".to_string(),
|
"(f1|f2|...|f20)".to_string(),
|
||||||
format!("unknown function key: {c}"),
|
format!("unknown function key: {c}"),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span(),
|
||||||
))?;
|
))?;
|
||||||
KeyCode::F(fn_num)
|
KeyCode::F(fn_num)
|
||||||
}
|
}
|
||||||
@ -696,7 +694,7 @@ fn add_parsed_keybinding(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"crossterm KeyCode".to_string(),
|
"crossterm KeyCode".to_string(),
|
||||||
keybinding.keycode.into_abbreviated_string(config),
|
keybinding.keycode.into_abbreviated_string(config),
|
||||||
keybinding.keycode.span()?,
|
keybinding.keycode.span(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -716,27 +714,22 @@ enum EventType<'config> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'config> EventType<'config> {
|
impl<'config> EventType<'config> {
|
||||||
fn try_from_columns(
|
fn try_from_record(record: &'config Record, span: Span) -> Result<Self, ShellError> {
|
||||||
cols: &'config [String],
|
extract_value("send", record, span)
|
||||||
vals: &'config [Value],
|
|
||||||
span: &'config Span,
|
|
||||||
) -> Result<Self, ShellError> {
|
|
||||||
extract_value("send", cols, vals, span)
|
|
||||||
.map(Self::Send)
|
.map(Self::Send)
|
||||||
.or_else(|_| extract_value("edit", cols, vals, span).map(Self::Edit))
|
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
||||||
.or_else(|_| extract_value("until", cols, vals, 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("send, edit or until".to_string(), span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>, ShellError> {
|
fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>, ShellError> {
|
||||||
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::Record { cols, vals, span } => {
|
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
||||||
match EventType::try_from_columns(cols, vals, 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_lowercase().as_str(),
|
||||||
cols,
|
record,
|
||||||
vals,
|
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
@ -744,8 +737,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
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_lowercase().as_str(),
|
||||||
cols,
|
record,
|
||||||
vals,
|
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
)?;
|
)?;
|
||||||
@ -760,7 +752,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
None => Err(ShellError::UnsupportedConfigValue(
|
None => Err(ShellError::UnsupportedConfigValue(
|
||||||
"List containing valid events".to_string(),
|
"List containing valid events".to_string(),
|
||||||
"Nothing value (null)".to_string(),
|
"Nothing value (null)".to_string(),
|
||||||
value.span()?,
|
value.span(),
|
||||||
)),
|
)),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
},
|
},
|
||||||
@ -773,11 +765,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue(
|
||||||
"list of events".to_string(),
|
"list of events".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
v.into_abbreviated_string(config),
|
||||||
v.span()?,
|
v.span(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let events = vals
|
let events = vals
|
||||||
.iter()
|
.iter()
|
||||||
@ -786,7 +777,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
None => Err(ShellError::UnsupportedConfigValue(
|
None => Err(ShellError::UnsupportedConfigValue(
|
||||||
"List containing valid events".to_string(),
|
"List containing valid events".to_string(),
|
||||||
"Nothing value (null)".to_string(),
|
"Nothing value (null)".to_string(),
|
||||||
value.span()?,
|
value.span(),
|
||||||
)),
|
)),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
},
|
},
|
||||||
@ -800,17 +791,16 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
v => Err(ShellError::UnsupportedConfigValue(
|
v => Err(ShellError::UnsupportedConfigValue(
|
||||||
"record or list of records, null to unbind key".to_string(),
|
"record or list of records, null to unbind key".to_string(),
|
||||||
v.into_abbreviated_string(config),
|
v.into_abbreviated_string(config),
|
||||||
v.span()?,
|
v.span(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_from_record(
|
fn event_from_record(
|
||||||
name: &str,
|
name: &str,
|
||||||
cols: &[String],
|
record: &Record,
|
||||||
vals: &[Value],
|
|
||||||
config: &Config,
|
config: &Config,
|
||||||
span: &Span,
|
span: Span,
|
||||||
) -> Result<ReedlineEvent, ShellError> {
|
) -> Result<ReedlineEvent, ShellError> {
|
||||||
let event = match name {
|
let event = match name {
|
||||||
"none" => ReedlineEvent::None,
|
"none" => ReedlineEvent::None,
|
||||||
@ -842,18 +832,18 @@ fn event_from_record(
|
|||||||
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
|
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
|
||||||
"openeditor" => ReedlineEvent::OpenEditor,
|
"openeditor" => ReedlineEvent::OpenEditor,
|
||||||
"menu" => {
|
"menu" => {
|
||||||
let menu = extract_value("name", cols, vals, span)?;
|
let menu = extract_value("name", record, span)?;
|
||||||
ReedlineEvent::Menu(menu.into_string("", config))
|
ReedlineEvent::Menu(menu.into_string("", config))
|
||||||
}
|
}
|
||||||
"executehostcommand" => {
|
"executehostcommand" => {
|
||||||
let cmd = extract_value("cmd", cols, vals, span)?;
|
let cmd = extract_value("cmd", record, span)?;
|
||||||
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(),
|
"Reedline event".to_string(),
|
||||||
v.to_string(),
|
v.to_string(),
|
||||||
*span,
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -863,10 +853,9 @@ fn event_from_record(
|
|||||||
|
|
||||||
fn edit_from_record(
|
fn edit_from_record(
|
||||||
name: &str,
|
name: &str,
|
||||||
cols: &[String],
|
record: &Record,
|
||||||
vals: &[Value],
|
|
||||||
config: &Config,
|
config: &Config,
|
||||||
span: &Span,
|
span: Span,
|
||||||
) -> Result<EditCommand, ShellError> {
|
) -> Result<EditCommand, ShellError> {
|
||||||
let edit = match name {
|
let edit = match name {
|
||||||
"movetostart" => EditCommand::MoveToStart,
|
"movetostart" => EditCommand::MoveToStart,
|
||||||
@ -883,16 +872,16 @@ fn edit_from_record(
|
|||||||
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
||||||
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
||||||
"movetoposition" => {
|
"movetoposition" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
EditCommand::MoveToPosition(value.as_int()? as usize)
|
EditCommand::MoveToPosition(value.as_int()? as usize)
|
||||||
}
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::InsertChar(char)
|
EditCommand::InsertChar(char)
|
||||||
}
|
}
|
||||||
"insertstring" => {
|
"insertstring" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
EditCommand::InsertString(value.into_string("", config))
|
EditCommand::InsertString(value.into_string("", config))
|
||||||
}
|
}
|
||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
@ -924,42 +913,42 @@ fn edit_from_record(
|
|||||||
"undo" => EditCommand::Undo,
|
"undo" => EditCommand::Undo,
|
||||||
"redo" => EditCommand::Redo,
|
"redo" => EditCommand::Redo,
|
||||||
"cutrightuntil" => {
|
"cutrightuntil" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::CutRightUntil(char)
|
EditCommand::CutRightUntil(char)
|
||||||
}
|
}
|
||||||
"cutrightbefore" => {
|
"cutrightbefore" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::CutRightBefore(char)
|
EditCommand::CutRightBefore(char)
|
||||||
}
|
}
|
||||||
"moverightuntil" => {
|
"moverightuntil" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveRightUntil(char)
|
EditCommand::MoveRightUntil(char)
|
||||||
}
|
}
|
||||||
"moverightbefore" => {
|
"moverightbefore" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveRightBefore(char)
|
EditCommand::MoveRightBefore(char)
|
||||||
}
|
}
|
||||||
"cutleftuntil" => {
|
"cutleftuntil" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::CutLeftUntil(char)
|
EditCommand::CutLeftUntil(char)
|
||||||
}
|
}
|
||||||
"cutleftbefore" => {
|
"cutleftbefore" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::CutLeftBefore(char)
|
EditCommand::CutLeftBefore(char)
|
||||||
}
|
}
|
||||||
"moveleftuntil" => {
|
"moveleftuntil" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftUntil(char)
|
EditCommand::MoveLeftUntil(char)
|
||||||
}
|
}
|
||||||
"moveleftbefore" => {
|
"moveleftbefore" => {
|
||||||
let value = extract_value("value", cols, vals, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftBefore(char)
|
EditCommand::MoveLeftBefore(char)
|
||||||
}
|
}
|
||||||
@ -968,7 +957,7 @@ fn edit_from_record(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"reedline EditCommand".to_string(),
|
"reedline EditCommand".to_string(),
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
*span,
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -977,7 +966,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||||
let span = value.span()?;
|
let span = value.span();
|
||||||
value
|
value
|
||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.chars()
|
.chars()
|
||||||
@ -993,16 +982,13 @@ mod test {
|
|||||||
fn test_send_event() {
|
fn test_send_event() {
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::test_string("Enter")];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
let event = Record { vals, cols };
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
assert!(matches!(b, EventType::Send(_)));
|
assert!(matches!(b, EventType::Send(_)));
|
||||||
|
|
||||||
let event = Value::Record {
|
let event = Value::test_record(event);
|
||||||
vals,
|
|
||||||
cols,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
||||||
let parsed_event = parse_event(&event, &config).unwrap();
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
@ -1013,16 +999,13 @@ mod test {
|
|||||||
fn test_edit_event() {
|
fn test_edit_event() {
|
||||||
let cols = vec!["edit".to_string()];
|
let cols = vec!["edit".to_string()];
|
||||||
let vals = vec![Value::test_string("Clear")];
|
let vals = vec![Value::test_string("Clear")];
|
||||||
|
let event = Record { vals, cols };
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
assert!(matches!(b, EventType::Edit(_)));
|
assert!(matches!(b, EventType::Edit(_)));
|
||||||
|
|
||||||
let event = Value::Record {
|
let event = Value::test_record(event);
|
||||||
vals,
|
|
||||||
cols,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
||||||
let parsed_event = parse_event(&event, &config).unwrap();
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
@ -1039,16 +1022,13 @@ mod test {
|
|||||||
Value::test_string("Menu"),
|
Value::test_string("Menu"),
|
||||||
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_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
assert!(matches!(b, EventType::Send(_)));
|
assert!(matches!(b, EventType::Send(_)));
|
||||||
|
|
||||||
let event = Value::Record {
|
let event = Value::test_record(event);
|
||||||
vals,
|
|
||||||
cols,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
||||||
let parsed_event = parse_event(&event, &config).unwrap();
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
@ -1067,38 +1047,27 @@ mod test {
|
|||||||
Value::test_string("history_menu"),
|
Value::test_string("history_menu"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::test_record(Record { cols, vals });
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::test_string("Enter")];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::test_record(Record { cols, vals });
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Until event
|
// Until event
|
||||||
let cols = vec!["until".to_string()];
|
let cols = vec!["until".to_string()];
|
||||||
let vals = vec![Value::List {
|
let vals = vec![Value::list(
|
||||||
vals: vec![menu_event, enter_event],
|
vec![menu_event, enter_event],
|
||||||
span: 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_columns(&cols, &vals, &span).unwrap();
|
let b = EventType::try_from_record(&event, span).unwrap();
|
||||||
assert!(matches!(b, EventType::Until(_)));
|
assert!(matches!(b, EventType::Until(_)));
|
||||||
|
|
||||||
let event = Value::Record {
|
let event = Value::test_record(event);
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
||||||
let parsed_event = parse_event(&event, &config).unwrap();
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
@ -1120,27 +1089,16 @@ mod test {
|
|||||||
Value::test_string("history_menu"),
|
Value::test_string("history_menu"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let menu_event = Value::Record {
|
let menu_event = Value::test_record(Record { cols, vals });
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enter event
|
// Enter event
|
||||||
let cols = vec!["send".to_string()];
|
let cols = vec!["send".to_string()];
|
||||||
let vals = vec![Value::test_string("Enter")];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
|
||||||
let enter_event = Value::Record {
|
let enter_event = Value::test_record(Record { cols, vals });
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Multiple event
|
// Multiple event
|
||||||
let event = Value::List {
|
let event = Value::list(vec![menu_event, enter_event], Span::test_data());
|
||||||
vals: vec![menu_event, enter_event],
|
|
||||||
span: Span::test_data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let parsed_event = parse_event(&event, &config).unwrap();
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
@ -1157,9 +1115,10 @@ mod test {
|
|||||||
fn test_error() {
|
fn test_error() {
|
||||||
let cols = vec!["not_exist".to_string()];
|
let cols = vec!["not_exist".to_string()];
|
||||||
let vals = vec![Value::test_string("Enter")];
|
let vals = vec![Value::test_string("Enter")];
|
||||||
|
let event = Record { cols, vals };
|
||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
let b = EventType::try_from_record(&event, span);
|
||||||
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
|
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,19 +6,19 @@ use crate::{
|
|||||||
NuHighlighter, NuValidator, NushellPrompt,
|
NuHighlighter, NuValidator, NushellPrompt,
|
||||||
};
|
};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use is_terminal::IsTerminal;
|
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
use miette::{ErrReport, IntoDiagnostic, Result};
|
use miette::{ErrReport, IntoDiagnostic, Result};
|
||||||
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_command::hook::eval_hook;
|
|
||||||
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};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
eval_const::create_nu_constant,
|
||||||
report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||||
Value,
|
Value, NU_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use reedline::{
|
use reedline::{
|
||||||
@ -26,7 +26,8 @@ use reedline::{
|
|||||||
SqliteBackedHistory, Vi,
|
SqliteBackedHistory, Vi,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
env::temp_dir,
|
||||||
|
io::{self, IsTerminal, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
@ -51,7 +52,7 @@ pub fn evaluate_repl(
|
|||||||
load_std_lib: Option<Spanned<String>>,
|
load_std_lib: Option<Spanned<String>>,
|
||||||
entire_start_time: Instant,
|
entire_start_time: Instant,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use nu_command::hook;
|
use nu_cmd_base::hook;
|
||||||
use reedline::Signal;
|
use reedline::Signal;
|
||||||
let use_color = engine_state.get_config().use_ansi_coloring;
|
let use_color = engine_state.get_config().use_ansi_coloring;
|
||||||
|
|
||||||
@ -94,6 +95,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);
|
||||||
@ -165,6 +167,10 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||||
|
|
||||||
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
||||||
|
let nu_const = create_nu_constant(engine_state, Span::unknown())?;
|
||||||
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
||||||
|
|
||||||
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
@ -176,6 +182,14 @@ pub fn evaluate_repl(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if engine_state.get_config().use_kitty_protocol {
|
||||||
|
if line_editor.can_use_kitty_protocol() {
|
||||||
|
line_editor.enable_kitty_protocol();
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
|
||||||
@ -231,13 +245,15 @@ 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: Some(map_nucursorshape_to_cursorshape(
|
vi_insert: config
|
||||||
config.cursor_shape_vi_insert,
|
.cursor_shape_vi_insert
|
||||||
)),
|
.map(map_nucursorshape_to_cursorshape),
|
||||||
vi_normal: Some(map_nucursorshape_to_cursorshape(
|
vi_normal: config
|
||||||
config.cursor_shape_vi_normal,
|
.cursor_shape_vi_normal
|
||||||
)),
|
.map(map_nucursorshape_to_cursorshape),
|
||||||
emacs: Some(map_nucursorshape_to_cursorshape(config.cursor_shape_emacs)),
|
emacs: config
|
||||||
|
.cursor_shape_emacs
|
||||||
|
.map(map_nucursorshape_to_cursorshape),
|
||||||
};
|
};
|
||||||
perf(
|
perf(
|
||||||
"get config/cursor config",
|
"get config/cursor config",
|
||||||
@ -265,7 +281,11 @@ pub fn evaluate_repl(
|
|||||||
.with_quick_completions(config.quick_completions)
|
.with_quick_completions(config.quick_completions)
|
||||||
.with_partial_completions(config.partial_completions)
|
.with_partial_completions(config.partial_completions)
|
||||||
.with_ansi_colors(config.use_ansi_coloring)
|
.with_ansi_colors(config.use_ansi_coloring)
|
||||||
.with_cursor_config(cursor_config);
|
.with_cursor_config(cursor_config)
|
||||||
|
.with_transient_prompt(prompt_update::transient_prompt(
|
||||||
|
engine_reference.clone(),
|
||||||
|
stack,
|
||||||
|
));
|
||||||
perf(
|
perf(
|
||||||
"reedline builder",
|
"reedline builder",
|
||||||
start_time,
|
start_time,
|
||||||
@ -312,23 +332,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
|
||||||
};
|
};
|
||||||
@ -391,7 +405,7 @@ pub fn evaluate_repl(
|
|||||||
// Right before we start our prompt and take input from the user,
|
// Right before we start our prompt and take input from the user,
|
||||||
// fire the "pre_prompt" hook
|
// fire the "pre_prompt" hook
|
||||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook, "pre_prompt") {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,7 +480,9 @@ pub fn evaluate_repl(
|
|||||||
repl.buffer = s.to_string();
|
repl.buffer = s.to_string();
|
||||||
drop(repl);
|
drop(repl);
|
||||||
|
|
||||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
if let Err(err) =
|
||||||
|
eval_hook(engine_state, stack, None, vec![], &hook, "pre_execution")
|
||||||
|
{
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,7 +516,10 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
report_error(
|
report_error(
|
||||||
&working_set,
|
&working_set,
|
||||||
&ShellError::DirectoryNotFound(tokens.0[0].span, None),
|
&ShellError::DirectoryNotFound(
|
||||||
|
tokens.0[0].span,
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let path = nu_path::canonicalize_with(path, &cwd)
|
let path = nu_path::canonicalize_with(path, &cwd)
|
||||||
@ -508,24 +527,12 @@ pub fn evaluate_repl(
|
|||||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||||
};
|
};
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
|
||||||
"OLDPWD".into(),
|
|
||||||
Value::String {
|
|
||||||
val: cwd.clone(),
|
|
||||||
span: Span::unknown(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
//FIXME: this only changes the current scope, but instead this environment variable
|
//FIXME: this only changes the current scope, but instead this environment variable
|
||||||
//should probably be a block that loads the information from the state in the overlay
|
//should probably be a block that loads the information from the state in the overlay
|
||||||
stack.add_env_var(
|
stack.add_env_var("PWD".into(), Value::string(path.clone(), Span::unknown()));
|
||||||
"PWD".into(),
|
let cwd = Value::string(cwd, span);
|
||||||
Value::String {
|
|
||||||
val: path.clone(),
|
|
||||||
span: Span::unknown(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let cwd = Value::String { val: cwd, span };
|
|
||||||
|
|
||||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||||
let mut shells = if let Some(v) = shells {
|
let mut shells = if let Some(v) = shells {
|
||||||
@ -550,15 +557,12 @@ pub fn evaluate_repl(
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
shells[current_shell] = Value::String { val: path, span };
|
shells[current_shell] = Value::string(path, span);
|
||||||
|
|
||||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
|
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"NUSHELL_LAST_SHELL".into(),
|
"NUSHELL_LAST_SHELL".into(),
|
||||||
Value::Int {
|
Value::int(last_shell as i64, span),
|
||||||
val: last_shell as i64,
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} else if !s.trim().is_empty() {
|
} else if !s.trim().is_empty() {
|
||||||
trace!("eval source: {}", s);
|
trace!("eval source: {}", s);
|
||||||
@ -601,10 +605,7 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
Value::String {
|
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
|
||||||
val: format!("{}", cmd_duration.as_millis()),
|
|
||||||
span: Span::unknown(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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()
|
||||||
@ -740,9 +741,14 @@ fn update_line_editor_history(
|
|||||||
)
|
)
|
||||||
.into_diagnostic()?,
|
.into_diagnostic()?,
|
||||||
),
|
),
|
||||||
HistoryFileFormat::Sqlite => {
|
HistoryFileFormat::Sqlite => Box::new(
|
||||||
Box::new(SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?)
|
SqliteBackedHistory::with_file(
|
||||||
}
|
history_path.to_path_buf(),
|
||||||
|
history_session_id,
|
||||||
|
Some(chrono::Utc::now()),
|
||||||
|
)
|
||||||
|
.into_diagnostic()?,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let line_editor = line_editor
|
let line_editor = line_editor
|
||||||
.with_history_session_id(history_session_id)
|
.with_history_session_id(history_session_id)
|
||||||
@ -814,16 +820,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]
|
||||||
|
@ -59,7 +59,7 @@ impl Highlighter for NuHighlighter {
|
|||||||
($shape:expr, $span:expr, $text:expr) => {{
|
($shape:expr, $span:expr, $text:expr) => {{
|
||||||
let spans = split_span_by_highlight_positions(
|
let spans = split_span_by_highlight_positions(
|
||||||
line,
|
line,
|
||||||
&$span,
|
$span,
|
||||||
&matching_brackets_pos,
|
&matching_brackets_pos,
|
||||||
global_span_offset,
|
global_span_offset,
|
||||||
);
|
);
|
||||||
@ -143,8 +143,8 @@ impl Highlighter for NuHighlighter {
|
|||||||
|
|
||||||
fn split_span_by_highlight_positions(
|
fn split_span_by_highlight_positions(
|
||||||
line: &str,
|
line: &str,
|
||||||
span: &Span,
|
span: Span,
|
||||||
highlight_positions: &Vec<usize>,
|
highlight_positions: &[usize],
|
||||||
global_span_offset: usize,
|
global_span_offset: usize,
|
||||||
) -> Vec<(Span, bool)> {
|
) -> Vec<(Span, bool)> {
|
||||||
let mut start = span.start;
|
let mut start = span.start;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use nu_command::hook::eval_hook;
|
use nu_cmd_base::hook::eval_hook;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
@ -105,7 +105,7 @@ fn gather_env_vars(
|
|||||||
span: full_span,
|
span: full_span,
|
||||||
} = token
|
} = token
|
||||||
{
|
{
|
||||||
let contents = engine_state.get_span_contents(&full_span);
|
let contents = engine_state.get_span_contents(full_span);
|
||||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||||
|
|
||||||
let name = if let Some(Token {
|
let name = if let Some(Token {
|
||||||
@ -185,10 +185,7 @@ fn gather_env_vars(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::String {
|
Value::string(bytes, *span)
|
||||||
val: bytes,
|
|
||||||
span: *span,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
report_capture_error(
|
report_capture_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
@ -257,7 +254,14 @@ pub fn eval_source(
|
|||||||
{
|
{
|
||||||
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
||||||
} else if let Some(hook) = config.hooks.display_output.clone() {
|
} else if let Some(hook) = config.hooks.display_output.clone() {
|
||||||
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
|
match eval_hook(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
Some(pipeline_data),
|
||||||
|
vec![],
|
||||||
|
&hook,
|
||||||
|
"display_output",
|
||||||
|
) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
result = Err(err);
|
result = Err(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 {
|
||||||
@ -201,6 +206,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();
|
||||||
@ -445,6 +531,18 @@ fn file_completion_quoted() {
|
|||||||
"`te#st.txt`".to_string(),
|
"`te#st.txt`".to_string(),
|
||||||
"`te'st.txt`".to_string(),
|
"`te'st.txt`".to_string(),
|
||||||
"`te(st).txt`".to_string(),
|
"`te(st).txt`".to_string(),
|
||||||
|
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)
|
||||||
|
@ -4,7 +4,8 @@ use nu_engine::eval_block;
|
|||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, ShellError, Span, Value,
|
eval_const::create_nu_constant,
|
||||||
|
PipelineData, ShellError, Span, Value, NU_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs;
|
use nu_test_support::fs;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
@ -28,39 +29,41 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
// Create a new engine with default context
|
// Create a new engine with default context
|
||||||
let mut engine_state = create_default_context();
|
let mut engine_state = create_default_context();
|
||||||
|
|
||||||
|
// Add $nu
|
||||||
|
let nu_const =
|
||||||
|
create_nu_constant(&engine_state, Span::test_data()).expect("Failed creating $nu");
|
||||||
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
||||||
|
|
||||||
// New stack
|
// New stack
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
// Add pwd as env var
|
// Add pwd as env var
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
Value::String {
|
Value::string(dir_str.clone(), nu_protocol::Span::new(0, dir_str.len())),
|
||||||
val: dir_str.clone(),
|
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"TEST".to_string(),
|
"TEST".to_string(),
|
||||||
Value::String {
|
Value::string(
|
||||||
val: "NUSHELL".to_string(),
|
"NUSHELL".to_string(),
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"Path".to_string(),
|
"Path".to_string(),
|
||||||
Value::String {
|
Value::string(
|
||||||
val: "c:\\some\\path;c:\\some\\other\\path".to_string(),
|
"c:\\some\\path;c:\\some\\other\\path".to_string(),
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PATH".to_string(),
|
"PATH".to_string(),
|
||||||
Value::String {
|
Value::string(
|
||||||
val: "/some/path:/some/other/path".to_string(),
|
"/some/path:/some/other/path".to_string(),
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
nu_protocol::Span::new(0, dir_str.len()),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
@ -89,17 +92,50 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
|||||||
// Add pwd as env var
|
// Add pwd as env var
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"PWD".to_string(),
|
"PWD".to_string(),
|
||||||
Value::String {
|
Value::string(dir_str.clone(), nu_protocol::Span::new(0, dir_str.len())),
|
||||||
val: dir_str.clone(),
|
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"TEST".to_string(),
|
"TEST".to_string(),
|
||||||
Value::String {
|
Value::string(
|
||||||
val: "NUSHELL".to_string(),
|
"NUSHELL".to_string(),
|
||||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Merge environment into the permanent state
|
||||||
@ -162,12 +198,7 @@ pub fn merge_input(
|
|||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&block,
|
&block,
|
||||||
PipelineData::Value(
|
PipelineData::Value(Value::nothing(Span::unknown(),), None),
|
||||||
Value::Nothing {
|
|
||||||
span: Span::unknown(),
|
|
||||||
},
|
|
||||||
None
|
|
||||||
),
|
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
@ -5,12 +5,14 @@ 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.83.0"
|
version = "0.86.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.83.0" }
|
nu-engine = { path = "../nu-engine", version = "0.86.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.83.0" }
|
nu-parser = { path = "../nu-parser", version = "0.86.0" }
|
||||||
nu-protocol = { version = "0.83.0", path = "../nu-protocol" }
|
nu-path = { path = "../nu-path", version = "0.86.0" }
|
||||||
|
nu-protocol = { version = "0.86.0", path = "../nu-protocol" }
|
||||||
indexmap = { version = "2.0" }
|
indexmap = { version = "2.0" }
|
||||||
|
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||||
|
@ -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 { cols, .. } => cols.to_owned(),
|
Value::Record { val, .. } => val.cols.clone(),
|
||||||
_ => vec!["".to_string()],
|
_ => vec!["".to_string()],
|
||||||
};
|
};
|
||||||
for desc in data_descriptors {
|
for desc in data_descriptors {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::util::get_guaranteed_cwd;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_cmd_base::util::get_guaranteed_cwd;
|
|
||||||
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::ast::PathMember;
|
||||||
@ -14,12 +14,8 @@ pub fn eval_env_change_hook(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
if let Some(hook) = env_change_hook {
|
if let Some(hook) = env_change_hook {
|
||||||
match hook {
|
match hook {
|
||||||
Value::Record {
|
Value::Record { val, .. } => {
|
||||||
cols: env_names,
|
for (env_name, hook_value) in &val {
|
||||||
vals: hook_values,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
|
||||||
let before = engine_state
|
let before = engine_state
|
||||||
.previous_env_vars
|
.previous_env_vars
|
||||||
.get(env_name)
|
.get(env_name)
|
||||||
@ -37,6 +33,7 @@ pub fn eval_env_change_hook(
|
|||||||
None,
|
None,
|
||||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||||
hook_value,
|
hook_value,
|
||||||
|
"env_change",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
engine_state
|
engine_state
|
||||||
@ -48,7 +45,7 @@ pub fn eval_env_change_hook(
|
|||||||
x => {
|
x => {
|
||||||
return Err(ShellError::TypeMismatch {
|
return Err(ShellError::TypeMismatch {
|
||||||
err_message: "record for the 'env_change' hook".to_string(),
|
err_message: "record for the 'env_change' hook".to_string(),
|
||||||
span: x.span()?,
|
span: x.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,8 +60,9 @@ pub fn eval_hook(
|
|||||||
input: Option<PipelineData>,
|
input: Option<PipelineData>,
|
||||||
arguments: Vec<(String, Value)>,
|
arguments: Vec<(String, Value)>,
|
||||||
value: &Value,
|
value: &Value,
|
||||||
|
hook_name: &str,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let value_span = value.span()?;
|
let value_span = value.span();
|
||||||
|
|
||||||
// Hooks can optionally be a record in this form:
|
// Hooks can optionally be a record in this form:
|
||||||
// {
|
// {
|
||||||
@ -86,33 +84,97 @@ pub fn eval_hook(
|
|||||||
optional: false,
|
optional: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
let (block, delta, vars) = {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||||
|
|
||||||
|
for (name, val) in arguments {
|
||||||
|
let var_id = working_set.add_variable(
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
val.span(),
|
||||||
|
Type::Any,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
vars.push((var_id, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = parse(
|
||||||
|
&mut working_set,
|
||||||
|
Some(&format!("{hook_name} hook")),
|
||||||
|
val.as_bytes(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
|
report_error(&working_set, err);
|
||||||
|
|
||||||
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
|
"valid source code".into(),
|
||||||
|
"source code with syntax errors".into(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, working_set.render(), vars)
|
||||||
|
};
|
||||||
|
|
||||||
|
engine_state.merge_delta(delta)?;
|
||||||
|
let input = if let Some(input) = input {
|
||||||
|
input
|
||||||
|
} else {
|
||||||
|
PipelineData::empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
let var_ids: Vec<VarId> = vars
|
||||||
|
.into_iter()
|
||||||
|
.map(|(var_id, val)| {
|
||||||
|
stack.add_var(var_id, val);
|
||||||
|
var_id
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
|
Ok(pipeline_data) => {
|
||||||
|
output = pipeline_data;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
report_error_new(engine_state, &err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for var_id in var_ids.iter() {
|
||||||
|
stack.remove_var(*var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for val in vals {
|
for val in vals {
|
||||||
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
|
eval_hook(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
None,
|
||||||
|
arguments.clone(),
|
||||||
|
val,
|
||||||
|
&format!("{hook_name} list, recursive"),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
let do_run_hook =
|
let do_run_hook = if let Ok(condition) =
|
||||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
value.clone().follow_cell_path(&[condition_path], false)
|
||||||
|
{
|
||||||
|
let other_span = condition.span();
|
||||||
match condition {
|
match condition {
|
||||||
Value::Block {
|
Value::Block { val: block_id, .. } | Value::Closure { val: block_id, .. } => {
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Value::Closure {
|
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
match run_hook_block(
|
match run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
block_id,
|
||||||
None,
|
None,
|
||||||
arguments.clone(),
|
arguments.clone(),
|
||||||
block_span,
|
other_span,
|
||||||
) {
|
) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
|
||||||
@ -123,7 +185,7 @@ pub fn eval_hook(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"boolean output".to_string(),
|
"boolean output".to_string(),
|
||||||
"other PipelineData variant".to_string(),
|
"other PipelineData variant".to_string(),
|
||||||
block_span,
|
other_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +198,7 @@ pub fn eval_hook(
|
|||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"block".to_string(),
|
"block".to_string(),
|
||||||
format!("{}", other.get_type()),
|
format!("{}", other.get_type()),
|
||||||
other.span()?,
|
other_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,11 +208,10 @@ pub fn eval_hook(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if do_run_hook {
|
if do_run_hook {
|
||||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
let follow = value.clone().follow_cell_path(&[code_path], false)?;
|
||||||
Value::String {
|
let source_span = follow.span();
|
||||||
val,
|
match follow {
|
||||||
span: source_span,
|
Value::String { val, .. } => {
|
||||||
} => {
|
|
||||||
let (block, delta, vars) = {
|
let (block, delta, vars) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
@ -159,16 +220,19 @@ pub fn eval_hook(
|
|||||||
for (name, val) in arguments {
|
for (name, val) in arguments {
|
||||||
let var_id = working_set.add_variable(
|
let var_id = working_set.add_variable(
|
||||||
name.as_bytes().to_vec(),
|
name.as_bytes().to_vec(),
|
||||||
val.span()?,
|
val.span(),
|
||||||
Type::Any,
|
Type::Any,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
vars.push((var_id, val));
|
vars.push((var_id, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
let output =
|
let output = parse(
|
||||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false);
|
&mut working_set,
|
||||||
|
Some(&format!("{hook_name} hook")),
|
||||||
|
val.as_bytes(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
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);
|
||||||
|
|
||||||
@ -206,77 +270,47 @@ pub fn eval_hook(
|
|||||||
stack.remove_var(*var_id);
|
stack.remove_var(*var_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Block { val: block_id, .. } => {
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
run_hook_block(
|
run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
block_id,
|
||||||
input,
|
input,
|
||||||
arguments,
|
arguments,
|
||||||
block_span,
|
source_span,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Value::Closure {
|
Value::Closure { val: block_id, .. } => {
|
||||||
val: block_id,
|
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
run_hook_block(
|
run_hook_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
block_id,
|
||||||
input,
|
input,
|
||||||
arguments,
|
arguments,
|
||||||
block_span,
|
source_span,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"block or string".to_string(),
|
"block or string".to_string(),
|
||||||
format!("{}", other.get_type()),
|
format!("{}", other.get_type()),
|
||||||
other.span()?,
|
source_span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Block {
|
Value::Block { val: block_id, .. } => {
|
||||||
val: block_id,
|
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
output = run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
*block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
*block_span,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Value::Closure {
|
Value::Closure { val: block_id, .. } => {
|
||||||
val: block_id,
|
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
||||||
span: block_span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
output = run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
*block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
*block_span,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"block, record, or list of records".into(),
|
"string, block, record, or list of commands".into(),
|
||||||
format!("{}", other.get_type()),
|
format!("{}", other.get_type()),
|
||||||
other.span()?,
|
other.span(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,7 +333,7 @@ fn run_hook_block(
|
|||||||
|
|
||||||
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
||||||
|
|
||||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
let mut callee_stack = stack.gather_captures(engine_state, &block.captures);
|
||||||
|
|
||||||
for (idx, PositionalArg { var_id, .. }) in
|
for (idx, PositionalArg { var_id, .. }) in
|
||||||
block.signature.required_positional.iter().enumerate()
|
block.signature.required_positional.iter().enumerate()
|
||||||
@ -319,7 +353,7 @@ fn run_hook_block(
|
|||||||
let pipeline_data =
|
let pipeline_data =
|
||||||
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
||||||
|
|
||||||
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
|
if let PipelineData::Value(Value::Error { error, .. }, _) = pipeline_data {
|
||||||
return Err(*error);
|
return Err(*error);
|
||||||
}
|
}
|
||||||
|
|
@ -76,9 +76,7 @@ where
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error {
|
return Value::error(error, span);
|
||||||
error: Box::new(error),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub mod formats;
|
pub mod formats;
|
||||||
|
pub mod hook;
|
||||||
pub mod input_handler;
|
pub mod input_handler;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -55,3 +55,72 @@ 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(
|
||||||
|
"Editor executable is missing".into(),
|
||||||
|
"Set the first element to an executable".into(),
|
||||||
|
Some(value.span()),
|
||||||
|
Some(HELP_MSG.into()),
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String { .. } | Value::List { .. } => Err(ShellError::GenericError(
|
||||||
|
format!("{var_name} should be a non-empty string or list<String>"),
|
||||||
|
"Specify an executable here".into(),
|
||||||
|
Some(value.span()),
|
||||||
|
Some(HELP_MSG.into()),
|
||||||
|
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(
|
||||||
|
"No editor configured".into(),
|
||||||
|
"Please specify one via `$env.config.buffer_editor` or `$env.EDITOR`/`$env.VISUAL`"
|
||||||
|
.into(),
|
||||||
|
Some(span),
|
||||||
|
Some(HELP_MSG.into()),
|
||||||
|
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.83.0"
|
version = "0.86.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,9 +13,9 @@ version = "0.83.0"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.83.0" }
|
nu-engine = { path = "../nu-engine", version = "0.86.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.83.0" }
|
nu-parser = { path = "../nu-parser", version = "0.86.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.83.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.86.0" }
|
||||||
|
|
||||||
# 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 }
|
||||||
@ -23,7 +23,8 @@ fancy-regex = "0.11"
|
|||||||
indexmap = { version = "2.0" }
|
indexmap = { version = "2.0" }
|
||||||
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.34", features = ["serde"], optional = true }
|
sqlparser = { version = "0.36.1", optional = true }
|
||||||
|
polars-io = { version = "0.33", features = ["avro"], optional = true }
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
features = [
|
features = [
|
||||||
@ -37,7 +38,7 @@ features = [
|
|||||||
"dtype-categorical",
|
"dtype-categorical",
|
||||||
"dtype-datetime",
|
"dtype-datetime",
|
||||||
"dtype-struct",
|
"dtype-struct",
|
||||||
"dynamic_groupby",
|
"dynamic_group_by",
|
||||||
"ipc",
|
"ipc",
|
||||||
"is_in",
|
"is_in",
|
||||||
"json",
|
"json",
|
||||||
@ -53,12 +54,12 @@ features = [
|
|||||||
"to_dummies",
|
"to_dummies",
|
||||||
]
|
]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.30.0"
|
version = "0.33"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dataframe = ["default"]
|
dataframe = ["num", "polars", "polars-io", "sqlparser"]
|
||||||
default = ["num", "polars", "sqlparser"]
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.86.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.83.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.86.0" }
|
||||||
|
@ -27,10 +27,10 @@ impl Command for ColumnsDF {
|
|||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Dataframe columns",
|
description: "Dataframe columns",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr columns",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr columns",
|
||||||
result: Some(Value::List {
|
result: Some(Value::list(
|
||||||
vals: vec![Value::test_string("a"), Value::test_string("b")],
|
vec![Value::test_string("a"), Value::test_string("b")],
|
||||||
span: Span::test_data(),
|
Span::test_data(),
|
||||||
}),
|
)),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +60,7 @@ fn command(
|
|||||||
.map(|v| Value::string(*v, call.head))
|
.map(|v| Value::string(*v, call.head))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let names = Value::List {
|
let names = Value::list(names, call.head);
|
||||||
vals: names,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(names, None))
|
Ok(PipelineData::Value(names, None))
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,7 @@ fn command(
|
|||||||
.dtype();
|
.dtype();
|
||||||
|
|
||||||
let dtype_str = dtype.to_string();
|
let dtype_str = dtype.to_string();
|
||||||
dtypes.push(Value::String {
|
dtypes.push(Value::string(dtype_str, call.head));
|
||||||
val: dtype_str,
|
|
||||||
span: call.head,
|
|
||||||
});
|
|
||||||
|
|
||||||
Value::string(*v, call.head)
|
Value::string(*v, call.head)
|
||||||
})
|
})
|
||||||
|
@ -20,6 +20,7 @@ impl Command for Dummies {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
|
.switch("drop-first", "Drop first row", Some('d'))
|
||||||
.input_output_type(
|
.input_output_type(
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
@ -115,10 +116,11 @@ fn command(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let drop_first: bool = call.has_flag("drop-first");
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
.to_dummies(None)
|
.to_dummies(None, drop_first)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Error calculating dummies".into(),
|
"Error calculating dummies".into(),
|
||||||
|
@ -92,7 +92,7 @@ fn command_eager(
|
|||||||
df: NuDataFrame,
|
df: NuDataFrame,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let mask_value: Value = call.req(engine_state, stack, 0)?;
|
let mask_value: Value = call.req(engine_state, stack, 0)?;
|
||||||
let mask_span = mask_value.span()?;
|
let mask_span = mask_value.span();
|
||||||
|
|
||||||
if NuExpression::can_downcast(&mask_value) {
|
if NuExpression::can_downcast(&mask_value) {
|
||||||
let expression = NuExpression::try_from_value(mask_value)?;
|
let expression = NuExpression::try_from_value(mask_value)?;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use super::super::values::{Column, NuDataFrame, NuExpression};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
@ -15,7 +15,7 @@ impl Command for FirstDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Show only the first number of rows."
|
"Show only the first number of rows or create a first expression"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -25,10 +25,16 @@ impl Command for FirstDF {
|
|||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
"starting from the front, the number of rows to return",
|
"starting from the front, the number of rows to return",
|
||||||
)
|
)
|
||||||
.input_output_type(
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
)
|
),
|
||||||
|
])
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +70,11 @@ impl Command for FirstDF {
|
|||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Creates a first expression from a column",
|
||||||
|
example: "dfr col a | dfr first",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +85,19 @@ impl Command for FirstDF {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
let df = NuDataFrame::try_from_value(value)?;
|
||||||
command(engine_state, stack, call, df)
|
command(engine_state, stack, call, df)
|
||||||
|
} else {
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().first().into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,11 +119,25 @@ fn command(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples_dataframe() {
|
||||||
test_dataframe(vec![Box::new(FirstDF {})])
|
let mut engine_state = build_test_engine_state(vec![Box::new(FirstDF {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &FirstDF.examples()[0]);
|
||||||
|
test_dataframe_example(&mut engine_state, &FirstDF.examples()[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expression() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new(FirstDF {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &FirstDF.examples()[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame};
|
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame, NuExpression};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
@ -21,15 +21,22 @@ impl Command for LastDF {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.optional("rows", SyntaxShape::Int, "Number of rows for tail")
|
.optional("rows", SyntaxShape::Int, "Number of rows for tail")
|
||||||
.input_output_type(
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
)
|
),
|
||||||
|
])
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Create new dataframe with last rows",
|
description: "Create new dataframe with last rows",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
||||||
result: Some(
|
result: Some(
|
||||||
@ -40,7 +47,13 @@ impl Command for LastDF {
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Creates a last expression from a column",
|
||||||
|
example: "dfr col a | dfr last",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -50,8 +63,19 @@ impl Command for LastDF {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
let df = NuDataFrame::try_from_value(value)?;
|
||||||
command(engine_state, stack, call, df)
|
command(engine_state, stack, call, df)
|
||||||
|
} else {
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().last().into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,11 +97,24 @@ fn command(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples_dataframe() {
|
||||||
test_dataframe(vec![Box::new(LastDF {})])
|
let mut engine_state = build_test_engine_state(vec![Box::new(LastDF {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LastDF.examples()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expression() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new(LastDF {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LastDF.examples()[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dataframe::values::NuDataFrame;
|
use crate::dataframe::values::NuDataFrame;
|
||||||
@ -55,34 +55,18 @@ impl Command for ListDF {
|
|||||||
NuDataFrame::try_from_value(value).ok().map(|df| (name, df))
|
NuDataFrame::try_from_value(value).ok().map(|df| (name, df))
|
||||||
})
|
})
|
||||||
.map(|(name, df)| {
|
.map(|(name, df)| {
|
||||||
let name = Value::String {
|
Value::record(
|
||||||
val: name,
|
record! {
|
||||||
span: call.head,
|
"name" => Value::string(name, call.head),
|
||||||
};
|
"columns" => Value::int(df.as_ref().width() as i64, call.head),
|
||||||
|
"rows" => Value::int(df.as_ref().height() as i64, call.head),
|
||||||
let columns = Value::int(df.as_ref().width() as i64, call.head);
|
},
|
||||||
|
call.head,
|
||||||
let rows = Value::int(df.as_ref().height() as i64, call.head);
|
)
|
||||||
|
|
||||||
let cols = vec![
|
|
||||||
"name".to_string(),
|
|
||||||
"columns".to_string(),
|
|
||||||
"rows".to_string(),
|
|
||||||
];
|
|
||||||
let vals = vec![name, columns, rows];
|
|
||||||
|
|
||||||
Value::Record {
|
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<Value>>();
|
.collect::<Vec<Value>>();
|
||||||
|
|
||||||
let list = Value::List {
|
let list = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(list.into_pipeline_data())
|
Ok(list.into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ mod sql_expr;
|
|||||||
mod summary;
|
mod summary;
|
||||||
mod take;
|
mod take;
|
||||||
mod to_arrow;
|
mod to_arrow;
|
||||||
|
mod to_avro;
|
||||||
mod to_csv;
|
mod to_csv;
|
||||||
mod to_df;
|
mod to_df;
|
||||||
mod to_json_lines;
|
mod to_json_lines;
|
||||||
@ -55,6 +56,7 @@ pub use sql_expr::parse_sql_expr;
|
|||||||
pub use summary::Summary;
|
pub use summary::Summary;
|
||||||
pub use take::TakeDF;
|
pub use take::TakeDF;
|
||||||
pub use to_arrow::ToArrow;
|
pub use to_arrow::ToArrow;
|
||||||
|
pub use to_avro::ToAvro;
|
||||||
pub use to_csv::ToCSV;
|
pub use to_csv::ToCSV;
|
||||||
pub use to_df::ToDataFrame;
|
pub use to_df::ToDataFrame;
|
||||||
pub use to_json_lines::ToJsonLines;
|
pub use to_json_lines::ToJsonLines;
|
||||||
@ -96,6 +98,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
|||||||
SliceDF,
|
SliceDF,
|
||||||
TakeDF,
|
TakeDF,
|
||||||
ToArrow,
|
ToArrow,
|
||||||
|
ToAvro,
|
||||||
ToCSV,
|
ToCSV,
|
||||||
ToDataFrame,
|
ToDataFrame,
|
||||||
ToNu,
|
ToNu,
|
||||||
|
@ -13,6 +13,8 @@ use polars::prelude::{
|
|||||||
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use polars_io::avro::AvroReader;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OpenDataFrame;
|
pub struct OpenDataFrame;
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ impl Command for OpenDataFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Opens CSV, JSON, JSON lines, arrow, or parquet file to create dataframe."
|
"Opens CSV, JSON, JSON lines, arrow, avro, or parquet file to create dataframe."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -36,7 +38,7 @@ impl Command for OpenDataFrame {
|
|||||||
.named(
|
.named(
|
||||||
"type",
|
"type",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"File type: csv, tsv, json, parquet, arrow. If omitted, derive from file extension",
|
"File type: csv, tsv, json, parquet, arrow, avro. If omitted, derive from file extension",
|
||||||
Some('t'),
|
Some('t'),
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
@ -114,10 +116,11 @@ fn command(
|
|||||||
match type_id {
|
match type_id {
|
||||||
Some((e, msg, blamed)) => match e.as_str() {
|
Some((e, msg, blamed)) => match e.as_str() {
|
||||||
"csv" | "tsv" => from_csv(engine_state, stack, call),
|
"csv" | "tsv" => from_csv(engine_state, stack, call),
|
||||||
"parquet" => from_parquet(engine_state, stack, call),
|
"parquet" | "parq" => from_parquet(engine_state, stack, call),
|
||||||
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
|
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
|
||||||
"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),
|
||||||
_ => Err(ShellError::FileNotFoundCustom(
|
_ => Err(ShellError::FileNotFoundCustom(
|
||||||
format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
||||||
blamed,
|
blamed,
|
||||||
@ -199,6 +202,46 @@ fn from_parquet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_avro(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
|
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||||
|
|
||||||
|
let r = File::open(&file.item).map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Error opening file".into(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(file.span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let reader = AvroReader::new(r);
|
||||||
|
|
||||||
|
let reader = match columns {
|
||||||
|
None => reader,
|
||||||
|
Some(columns) => reader.with_columns(Some(columns)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let df: NuDataFrame = reader
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Avro reader error".into(),
|
||||||
|
format!("{e:?}"),
|
||||||
|
Some(call.head),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
Ok(df.into_value(call.head))
|
||||||
|
}
|
||||||
|
|
||||||
fn from_ipc(
|
fn from_ipc(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
@ -88,10 +88,7 @@ fn command(
|
|||||||
let lazy = NuLazyFrame::new(false, df_sql);
|
let lazy = NuLazyFrame::new(false, df_sql);
|
||||||
|
|
||||||
let eager = lazy.collect(call.head)?;
|
let eager = lazy.collect(call.head)?;
|
||||||
let value = Value::CustomValue {
|
let value = Value::custom_value(Box::new(eager), call.head);
|
||||||
val: Box::new(eager),
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
Ok(PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ fn command_lazy(
|
|||||||
let value: Value = call.req(engine_state, stack, 1)?;
|
let value: Value = call.req(engine_state, stack, 1)?;
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: "New name list has different size to column list".into(),
|
msg: "New name list has different size to column list".into(),
|
||||||
span: value.span()?,
|
span: value.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,12 +52,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
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -147,7 +147,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)
|
||||||
|
@ -125,7 +125,7 @@ pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_window_spec(expr: Expr, window_type: &Option<WindowType>) -> Result<Expr> {
|
fn apply_window_spec(expr: Expr, window_type: Option<&WindowType>) -> Result<Expr> {
|
||||||
Ok(match &window_type {
|
Ok(match &window_type {
|
||||||
Some(wtype) => match wtype {
|
Some(wtype) => match wtype {
|
||||||
WindowType::WindowSpec(window_spec) => {
|
WindowType::WindowSpec(window_spec) => {
|
||||||
@ -168,13 +168,13 @@ fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
|||||||
sql_function.distinct,
|
sql_function.distinct,
|
||||||
) {
|
) {
|
||||||
("sum", [FunctionArgExpr::Expr(expr)], false) => {
|
("sum", [FunctionArgExpr::Expr(expr)], false) => {
|
||||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.sum()
|
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.sum()
|
||||||
}
|
}
|
||||||
("count", [FunctionArgExpr::Expr(expr)], false) => {
|
("count", [FunctionArgExpr::Expr(expr)], false) => {
|
||||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.count()
|
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.count()
|
||||||
}
|
}
|
||||||
("count", [FunctionArgExpr::Expr(expr)], true) => {
|
("count", [FunctionArgExpr::Expr(expr)], true) => {
|
||||||
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.n_unique()
|
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.n_unique()
|
||||||
}
|
}
|
||||||
// Special case for wildcard args to count function.
|
// Special case for wildcard args to count function.
|
||||||
("count", [FunctionArgExpr::Wildcard], false) => lit(1i32).count(),
|
("count", [FunctionArgExpr::Wildcard], false) => lit(1i32).count(),
|
||||||
|
@ -120,30 +120,31 @@ fn command(
|
|||||||
let quantiles = quantiles.map(|values| {
|
let quantiles = quantiles.map(|values| {
|
||||||
values
|
values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|value| match value {
|
.map(|value| {
|
||||||
Value::Float { val, span } => {
|
let span = value.span();
|
||||||
|
match value {
|
||||||
|
Value::Float { val, .. } => {
|
||||||
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(),
|
"Incorrect value for quantile".to_string(),
|
||||||
"value should be between 0 and 1".to_string(),
|
"value should be between 0 and 1".to_string(),
|
||||||
Some(*span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => match value.span() {
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
Ok(span) => Err(ShellError::GenericError(
|
_ => Err(ShellError::GenericError(
|
||||||
"Incorrect value for quantile".to_string(),
|
"Incorrect value for quantile".to_string(),
|
||||||
"value should be a float".to_string(),
|
"value should be a float".to_string(),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
Err(e) => Err(e),
|
}
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<f64>, ShellError>>()
|
.collect::<Result<Vec<f64>, ShellError>>()
|
||||||
});
|
});
|
||||||
|
@ -93,7 +93,7 @@ fn command(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let index_value: Value = call.req(engine_state, stack, 0)?;
|
let index_value: Value = call.req(engine_state, stack, 0)?;
|
||||||
let index_span = index_value.span()?;
|
let index_span = index_value.span();
|
||||||
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() {
|
||||||
|
@ -78,16 +78,10 @@ fn command(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::String {
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
val: format!("saved {:?}", &file_name.item),
|
|
||||||
span: file_name.span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
Value::List {
|
Value::list(vec![file_value], call.head),
|
||||||
vals: vec![file_value],
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
117
crates/nu-cmd-dataframe/src/dataframe/eager/to_avro.rs
Normal file
117
crates/nu-cmd-dataframe/src/dataframe/eager/to_avro.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
|
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use polars_io::avro::{AvroCompression, AvroWriter};
|
||||||
|
use polars_io::SerWriter;
|
||||||
|
|
||||||
|
use super::super::values::NuDataFrame;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToAvro;
|
||||||
|
|
||||||
|
impl Command for ToAvro {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr to-avro"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Saves dataframe to avro file."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.named(
|
||||||
|
"compression",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"use compression, supports deflate or snappy",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
|
||||||
|
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Saves dataframe to avro file",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-avro test.avro",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
command(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_compression(call: &Call) -> Result<Option<AvroCompression>, ShellError> {
|
||||||
|
if let Some((compression, span)) = call
|
||||||
|
.get_flag_expr("compression")
|
||||||
|
.and_then(|e| e.as_string().map(|s| (s, e.span)))
|
||||||
|
{
|
||||||
|
match compression.as_ref() {
|
||||||
|
"snappy" => Ok(Some(AvroCompression::Snappy)),
|
||||||
|
"deflate" => Ok(Some(AvroCompression::Deflate)),
|
||||||
|
_ => Err(ShellError::IncorrectValue {
|
||||||
|
msg: "compression must be one of deflate or snappy".to_string(),
|
||||||
|
val_span: span,
|
||||||
|
call_span: span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
|
let compression = get_compression(call)?;
|
||||||
|
|
||||||
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
|
let file = File::create(&file_name.item).map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Error with file name".into(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(file_name.span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
AvroWriter::new(file)
|
||||||
|
.with_compression(compression)
|
||||||
|
.finish(df.as_mut())
|
||||||
|
.map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Error saving file".into(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(file_name.span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
Value::list(vec![file_value], call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -124,16 +124,10 @@ fn command(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::String {
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
val: format!("saved {:?}", &file_name.item),
|
|
||||||
span: file_name.span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
Value::List {
|
Value::list(vec![file_value], call.head),
|
||||||
vals: vec![file_value],
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -81,16 +81,10 @@ fn command(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::String {
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
val: format!("saved {:?}", &file_name.item),
|
|
||||||
span: file_name.span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
Value::List {
|
Value::list(vec![file_value], call.head),
|
||||||
vals: vec![file_value],
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ 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, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::dataframe::values::NuExpression;
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
use super::super::values::NuDataFrame;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,7 +18,7 @@ impl Command for ToNu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Converts a section of the dataframe into nushell Table."
|
"Converts a dataframe or an expression into into nushell value for access and exploration."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -28,44 +30,47 @@ impl Command for ToNu {
|
|||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
.switch("tail", "shows tail rows", Some('t'))
|
.switch("tail", "shows tail rows", Some('t'))
|
||||||
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
|
.input_output_types(vec![
|
||||||
|
(Type::Custom("expression".into()), Type::Any),
|
||||||
|
(Type::Custom("dataframe".into()), Type::Table(vec![])),
|
||||||
|
])
|
||||||
|
//.input_output_type(Type::Any, Type::Any)
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
let cols = vec!["index".into(), "a".into(), "b".into()];
|
let cols = vec!["index".into(), "a".into(), "b".into()];
|
||||||
let rec_1 = Value::Record {
|
let rec_1 = Value::test_record(Record {
|
||||||
cols: cols.clone(),
|
cols: cols.clone(),
|
||||||
vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
|
vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
|
||||||
span: Span::test_data(),
|
});
|
||||||
};
|
let rec_2 = Value::test_record(Record {
|
||||||
let rec_2 = Value::Record {
|
|
||||||
cols: cols.clone(),
|
cols: cols.clone(),
|
||||||
vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)],
|
vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)],
|
||||||
span: Span::test_data(),
|
});
|
||||||
};
|
let rec_3 = Value::test_record(Record {
|
||||||
let rec_3 = Value::Record {
|
|
||||||
cols,
|
cols,
|
||||||
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
|
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
|
||||||
span: Span::test_data(),
|
});
|
||||||
};
|
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Shows head rows from dataframe",
|
description: "Shows head rows from dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr into-nu",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr into-nu",
|
||||||
result: Some(Value::List {
|
result: Some(Value::list(vec![rec_1, rec_2], Span::test_data())),
|
||||||
vals: vec![rec_1, rec_2],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
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 {
|
result: Some(Value::list(vec![rec_3], Span::test_data())),
|
||||||
vals: vec![rec_3],
|
},
|
||||||
span: Span::test_data(),
|
Example {
|
||||||
}),
|
description: "Convert a col expression into a nushell value",
|
||||||
|
example: "dfr col a | dfr into-nu",
|
||||||
|
result: Some(Value::test_record(Record {
|
||||||
|
cols: vec!["expr".into(), "value".into()],
|
||||||
|
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -77,20 +82,25 @@ impl Command for ToNu {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
command(engine_state, stack, call, input)
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
dataframe_command(engine_state, stack, call, value)
|
||||||
|
} else {
|
||||||
|
expression_command(call, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command(
|
fn dataframe_command(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: Value,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?;
|
let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?;
|
||||||
let tail: bool = call.has_flag("tail");
|
let tail: bool = call.has_flag("tail");
|
||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_value(input)?;
|
||||||
|
|
||||||
let values = if tail {
|
let values = if tail {
|
||||||
df.tail(rows, call.head)?
|
df.tail(rows, call.head)?
|
||||||
@ -103,21 +113,30 @@ fn command(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = Value::List {
|
let value = Value::list(values, call.head);
|
||||||
vals: values,
|
|
||||||
span: call.head,
|
Ok(PipelineData::Value(value, None))
|
||||||
};
|
}
|
||||||
|
fn expression_command(call: &Call, input: Value) -> Result<PipelineData, ShellError> {
|
||||||
|
let expr = NuExpression::try_from_value(input)?;
|
||||||
|
let value = expr.to_value(call.head)?;
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
Ok(PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::super::super::expressions::ExprCol;
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples_dataframe_input() {
|
||||||
test_dataframe(vec![Box::new(ToNu {})])
|
test_dataframe(vec![Box::new(ToNu {})])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expression_input() {
|
||||||
|
test_dataframe(vec![Box::new(ToNu {}), Box::new(ExprCol {})])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,16 +78,10 @@ fn command(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_value = Value::String {
|
let file_value = Value::string(format!("saved {:?}", &file_name.item), file_name.span);
|
||||||
val: format!("saved {:?}", &file_name.item),
|
|
||||||
span: file_name.span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
Value::List {
|
Value::list(vec![file_value], call.head),
|
||||||
vals: vec![file_value],
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ impl Command for WithColumn {
|
|||||||
Err(ShellError::CantConvert {
|
Err(ShellError::CantConvert {
|
||||||
to_type: "lazy or eager dataframe".into(),
|
to_type: "lazy or eager dataframe".into(),
|
||||||
from_type: value.get_type().to_string(),
|
from_type: value.get_type().to_string(),
|
||||||
span: value.span()?,
|
span: value.span(),
|
||||||
help: None,
|
help: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -128,14 +128,11 @@ fn command_eager(
|
|||||||
mut df: NuDataFrame,
|
mut df: NuDataFrame,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let new_column: Value = call.req(engine_state, stack, 0)?;
|
let new_column: Value = call.req(engine_state, stack, 0)?;
|
||||||
let column_span = new_column.span()?;
|
let column_span = new_column.span();
|
||||||
|
|
||||||
if NuExpression::can_downcast(&new_column) {
|
if NuExpression::can_downcast(&new_column) {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
let lazy = NuLazyFrame::new(true, df.lazy().with_columns(&expressions));
|
let lazy = NuLazyFrame::new(true, df.lazy().with_columns(&expressions));
|
||||||
|
|
||||||
@ -179,10 +176,7 @@ fn command_lazy(
|
|||||||
lazy: NuLazyFrame,
|
lazy: NuLazyFrame,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
|
|
||||||
let lazy: NuLazyFrame = lazy.into_polars().with_columns(&expressions).into();
|
let lazy: NuLazyFrame = lazy.into_polars().with_columns(&expressions).into();
|
||||||
|
@ -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, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -41,20 +41,18 @@ impl Command for ExprAlias {
|
|||||||
let cols = vec!["expr".into(), "value".into()];
|
let cols = vec!["expr".into(), "value".into()];
|
||||||
let expr = Value::test_string("column");
|
let expr = Value::test_string("column");
|
||||||
let value = Value::test_string("a");
|
let value = Value::test_string("a");
|
||||||
let expr = Value::Record {
|
let expr = Value::test_record(Record {
|
||||||
cols,
|
cols,
|
||||||
vals: vec![expr, value],
|
vals: vec![expr, value],
|
||||||
span: Span::test_data(),
|
});
|
||||||
};
|
|
||||||
|
|
||||||
let cols = vec!["expr".into(), "alias".into()];
|
let cols = vec!["expr".into(), "alias".into()];
|
||||||
let value = Value::test_string("new_a");
|
let value = Value::test_string("new_a");
|
||||||
|
|
||||||
let record = Value::Record {
|
let record = Value::test_record(Record {
|
||||||
cols,
|
cols,
|
||||||
vals: vec![expr, value],
|
vals: vec![expr, value],
|
||||||
span: Span::test_data(),
|
});
|
||||||
};
|
|
||||||
|
|
||||||
Some(record)
|
Some(record)
|
||||||
},
|
},
|
||||||
@ -88,7 +86,7 @@ impl Command for ExprAlias {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dataframe::expressions::ExprAsNu;
|
use crate::dataframe::eager::ToNu;
|
||||||
use crate::dataframe::expressions::ExprCol;
|
use crate::dataframe::expressions::ExprCol;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -96,7 +94,7 @@ mod test {
|
|||||||
test_dataframe(vec![
|
test_dataframe(vec![
|
||||||
Box::new(ExprAlias {}),
|
Box::new(ExprAlias {}),
|
||||||
Box::new(ExprCol {}),
|
Box::new(ExprCol {}),
|
||||||
Box::new(ExprAsNu {}),
|
Box::new(ToNu {}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
use super::super::values::NuExpression;
|
|
||||||
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExprAsNu;
|
|
||||||
|
|
||||||
impl Command for ExprAsNu {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"dfr into-nu"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Convert expression into a nu value for access and exploration."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.input_output_type(Type::Custom("expression".into()), Type::Any)
|
|
||||||
.category(Category::Custom("expression".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Convert a col expression into a nushell value",
|
|
||||||
example: "dfr col a | dfr into-nu",
|
|
||||||
result: Some(Value::Record {
|
|
||||||
cols: vec!["expr".into(), "value".into()],
|
|
||||||
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["convert", "conversion"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
_engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let expr = NuExpression::try_from_pipeline(input, call.head)?;
|
|
||||||
let value = expr.to_value(call.head);
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
|
||||||
use super::super::ExprCol;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
test_dataframe(vec![Box::new(ExprAsNu {}), Box::new(ExprCol {})])
|
|
||||||
}
|
|
||||||
}
|
|
@ -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, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use polars::prelude::col;
|
use polars::prelude::col;
|
||||||
|
|
||||||
@ -34,11 +34,10 @@ 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::Record {
|
result: Some(Value::test_record(Record {
|
||||||
cols: vec!["expr".into(), "value".into()],
|
cols: vec!["expr".into(), "value".into()],
|
||||||
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
vals: vec![Value::test_string("column"), Value::test_string("a")],
|
||||||
span: Span::test_data(),
|
})),
|
||||||
}),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +63,10 @@ impl Command for ExprCol {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dataframe::expressions::as_nu::ExprAsNu;
|
use crate::dataframe::eager::ToNu;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
test_dataframe(vec![Box::new(ExprCol {}), Box::new(ExprAsNu {})])
|
test_dataframe(vec![Box::new(ExprCol {}), Box::new(ToNu {})])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,9 +144,9 @@ impl Command for ExprDatePart {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::dataframe::eager::ToNu;
|
||||||
use crate::dataframe::eager::WithColumn;
|
use crate::dataframe::eager::WithColumn;
|
||||||
use crate::dataframe::expressions::ExprAlias;
|
use crate::dataframe::expressions::ExprAlias;
|
||||||
use crate::dataframe::expressions::ExprAsNu;
|
|
||||||
use crate::dataframe::expressions::ExprCol;
|
use crate::dataframe::expressions::ExprCol;
|
||||||
use crate::dataframe::series::AsDateTime;
|
use crate::dataframe::series::AsDateTime;
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ mod test {
|
|||||||
test_dataframe(vec![
|
test_dataframe(vec![
|
||||||
Box::new(ExprDatePart {}),
|
Box::new(ExprDatePart {}),
|
||||||
Box::new(ExprCol {}),
|
Box::new(ExprCol {}),
|
||||||
Box::new(ExprAsNu {}),
|
Box::new(ToNu {}),
|
||||||
Box::new(AsDateTime {}),
|
Box::new(AsDateTime {}),
|
||||||
Box::new(WithColumn {}),
|
Box::new(WithColumn {}),
|
||||||
Box::new(ExprAlias {}),
|
Box::new(ExprAlias {}),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/// Definition of multiple Expression commands using a macro rule
|
/// Definition of multiple Expression commands using a macro rule
|
||||||
/// All of these expressions have an identical body and only require
|
/// All of these expressions have an identical body and only require
|
||||||
/// to have a change in the name, description and expression function
|
/// to have a change in the name, description and expression function
|
||||||
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
|
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
@ -134,6 +134,186 @@ macro_rules! expr_command {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The structs defined in this file are structs that form part of other commands
|
||||||
|
// since they share a similar name
|
||||||
|
macro_rules! lazy_expr_command {
|
||||||
|
($command: ident, $name: expr, $desc: expr, $examples: expr, $func: ident, $test: ident) => {
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct $command;
|
||||||
|
|
||||||
|
impl Command for $command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
$name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
$desc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.category(Category::Custom("expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
$examples
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
let lazy = NuLazyFrame::try_from_value(value)?;
|
||||||
|
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func());
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
|
||||||
|
} else {
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().$func().into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod $test {
|
||||||
|
use super::super::super::test_dataframe::{
|
||||||
|
build_test_engine_state, test_dataframe_example,
|
||||||
|
};
|
||||||
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_dataframe() {
|
||||||
|
// the first example should be a for the dataframe case
|
||||||
|
let example = &$command.examples()[0];
|
||||||
|
let mut engine_state = build_test_engine_state(vec![Box::new($command {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &example)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expressions() {
|
||||||
|
// the second example should be a for the dataframe case
|
||||||
|
let example = &$command.examples()[1];
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new($command {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &example)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($command: ident, $name: expr, $desc: expr, $examples: expr, $func: ident, $test: ident, $ddof: expr) => {
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct $command;
|
||||||
|
|
||||||
|
impl Command for $command {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
$name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
$desc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.category(Category::Custom("expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
$examples
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
let lazy = NuLazyFrame::try_from_value(value)?;
|
||||||
|
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func($ddof));
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
|
||||||
|
} else {
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().$func($ddof).into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod $test {
|
||||||
|
use super::super::super::test_dataframe::{
|
||||||
|
build_test_engine_state, test_dataframe_example,
|
||||||
|
};
|
||||||
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_dataframe() {
|
||||||
|
// the first example should be a for the dataframe case
|
||||||
|
let example = &$command.examples()[0];
|
||||||
|
let mut engine_state = build_test_engine_state(vec![Box::new($command {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &example)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expressions() {
|
||||||
|
// the second example should be a for the dataframe case
|
||||||
|
let example = &$command.examples()[1];
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new($command {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &example)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ExprList command
|
// ExprList command
|
||||||
// Expands to a command definition for a list expression
|
// Expands to a command definition for a list expression
|
||||||
expr_command!(
|
expr_command!(
|
||||||
@ -164,36 +344,6 @@ expr_command!(
|
|||||||
test_groups
|
test_groups
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprFlatten command
|
|
||||||
// Expands to a command definition for a flatten expression
|
|
||||||
expr_command!(
|
|
||||||
ExprFlatten,
|
|
||||||
"dfr flatten",
|
|
||||||
"creates a flatten expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "",
|
|
||||||
example: "",
|
|
||||||
result: None,
|
|
||||||
}],
|
|
||||||
flatten,
|
|
||||||
test_flatten
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprExplode command
|
|
||||||
// Expands to a command definition for a explode expression
|
|
||||||
expr_command!(
|
|
||||||
ExprExplode,
|
|
||||||
"dfr explode",
|
|
||||||
"creates an explode expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "",
|
|
||||||
example: "",
|
|
||||||
result: None,
|
|
||||||
}],
|
|
||||||
explode,
|
|
||||||
test_explode
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprCount command
|
// ExprCount command
|
||||||
// Expands to a command definition for a count expression
|
// Expands to a command definition for a count expression
|
||||||
expr_command!(
|
expr_command!(
|
||||||
@ -209,81 +359,6 @@ expr_command!(
|
|||||||
test_count
|
test_count
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprFirst command
|
|
||||||
// Expands to a command definition for a count expression
|
|
||||||
expr_command!(
|
|
||||||
ExprFirst,
|
|
||||||
"dfr first",
|
|
||||||
"creates a first expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a first expression from a column",
|
|
||||||
example: "dfr col a | dfr first",
|
|
||||||
result: None,
|
|
||||||
},],
|
|
||||||
first,
|
|
||||||
test_first
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprLast command
|
|
||||||
// Expands to a command definition for a count expression
|
|
||||||
expr_command!(
|
|
||||||
ExprLast,
|
|
||||||
"dfr last",
|
|
||||||
"creates a last expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a last expression from a column",
|
|
||||||
example: "dfr col a | dfr last",
|
|
||||||
result: None,
|
|
||||||
},],
|
|
||||||
last,
|
|
||||||
test_last
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprNUnique command
|
|
||||||
// Expands to a command definition for a n-unique expression
|
|
||||||
expr_command!(
|
|
||||||
ExprNUnique,
|
|
||||||
"dfr n-unique",
|
|
||||||
"creates a n-unique expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a is n-unique expression from a column",
|
|
||||||
example: "dfr col a | dfr n-unique",
|
|
||||||
result: None,
|
|
||||||
},],
|
|
||||||
n_unique,
|
|
||||||
test_nunique
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprIsNotNull command
|
|
||||||
// Expands to a command definition for a n-unique expression
|
|
||||||
expr_command!(
|
|
||||||
ExprIsNotNull,
|
|
||||||
"dfr is-not-null",
|
|
||||||
"creates a is not null expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a is not null expression from a column",
|
|
||||||
example: "dfr col a | dfr is-not-null",
|
|
||||||
result: None,
|
|
||||||
},],
|
|
||||||
is_not_null,
|
|
||||||
test_is_not_null
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprIsNull command
|
|
||||||
// Expands to a command definition for a n-unique expression
|
|
||||||
expr_command!(
|
|
||||||
ExprIsNull,
|
|
||||||
"dfr is-null",
|
|
||||||
"creates a is null expression",
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a is null expression from a column",
|
|
||||||
example: "dfr col a | dfr is-null",
|
|
||||||
result: None,
|
|
||||||
},],
|
|
||||||
is_null,
|
|
||||||
test_is_null
|
|
||||||
);
|
|
||||||
|
|
||||||
// ExprNot command
|
// ExprNot command
|
||||||
// Expands to a command definition for a not expression
|
// Expands to a command definition for a not expression
|
||||||
expr_command!(
|
expr_command!(
|
||||||
@ -301,11 +376,24 @@ expr_command!(
|
|||||||
|
|
||||||
// ExprMax command
|
// ExprMax command
|
||||||
// Expands to a command definition for max aggregation
|
// Expands to a command definition for max aggregation
|
||||||
expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMax,
|
ExprMax,
|
||||||
"dfr max",
|
"dfr max",
|
||||||
"Creates a max expression",
|
"Creates a max expression or aggregates columns to their max value",
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Max value from columns in a dataframe",
|
||||||
|
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_int(6)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_int(4)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Max aggregation for a group-by",
|
description: "Max 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]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -325,18 +413,32 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
max,
|
max,
|
||||||
test_max
|
test_max
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprMin command
|
// ExprMin command
|
||||||
// Expands to a command definition for min aggregation
|
// Expands to a command definition for min aggregation
|
||||||
expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMin,
|
ExprMin,
|
||||||
"dfr min",
|
"dfr min",
|
||||||
"Creates a min expression",
|
"Creates a min expression or aggregates columns to their min value",
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Min value from columns in a dataframe",
|
||||||
|
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_int(1)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_int(1)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Min aggregation for a group-by",
|
description: "Min 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]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -356,18 +458,32 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
min,
|
min,
|
||||||
test_min
|
test_min
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprSum command
|
// ExprSum command
|
||||||
// Expands to a command definition for sum aggregation
|
// Expands to a command definition for sum aggregation
|
||||||
expr_command!(
|
lazy_expr_command!(
|
||||||
ExprSum,
|
ExprSum,
|
||||||
"dfr sum",
|
"dfr sum",
|
||||||
"Creates a sum expression for an aggregation",
|
"Creates a sum expression for an aggregation or aggregates columns to their sum value",
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Sums all columns in a dataframe",
|
||||||
|
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_int(11)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_int(7)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Sum aggregation for a group-by",
|
description: "Sum 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]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -387,18 +503,32 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
sum,
|
sum,
|
||||||
test_sum
|
test_sum
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprMean command
|
// ExprMean command
|
||||||
// Expands to a command definition for mean aggregation
|
// Expands to a command definition for mean aggregation
|
||||||
expr_command!(
|
lazy_expr_command!(
|
||||||
ExprMean,
|
ExprMean,
|
||||||
"dfr mean",
|
"dfr mean",
|
||||||
"Creates a mean expression for an aggregation",
|
"Creates a mean expression for an aggregation or aggregates columns to their mean value",
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Mean value from columns in a dataframe",
|
||||||
|
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_float(2.0)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Mean aggregation for a group-by",
|
description: "Mean 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]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -418,7 +548,8 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
mean,
|
mean,
|
||||||
test_mean
|
test_mean
|
||||||
);
|
);
|
||||||
@ -456,11 +587,24 @@ expr_command!(
|
|||||||
|
|
||||||
// ExprStd command
|
// ExprStd command
|
||||||
// Expands to a command definition for std aggregation
|
// Expands to a command definition for std aggregation
|
||||||
expr_command!(
|
lazy_expr_command!(
|
||||||
ExprStd,
|
ExprStd,
|
||||||
"dfr std",
|
"dfr std",
|
||||||
"Creates a std expression for an aggregation",
|
"Creates a std expression for an aggregation of std value from columns in a dataframe",
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Std value from columns in a dataframe",
|
||||||
|
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_float(2.0)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Std aggregation for a group-by",
|
description: "Std aggregation for a group-by",
|
||||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -480,19 +624,34 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
std,
|
std,
|
||||||
test_std,
|
test_std,
|
||||||
0
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
// ExprVar command
|
// ExprVar command
|
||||||
// Expands to a command definition for var aggregation
|
// Expands to a command definition for var aggregation
|
||||||
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![Example {
|
vec![
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Var value from columns in a dataframe or aggregates columns to their var value",
|
||||||
|
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||||
|
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||||
|
])
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
description: "Var aggregation for a group-by",
|
description: "Var aggregation for a group-by",
|
||||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||||
| dfr into-df
|
| dfr into-df
|
||||||
@ -512,8 +671,9 @@ expr_command!(
|
|||||||
.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()),
|
||||||
),
|
),
|
||||||
},],
|
},
|
||||||
|
],
|
||||||
var,
|
var,
|
||||||
test_var,
|
test_var,
|
||||||
0
|
1
|
||||||
);
|
);
|
||||||
|
@ -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, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -33,11 +33,10 @@ 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::Record {
|
result: Some(Value::test_record(Record {
|
||||||
cols: vec!["expr".into(), "value".into()],
|
cols: vec!["expr".into(), "value".into()],
|
||||||
vals: vec![Value::test_string("literal"), Value::test_string("2")],
|
vals: vec![Value::test_string("literal"), Value::test_string("2")],
|
||||||
span: Span::test_data(),
|
})),
|
||||||
}),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,10 +65,10 @@ impl Command for ExprLit {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dataframe::expressions::as_nu::ExprAsNu;
|
use crate::dataframe::eager::ToNu;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
test_dataframe(vec![Box::new(ExprLit {}), Box::new(ExprAsNu {})])
|
test_dataframe(vec![Box::new(ExprLit {}), Box::new(ToNu {})])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
mod alias;
|
mod alias;
|
||||||
mod arg_where;
|
mod arg_where;
|
||||||
mod as_nu;
|
|
||||||
mod col;
|
mod col;
|
||||||
mod concat_str;
|
mod concat_str;
|
||||||
mod datepart;
|
mod datepart;
|
||||||
@ -15,7 +14,6 @@ use nu_protocol::engine::StateWorkingSet;
|
|||||||
|
|
||||||
pub(crate) use crate::dataframe::expressions::alias::ExprAlias;
|
pub(crate) use crate::dataframe::expressions::alias::ExprAlias;
|
||||||
use crate::dataframe::expressions::arg_where::ExprArgWhere;
|
use crate::dataframe::expressions::arg_where::ExprArgWhere;
|
||||||
use crate::dataframe::expressions::as_nu::ExprAsNu;
|
|
||||||
pub(super) use crate::dataframe::expressions::col::ExprCol;
|
pub(super) use crate::dataframe::expressions::col::ExprCol;
|
||||||
pub(super) use crate::dataframe::expressions::concat_str::ExprConcatStr;
|
pub(super) use crate::dataframe::expressions::concat_str::ExprConcatStr;
|
||||||
pub(crate) use crate::dataframe::expressions::datepart::ExprDatePart;
|
pub(crate) use crate::dataframe::expressions::datepart::ExprDatePart;
|
||||||
@ -44,21 +42,13 @@ pub fn add_expressions(working_set: &mut StateWorkingSet) {
|
|||||||
ExprConcatStr,
|
ExprConcatStr,
|
||||||
ExprCount,
|
ExprCount,
|
||||||
ExprLit,
|
ExprLit,
|
||||||
ExprAsNu,
|
|
||||||
ExprWhen,
|
ExprWhen,
|
||||||
ExprOtherwise,
|
ExprOtherwise,
|
||||||
ExprQuantile,
|
ExprQuantile,
|
||||||
ExprList,
|
ExprList,
|
||||||
ExprAggGroups,
|
ExprAggGroups,
|
||||||
ExprFlatten,
|
|
||||||
ExprExplode,
|
|
||||||
ExprCount,
|
ExprCount,
|
||||||
ExprFirst,
|
|
||||||
ExprLast,
|
|
||||||
ExprNUnique,
|
|
||||||
ExprIsIn,
|
ExprIsIn,
|
||||||
ExprIsNotNull,
|
|
||||||
ExprIsNull,
|
|
||||||
ExprNot,
|
ExprNot,
|
||||||
ExprMax,
|
ExprMax,
|
||||||
ExprMin,
|
ExprMin,
|
||||||
|
@ -95,10 +95,8 @@ impl Command for ExprOtherwise {
|
|||||||
|
|
||||||
let value = input.into_value(call.head);
|
let value = input.into_value(call.head);
|
||||||
let complete: NuExpression = match NuWhen::try_from_value(value)? {
|
let complete: NuExpression = match NuWhen::try_from_value(value)? {
|
||||||
NuWhen::WhenThen(when_then) => when_then
|
NuWhen::Then(then) => then.otherwise(otherwise_predicate.into_polars()).into(),
|
||||||
.otherwise(otherwise_predicate.into_polars())
|
NuWhen::ChainedThen(chained_when) => chained_when
|
||||||
.into(),
|
|
||||||
NuWhen::WhenThenThen(when_then_then) => when_then_then
|
|
||||||
.otherwise(otherwise_predicate.into_polars())
|
.otherwise(otherwise_predicate.into_polars())
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
@ -110,9 +108,9 @@ impl Command for ExprOtherwise {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use crate::dataframe::eager::WithColumn;
|
use crate::dataframe::eager::{ToNu, WithColumn};
|
||||||
use crate::dataframe::expressions::when::ExprWhen;
|
use crate::dataframe::expressions::when::ExprWhen;
|
||||||
use crate::dataframe::expressions::{ExprAlias, ExprAsNu, ExprCol};
|
use crate::dataframe::expressions::{ExprAlias, ExprCol};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -124,7 +122,7 @@ mod test {
|
|||||||
Box::new(ExprAlias {}),
|
Box::new(ExprAlias {}),
|
||||||
Box::new(ExprWhen {}),
|
Box::new(ExprWhen {}),
|
||||||
Box::new(ExprOtherwise {}),
|
Box::new(ExprOtherwise {}),
|
||||||
Box::new(ExprAsNu {}),
|
Box::new(ToNu {}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,11 +110,11 @@ impl Command for ExprWhen {
|
|||||||
.then(then_predicate.into_polars())
|
.then(then_predicate.into_polars())
|
||||||
.into(),
|
.into(),
|
||||||
v => match NuWhen::try_from_value(v)? {
|
v => match NuWhen::try_from_value(v)? {
|
||||||
NuWhen::WhenThen(when_then) => when_then
|
NuWhen::Then(when_then) => when_then
|
||||||
.when(when_predicate.into_polars())
|
.when(when_predicate.into_polars())
|
||||||
.then(then_predicate.into_polars())
|
.then(then_predicate.into_polars())
|
||||||
.into(),
|
.into(),
|
||||||
NuWhen::WhenThenThen(when_then_then) => when_then_then
|
NuWhen::ChainedThen(when_then_then) => when_then_then
|
||||||
.when(when_predicate.into_polars())
|
.when(when_predicate.into_polars())
|
||||||
.then(then_predicate.into_polars())
|
.then(then_predicate.into_polars())
|
||||||
.into(),
|
.into(),
|
||||||
@ -128,9 +128,9 @@ impl Command for ExprWhen {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::super::test_dataframe::test_dataframe;
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
use crate::dataframe::eager::WithColumn;
|
use crate::dataframe::eager::{ToNu, WithColumn};
|
||||||
use crate::dataframe::expressions::otherwise::ExprOtherwise;
|
use crate::dataframe::expressions::otherwise::ExprOtherwise;
|
||||||
use crate::dataframe::expressions::{ExprAlias, ExprAsNu, ExprCol};
|
use crate::dataframe::expressions::{ExprAlias, ExprCol};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ mod test {
|
|||||||
Box::new(ExprAlias {}),
|
Box::new(ExprAlias {}),
|
||||||
Box::new(ExprWhen {}),
|
Box::new(ExprWhen {}),
|
||||||
Box::new(ExprOtherwise {}),
|
Box::new(ExprOtherwise {}),
|
||||||
Box::new(ExprAsNu {}),
|
Box::new(ToNu {}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,10 +114,7 @@ impl Command for LazyAggregate {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
|
|
||||||
let group_by = NuLazyGroupBy::try_from_pipeline(input, call.head)?;
|
let group_by = NuLazyGroupBy::try_from_pipeline(input, call.head)?;
|
||||||
@ -172,7 +169,6 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||||||
},
|
},
|
||||||
Expr::Filter { input: expr, .. }
|
Expr::Filter { input: expr, .. }
|
||||||
| Expr::Slice { input: expr, .. }
|
| Expr::Slice { input: expr, .. }
|
||||||
| Expr::Cache { input: expr, .. }
|
|
||||||
| Expr::Cast { expr, .. }
|
| Expr::Cast { expr, .. }
|
||||||
| Expr::Sort { expr, .. }
|
| Expr::Sort { expr, .. }
|
||||||
| Expr::Take { expr, .. }
|
| Expr::Take { expr, .. }
|
||||||
@ -192,7 +188,8 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||||||
| Expr::Wildcard
|
| Expr::Wildcard
|
||||||
| Expr::RenameAlias { .. }
|
| Expr::RenameAlias { .. }
|
||||||
| Expr::Count
|
| Expr::Count
|
||||||
| Expr::Nth(_) => None,
|
| Expr::Nth(_)
|
||||||
|
| Expr::Selector(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,10 +58,7 @@ impl Command for LazyCollect {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
|
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
|
||||||
let eager = lazy.collect(call.head)?;
|
let eager = lazy.collect(call.head)?;
|
||||||
let value = Value::CustomValue {
|
let value = Value::custom_value(Box::new(eager), call.head);
|
||||||
val: Box::new(eager),
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
Ok(PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
|
158
crates/nu-cmd-dataframe/src/dataframe/lazy/explode.rs
Normal file
158
crates/nu-cmd-dataframe/src/dataframe/lazy/explode.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||||
|
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LazyExplode;
|
||||||
|
|
||||||
|
impl Command for LazyExplode {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr explode"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Explodes a dataframe or creates a explode expression."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.rest(
|
||||||
|
"columns",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"columns to explode, only applicable for dataframes",
|
||||||
|
)
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.category(Category::Custom("lazyframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Explode the specified dataframe",
|
||||||
|
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr explode hobbies | dfr collect",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new(
|
||||||
|
"id".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(2),
|
||||||
|
]),
|
||||||
|
Column::new(
|
||||||
|
"name".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Mercy"),
|
||||||
|
Value::test_string("Mercy"),
|
||||||
|
Value::test_string("Bob"),
|
||||||
|
Value::test_string("Bob"),
|
||||||
|
]),
|
||||||
|
Column::new(
|
||||||
|
"hobbies".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Cycling"),
|
||||||
|
Value::test_string("Knitting"),
|
||||||
|
Value::test_string("Skiing"),
|
||||||
|
Value::test_string("Football"),
|
||||||
|
]),
|
||||||
|
]).expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Select a column and explode the values",
|
||||||
|
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr explode)",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new(
|
||||||
|
"hobbies".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Cycling"),
|
||||||
|
Value::test_string("Knitting"),
|
||||||
|
Value::test_string("Skiing"),
|
||||||
|
Value::test_string("Football"),
|
||||||
|
]),
|
||||||
|
]).expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
explode(call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn explode(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||||
|
let value = input.into_value(call.head);
|
||||||
|
if NuDataFrame::can_downcast(&value) {
|
||||||
|
let df = NuLazyFrame::try_from_value(value)?;
|
||||||
|
let columns: Vec<String> = call
|
||||||
|
.positional_iter()
|
||||||
|
.filter_map(|e| e.as_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let exploded = df
|
||||||
|
.into_polars()
|
||||||
|
.explode(columns.iter().map(AsRef::as_ref).collect::<Vec<&str>>());
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuLazyFrame::from(exploded).into_value(call.head)?,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().explode().into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||||
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_dataframe() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![Box::new(LazyExplode {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore]
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expression() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new(LazyExplode {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -93,7 +93,7 @@ impl Command for LazyFillNA {
|
|||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let val_span = value.span()?;
|
let val_span = value.span();
|
||||||
let frame = NuDataFrame::try_from_value(value)?;
|
let frame = NuDataFrame::try_from_value(value)?;
|
||||||
let columns = frame.columns(val_span)?;
|
let columns = frame.columns(val_span)?;
|
||||||
let dataframe = columns
|
let dataframe = columns
|
||||||
@ -102,7 +102,9 @@ impl Command for LazyFillNA {
|
|||||||
let column_name = column.name().to_string();
|
let column_name = column.name().to_string();
|
||||||
let values = column
|
let values = column
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|value| match value {
|
.map(|value| {
|
||||||
|
let span = value.span();
|
||||||
|
match value {
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
if val.is_nan() {
|
if val.is_nan() {
|
||||||
fill.clone()
|
fill.clone()
|
||||||
@ -110,10 +112,11 @@ impl Command for LazyFillNA {
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::List { vals, span } => {
|
Value::List { vals, .. } => {
|
||||||
NuDataFrame::fill_list_nan(vals, span, fill.clone())
|
NuDataFrame::fill_list_nan(vals, span, fill.clone())
|
||||||
}
|
}
|
||||||
_ => value,
|
_ => value,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<Value>>();
|
.collect::<Vec<Value>>();
|
||||||
Column::new(column_name, values)
|
Column::new(column_name, values)
|
||||||
|
132
crates/nu-cmd-dataframe/src/dataframe/lazy/flatten.rs
Normal file
132
crates/nu-cmd-dataframe/src/dataframe/lazy/flatten.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
|
|
||||||
|
use super::explode::explode;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LazyFlatten;
|
||||||
|
|
||||||
|
impl Command for LazyFlatten {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr flatten"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"An alias for dfr explode"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.rest(
|
||||||
|
"columns",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"columns to flatten, only applicable for dataframes",
|
||||||
|
)
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.category(Category::Custom("lazyframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Flatten the specified dataframe",
|
||||||
|
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr flatten hobbies | dfr collect",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new(
|
||||||
|
"id".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(2),
|
||||||
|
]),
|
||||||
|
Column::new(
|
||||||
|
"name".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Mercy"),
|
||||||
|
Value::test_string("Mercy"),
|
||||||
|
Value::test_string("Bob"),
|
||||||
|
Value::test_string("Bob"),
|
||||||
|
]),
|
||||||
|
Column::new(
|
||||||
|
"hobbies".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Cycling"),
|
||||||
|
Value::test_string("Knitting"),
|
||||||
|
Value::test_string("Skiing"),
|
||||||
|
Value::test_string("Football"),
|
||||||
|
]),
|
||||||
|
]).expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Select a column and flatten the values",
|
||||||
|
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr flatten)",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![
|
||||||
|
Column::new(
|
||||||
|
"hobbies".to_string(),
|
||||||
|
vec![
|
||||||
|
Value::test_string("Cycling"),
|
||||||
|
Value::test_string("Knitting"),
|
||||||
|
Value::test_string("Skiing"),
|
||||||
|
Value::test_string("Football"),
|
||||||
|
]),
|
||||||
|
]).expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
explode(call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||||
|
use super::*;
|
||||||
|
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||||
|
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples_dataframe() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![Box::new(LazyFlatten {})]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore]
|
||||||
|
#[test]
|
||||||
|
fn test_examples_expression() {
|
||||||
|
let mut engine_state = build_test_engine_state(vec![
|
||||||
|
Box::new(LazyFlatten {}),
|
||||||
|
Box::new(LazyAggregate {}),
|
||||||
|
Box::new(ToLazyGroupBy {}),
|
||||||
|
]);
|
||||||
|
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -113,10 +113,7 @@ impl Command for ToLazyGroupBy {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
|
|
||||||
if expressions
|
if expressions
|
||||||
@ -126,7 +123,7 @@ impl Command for ToLazyGroupBy {
|
|||||||
let value: Value = call.req(engine_state, stack, 0)?;
|
let value: Value = call.req(engine_state, stack, 0)?;
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: "Expected only Col expressions".into(),
|
msg: "Expected only Col expressions".into(),
|
||||||
span: value.span()?,
|
span: value.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,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))
|
||||||
|
@ -199,7 +199,7 @@ impl Command for LazyJoin {
|
|||||||
let right_on: Value = call.req(engine_state, stack, 2)?;
|
let right_on: Value = call.req(engine_state, stack, 2)?;
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: "The right column list has a different size to the left column list".into(),
|
msg: "The right column list has a different size to the left column list".into(),
|
||||||
span: right_on.span()?,
|
span: right_on.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ impl Command for LazyJoin {
|
|||||||
let value: Value = call.req(engine_state, stack, *index)?;
|
let value: Value = call.req(engine_state, stack, *index)?;
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: "Expected only a string, col expressions or list of strings".into(),
|
msg: "Expected only a string, col expressions or list of strings".into(),
|
||||||
span: value.span()?,
|
span: value.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,94 +157,6 @@ lazy_command!(
|
|||||||
test_cache
|
test_cache
|
||||||
);
|
);
|
||||||
|
|
||||||
// LazyMax command
|
|
||||||
// Expands to a command definition for max aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazyMax,
|
|
||||||
"dfr max",
|
|
||||||
"Aggregates columns to their max value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Max value from columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_int(6)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_int(4)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
max,
|
|
||||||
test_max
|
|
||||||
);
|
|
||||||
|
|
||||||
// LazyMin command
|
|
||||||
// Expands to a command definition for min aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazyMin,
|
|
||||||
"dfr min",
|
|
||||||
"Aggregates columns to their min value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Min value from columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_int(1)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_int(1)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
min,
|
|
||||||
test_min
|
|
||||||
);
|
|
||||||
|
|
||||||
// LazySum command
|
|
||||||
// Expands to a command definition for sum aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazySum,
|
|
||||||
"dfr sum",
|
|
||||||
"Aggregates columns to their sum value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Sums all columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_int(11)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_int(7)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
sum,
|
|
||||||
test_sum
|
|
||||||
);
|
|
||||||
|
|
||||||
// LazyMean command
|
|
||||||
// Expands to a command definition for mean aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazyMean,
|
|
||||||
"dfr mean",
|
|
||||||
"Aggregates columns to their mean value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Mean value from columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_float(2.0)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
mean,
|
|
||||||
test_mean
|
|
||||||
);
|
|
||||||
|
|
||||||
// LazyMedian command
|
// LazyMedian command
|
||||||
// Expands to a command definition for median aggregation
|
// Expands to a command definition for median aggregation
|
||||||
lazy_command!(
|
lazy_command!(
|
||||||
@ -266,49 +178,3 @@ lazy_command!(
|
|||||||
median,
|
median,
|
||||||
test_median
|
test_median
|
||||||
);
|
);
|
||||||
|
|
||||||
// LazyStd command
|
|
||||||
// Expands to a command definition for std aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazyStd,
|
|
||||||
"dfr std",
|
|
||||||
"Aggregates columns to their std value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Std value from columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_float(2.0)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
std,
|
|
||||||
test_std,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
// LazyVar command
|
|
||||||
// Expands to a command definition for var aggregation
|
|
||||||
lazy_command!(
|
|
||||||
LazyVar,
|
|
||||||
"dfr var",
|
|
||||||
"Aggregates columns to their var value",
|
|
||||||
vec![Example {
|
|
||||||
description: "Var value from columns in a dataframe",
|
|
||||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var",
|
|
||||||
result: Some(
|
|
||||||
NuDataFrame::try_from_columns(vec![
|
|
||||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
|
||||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
|
||||||
])
|
|
||||||
.expect("simple df for test should not fail")
|
|
||||||
.into_value(Span::test_data()),
|
|
||||||
),
|
|
||||||
},],
|
|
||||||
var,
|
|
||||||
test_var,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
pub mod aggregate;
|
pub mod aggregate;
|
||||||
mod collect;
|
mod collect;
|
||||||
|
mod explode;
|
||||||
mod fetch;
|
mod fetch;
|
||||||
mod fill_nan;
|
mod fill_nan;
|
||||||
mod fill_null;
|
mod fill_null;
|
||||||
mod filter;
|
mod filter;
|
||||||
|
mod flatten;
|
||||||
pub mod groupby;
|
pub mod groupby;
|
||||||
mod join;
|
mod join;
|
||||||
mod macro_commands;
|
mod macro_commands;
|
||||||
@ -27,6 +29,8 @@ use crate::dataframe::lazy::quantile::LazyQuantile;
|
|||||||
pub(crate) use crate::dataframe::lazy::select::LazySelect;
|
pub(crate) use crate::dataframe::lazy::select::LazySelect;
|
||||||
use crate::dataframe::lazy::sort_by_expr::LazySortBy;
|
use crate::dataframe::lazy::sort_by_expr::LazySortBy;
|
||||||
pub use crate::dataframe::lazy::to_lazy::ToLazyFrame;
|
pub use crate::dataframe::lazy::to_lazy::ToLazyFrame;
|
||||||
|
pub use explode::LazyExplode;
|
||||||
|
pub use flatten::LazyFlatten;
|
||||||
|
|
||||||
pub fn add_lazy_decls(working_set: &mut StateWorkingSet) {
|
pub fn add_lazy_decls(working_set: &mut StateWorkingSet) {
|
||||||
macro_rules! bind_command {
|
macro_rules! bind_command {
|
||||||
@ -49,17 +53,13 @@ pub fn add_lazy_decls(working_set: &mut StateWorkingSet) {
|
|||||||
LazyFilter,
|
LazyFilter,
|
||||||
LazyJoin,
|
LazyJoin,
|
||||||
LazyQuantile,
|
LazyQuantile,
|
||||||
LazyMax,
|
|
||||||
LazyMin,
|
|
||||||
LazySum,
|
|
||||||
LazyMean,
|
|
||||||
LazyMedian,
|
LazyMedian,
|
||||||
LazyStd,
|
|
||||||
LazyVar,
|
|
||||||
LazyReverse,
|
LazyReverse,
|
||||||
LazySelect,
|
LazySelect,
|
||||||
LazySortBy,
|
LazySortBy,
|
||||||
ToLazyFrame,
|
ToLazyFrame,
|
||||||
ToLazyGroupBy
|
ToLazyGroupBy,
|
||||||
|
LazyExplode,
|
||||||
|
LazyFlatten
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,7 @@ impl Command for LazySelect {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
|
|
||||||
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
|
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
@ -37,6 +37,7 @@ impl Command for LazySortBy {
|
|||||||
"nulls are shown last in the dataframe",
|
"nulls are shown last in the dataframe",
|
||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
|
.switch("maintain-order", "Maintains order during sort", Some('m'))
|
||||||
.input_output_type(
|
.input_output_type(
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
Type::Custom("dataframe".into()),
|
Type::Custom("dataframe".into()),
|
||||||
@ -104,12 +105,10 @@ impl Command for LazySortBy {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let value = Value::List {
|
let value = Value::list(vals, call.head);
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let expressions = NuExpression::extract_exprs(value)?;
|
let expressions = NuExpression::extract_exprs(value)?;
|
||||||
let nulls_last = call.has_flag("nulls-last");
|
let nulls_last = call.has_flag("nulls-last");
|
||||||
|
let maintain_order = call.has_flag("maintain-order");
|
||||||
|
|
||||||
let reverse: Option<Vec<bool>> = call.get_flag(engine_state, stack, "reverse")?;
|
let reverse: Option<Vec<bool>> = call.get_flag(engine_state, stack, "reverse")?;
|
||||||
let reverse = match reverse {
|
let reverse = match reverse {
|
||||||
@ -118,7 +117,7 @@ impl Command for LazySortBy {
|
|||||||
let span = call
|
let span = call
|
||||||
.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(),
|
"Incorrect list size".into(),
|
||||||
"Size doesn't match expression list".into(),
|
"Size doesn't match expression list".into(),
|
||||||
@ -137,7 +136,7 @@ impl Command for LazySortBy {
|
|||||||
let lazy = NuLazyFrame::new(
|
let lazy = NuLazyFrame::new(
|
||||||
lazy.from_eager,
|
lazy.from_eager,
|
||||||
lazy.into_polars()
|
lazy.into_polars()
|
||||||
.sort_by_exprs(&expressions, reverse, nulls_last),
|
.sort_by_exprs(&expressions, reverse, nulls_last, maintain_order),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
|
@ -41,10 +41,7 @@ impl Command for ToLazyFrame {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let df = NuDataFrame::try_from_iter(input.into_iter())?;
|
let df = NuDataFrame::try_from_iter(input.into_iter())?;
|
||||||
let lazy = NuLazyFrame::from_dataframe(df);
|
let lazy = NuLazyFrame::from_dataframe(df);
|
||||||
let value = Value::CustomValue {
|
let value = Value::custom_value(Box::new(lazy), call.head);
|
||||||
val: Box::new(lazy),
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
Ok(PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -56,22 +56,22 @@ impl Command for AsDateTime {
|
|||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(vec![Column::new(
|
||||||
"datetime".to_string(),
|
"datetime".to_string(),
|
||||||
vec![
|
vec![
|
||||||
Value::Date {
|
Value::date(
|
||||||
val: DateTime::parse_from_str(
|
DateTime::parse_from_str(
|
||||||
"2021-12-30 00:00:00 +0000",
|
"2021-12-30 00:00:00 +0000",
|
||||||
"%Y-%m-%d %H:%M:%S %z",
|
"%Y-%m-%d %H:%M:%S %z",
|
||||||
)
|
)
|
||||||
.expect("date calculation should not fail in test"),
|
.expect("date calculation should not fail in test"),
|
||||||
span: Span::test_data(),
|
Span::test_data(),
|
||||||
},
|
),
|
||||||
Value::Date {
|
Value::date(
|
||||||
val: DateTime::parse_from_str(
|
DateTime::parse_from_str(
|
||||||
"2021-12-31 00:00:00 +0000",
|
"2021-12-31 00:00:00 +0000",
|
||||||
"%Y-%m-%d %H:%M:%S %z",
|
"%Y-%m-%d %H:%M:%S %z",
|
||||||
)
|
)
|
||||||
.expect("date calculation should not fail in test"),
|
.expect("date calculation should not fail in test"),
|
||||||
span: Span::test_data(),
|
Span::test_data(),
|
||||||
},
|
),
|
||||||
],
|
],
|
||||||
)])
|
)])
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
@ -85,22 +85,22 @@ impl Command for AsDateTime {
|
|||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(vec![Column::new(
|
||||||
"datetime".to_string(),
|
"datetime".to_string(),
|
||||||
vec![
|
vec![
|
||||||
Value::Date {
|
Value::date(
|
||||||
val: DateTime::parse_from_str(
|
DateTime::parse_from_str(
|
||||||
"2021-12-30 00:00:00.123456789 +0000",
|
"2021-12-30 00:00:00.123456789 +0000",
|
||||||
"%Y-%m-%d %H:%M:%S.%9f %z",
|
"%Y-%m-%d %H:%M:%S.%9f %z",
|
||||||
)
|
)
|
||||||
.expect("date calculation should not fail in test"),
|
.expect("date calculation should not fail in test"),
|
||||||
span: Span::test_data(),
|
Span::test_data(),
|
||||||
},
|
),
|
||||||
Value::Date {
|
Value::date(
|
||||||
val: DateTime::parse_from_str(
|
DateTime::parse_from_str(
|
||||||
"2021-12-31 00:00:00.123456789 +0000",
|
"2021-12-31 00:00:00.123456789 +0000",
|
||||||
"%Y-%m-%d %H:%M:%S.%9f %z",
|
"%Y-%m-%d %H:%M:%S.%9f %z",
|
||||||
)
|
)
|
||||||
.expect("date calculation should not fail in test"),
|
.expect("date calculation should not fail in test"),
|
||||||
span: Span::test_data(),
|
Span::test_data(),
|
||||||
},
|
),
|
||||||
],
|
],
|
||||||
)])
|
)])
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
@ -143,7 +143,13 @@ fn command(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let res = if not_exact {
|
let res = if not_exact {
|
||||||
casted.as_datetime_not_exact(Some(format.as_str()), TimeUnit::Nanoseconds, None)
|
casted.as_datetime_not_exact(
|
||||||
|
Some(format.as_str()),
|
||||||
|
TimeUnit::Nanoseconds,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
&Default::default(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
casted.as_datetime(
|
casted.as_datetime(
|
||||||
Some(format.as_str()),
|
Some(format.as_str()),
|
||||||
@ -151,6 +157,7 @@ fn command(
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
|
&Default::default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user