mirror of
https://github.com/nushell/nushell.git
synced 2025-07-08 10:27:47 +02:00
Compare commits
181 Commits
Author | SHA1 | Date | |
---|---|---|---|
6cc4ef6c70 | |||
517173bb8c | |||
e415be6c0e | |||
59332562bb | |||
3d8d7787de | |||
611fe41788 | |||
a6118eed8d | |||
d4798d6ee1 | |||
5ea245badf | |||
5ee7847035 | |||
8a812cf03c | |||
9a1cedfd08 | |||
766d1ef374 | |||
beec658872 | |||
b90d701f89 | |||
be5d71ea47 | |||
f1bde69131 | |||
36ae384fb3 | |||
2c4048eb43 | |||
b9195c2668 | |||
bb968304da | |||
ca9bf19041 | |||
ecfee4c542 | |||
acb34561eb | |||
43aec8cdbe | |||
e46d610f77 | |||
1d95861a09 | |||
f48de73236 | |||
0b4daa66b0 | |||
412952182f | |||
014d36b17a | |||
f44f3a8af1 | |||
457514590d | |||
843d8c2242 | |||
ce4ae00a6f | |||
4f7f6a2932 | |||
7039602e4d | |||
acb7aff6fb | |||
8940ee6c3f | |||
3b26b4355e | |||
b56e603c58 | |||
66c2a36123 | |||
8838815737 | |||
834522d002 | |||
f281cd5aa3 | |||
5add5cbd12 | |||
902aad6016 | |||
13f87857cf | |||
e0cc2c9112 | |||
92ab8b831b | |||
6a7a60429f | |||
79fd7d54b2 | |||
ebca840d91 | |||
17b2bcc125 | |||
24a98f8999 | |||
e49b359848 | |||
c9fb381d69 | |||
8224ec49bc | |||
fe7e87ee02 | |||
a3dce8ff19 | |||
89f3cbf318 | |||
3f555a6836 | |||
4fdf5c663c | |||
1ec41a0ab4 | |||
ab0a6b6ca6 | |||
e3bf6fdfc0 | |||
46c0d29c08 | |||
76ccd5668a | |||
c6436eb32f | |||
b2c29117d9 | |||
ffb1dfb012 | |||
88c6fa9933 | |||
60df45a390 | |||
10aa86272b | |||
d37e6ba3b5 | |||
5eee33c7e4 | |||
5e748ae8fc | |||
50e53e788a | |||
7336e1df1a | |||
a724a8fe7d | |||
c731a4e275 | |||
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba |
27
.github/pull_request_template.md
vendored
27
.github/pull_request_template.md
vendored
@ -1,17 +1,26 @@
|
|||||||
# Description
|
# Description
|
||||||
|
|
||||||
(description of your pull request here)
|
(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)
|
||||||
|
|
||||||
# Tests
|
# Major Changes
|
||||||
|
|
||||||
Make sure you've done the following:
|
If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose).
|
||||||
|
Making sure we're all on board with the change saves everybody's time.
|
||||||
|
Thanks!
|
||||||
|
|
||||||
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
|
# Tests + Formatting
|
||||||
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
|
|
||||||
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
|
Make sure you've done the following, if applicable:
|
||||||
|
|
||||||
|
- Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder)
|
||||||
|
- Try to think about corner cases and various ways how your changes could break. Cover those in the tests
|
||||||
|
|
||||||
Make sure you've run and fixed any issues with these commands:
|
Make sure you've run and fixed any issues with these commands:
|
||||||
|
|
||||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||||
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
- `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
- `cargo test --workspace --features=extra` to check that all tests pass
|
||||||
|
|
||||||
|
# After Submitting
|
||||||
|
|
||||||
|
* Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||||
|
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
@ -20,29 +20,19 @@ jobs:
|
|||||||
NUSHELL_CARGO_TARGET: ci
|
NUSHELL_CARGO_TARGET: ci
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
# makes ci use rust-toolchain.toml
|
|
||||||
# with:
|
|
||||||
# profile: minimal
|
|
||||||
# toolchain: ${{ matrix.rust }}
|
|
||||||
# override: true
|
|
||||||
# components: rustfmt, clippy
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: "v2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
@ -72,22 +62,13 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
# makes ci use rust-toolchain.toml
|
|
||||||
# with:
|
|
||||||
# profile: minimal
|
|
||||||
# toolchain: ${{ matrix.rust }}
|
|
||||||
# override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||||
@ -108,28 +89,19 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
# makes ci use rust-toolchain.toml
|
|
||||||
# with:
|
|
||||||
# profile: minimal
|
|
||||||
# toolchain: ${{ matrix.rust }}
|
|
||||||
# override: true
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
key: "2" # increment this to bust the cache if needed
|
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: install
|
command: install
|
||||||
args: --path=. --profile ci --no-default-features
|
args: --path=. --profile ci --no-default-features
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
@ -159,24 +131,19 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.3.4
|
||||||
# makes ci use rust-toolchain.toml
|
|
||||||
# with:
|
|
||||||
# profile: minimal
|
|
||||||
# toolchain: ${{ matrix.rust }}
|
|
||||||
# override: true
|
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1.0.1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --profile ci --package nu_plugin_*
|
args: --profile ci --package nu_plugin_*
|
||||||
|
16
.github/workflows/release-pkg.nu
vendored
16
.github/workflows/release-pkg.nu
vendored
@ -76,9 +76,9 @@ cp -v README.release.txt $'($dist)/README.txt'
|
|||||||
|
|
||||||
$'(char nl)Check binary release version detail:'; hr-line
|
$'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os == 'windows-latest' {
|
let ver = if $os == 'windows-latest' {
|
||||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||||
} else {
|
} else {
|
||||||
(do -i { ./output/nu -c 'version' }) | str collect
|
(do -i { ./output/nu -c 'version' }) | str join
|
||||||
}
|
}
|
||||||
if ($ver | str trim | is-empty) {
|
if ($ver | str trim | is-empty) {
|
||||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||||
@ -90,10 +90,16 @@ if ($ver | str trim | is-empty) {
|
|||||||
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||||
|
|
||||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
let files = (ls | get name)
|
||||||
|
let dest = $'($bin)-($version)-($target)'
|
||||||
|
let archive = $'($dist)/($dest).tar.gz'
|
||||||
|
|
||||||
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
|
mkdir $dest
|
||||||
tar czf $archive *
|
$files | each {|it| mv $it $dest } | ignore
|
||||||
|
|
||||||
|
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||||
|
|
||||||
|
tar -czf $archive $dest
|
||||||
print $'archive: ---> ($archive)'; ls $archive
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
echo $'::set-output name=archive::($archive)'
|
echo $'::set-output name=archive::($archive)'
|
||||||
|
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v2.1
|
uses: hustcer/setup-nu@v2.1
|
||||||
with:
|
with:
|
||||||
version: 0.68.0
|
version: 0.69.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -22,6 +22,10 @@ debian/nu/
|
|||||||
# VSCode's IDE items
|
# VSCode's IDE items
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
# Visual Studio Extension SourceGear Rust items
|
||||||
|
VSWorkspaceSettings.json
|
||||||
|
unstable_cargo_features.txt
|
||||||
|
|
||||||
# Helix configuration folder
|
# Helix configuration folder
|
||||||
.helix/*
|
.helix/*
|
||||||
.helix
|
.helix
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Welcome to Nushell!
|
Welcome to Nushell and thank you for considering contributing!
|
||||||
|
|
||||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
## Review Process
|
||||||
|
|
||||||
|
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.
|
||||||
|
So, please, reach out and tell us what you want to do.
|
||||||
|
This will significantly increase the chance of your PR being accepted.
|
||||||
|
|
||||||
|
The review process can be summarized as follows:
|
||||||
|
1. You want to make some change to Nushell that is more involved than simple bug-fixing.
|
||||||
|
2. Go to [Discord](https://discordapp.com/invite/NtAbbGn) or a [GitHub issue](https://github.com/nushell/nushell/issues/new/choose) and chat with some core team members and/or other contributors about it.
|
||||||
|
3. After getting a green light from the core team, implement the feature, open a pull request (PR) and write a concise but comprehensive description of the change.
|
||||||
|
4. If your PR includes any use-facing features (such as adding a flag to a command), clearly list them in the PR description.
|
||||||
|
5. Then, core team members and other regular contributors will review the PR and suggest changes.
|
||||||
|
6. When we all agree, the PR will be merged.
|
||||||
|
7. If your PR includes any user-facing features, make sure the changes are also reflected in [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||||
|
8. Congratulate yourself, you just improved Nushell! :-)
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
|
977
Cargo.lock
generated
977
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
55
Cargo.toml
55
Cargo.toml
@ -8,10 +8,9 @@ exclude = ["images"]
|
|||||||
homepage = "https://www.nushell.sh"
|
homepage = "https://www.nushell.sh"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
version = "0.68.1"
|
version = "0.71.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
|
||||||
|
|
||||||
@ -37,23 +36,24 @@ chrono = { version = "0.4.21", features = ["serde"] }
|
|||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
ctrlc = "3.2.1"
|
ctrlc = "3.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "5.1.0"
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-cli = { path="./crates/nu-cli", version = "0.68.1" }
|
nu-cli = { path="./crates/nu-cli", version = "0.71.0" }
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.68.1" }
|
nu-color-config = { path = "./crates/nu-color-config", version = "0.71.0" }
|
||||||
nu-command = { path="./crates/nu-command", version = "0.68.1" }
|
nu-command = { path="./crates/nu-command", version = "0.71.0" }
|
||||||
nu-engine = { path="./crates/nu-engine", version = "0.68.1" }
|
nu-engine = { path="./crates/nu-engine", version = "0.71.0" }
|
||||||
nu-json = { path="./crates/nu-json", version = "0.68.1" }
|
nu-json = { path="./crates/nu-json", version = "0.71.0" }
|
||||||
nu-parser = { path="./crates/nu-parser", version = "0.68.1" }
|
nu-parser = { path="./crates/nu-parser", version = "0.71.0" }
|
||||||
nu-path = { path="./crates/nu-path", version = "0.68.1" }
|
nu-path = { path="./crates/nu-path", version = "0.71.0" }
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.68.1" }
|
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.71.0" }
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.68.1" }
|
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.71.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.68.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.71.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.68.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.71.0" }
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.68.1" }
|
nu-table = { path = "./crates/nu-table", version = "0.71.0" }
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.68.1" }
|
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.71.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.68.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.71.0" }
|
||||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
|
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
simplelog = "0.12.0"
|
simplelog = "0.12.0"
|
||||||
@ -64,8 +64,16 @@ time = "0.3.12"
|
|||||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||||
signal-hook = { version = "0.3.14", default-features = false }
|
signal-hook = { version = "0.3.14", default-features = false }
|
||||||
|
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
|
winres = "0.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
|
nix = { version = "0.25", default-features = false, features = ["signal", "process", "fs", "term"]}
|
||||||
|
atty = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="./crates/nu-test-support", version = "0.68.1" }
|
nu-test-support = { path="./crates/nu-test-support", version = "0.71.0" }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
assert_cmd = "2.0.2"
|
assert_cmd = "2.0.2"
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
@ -74,14 +82,11 @@ hamcrest2 = "0.3.0"
|
|||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
|
||||||
winres = "0.1"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||||
|
extra = ["default", "dataframe", "database"]
|
||||||
default = ["plugin", "which-support", "trash-support"]
|
default = ["plugin", "which-support", "trash-support"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
extra = ["default", "dataframe", "database"]
|
|
||||||
wasi = []
|
wasi = []
|
||||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
@ -122,3 +127,7 @@ debug = false
|
|||||||
name = "nu"
|
name = "nu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
[patch.crates-io]
|
||||||
|
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||||
|
@ -71,7 +71,7 @@ Additionally, commands can output structured data (you can think of this as a th
|
|||||||
Commands that work in the pipeline fit into one of three categories:
|
Commands that work in the pipeline fit into one of three categories:
|
||||||
|
|
||||||
- Commands that produce a stream (e.g., `ls`)
|
- Commands that produce a stream (e.g., `ls`)
|
||||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||||
- Commands that consume the output of the pipeline (e.g., `table`)
|
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||||
|
|
||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||||
|
|
||||||
> register -e json ./nu_plugin_query
|
> register ./nu_plugin_query
|
||||||
|
@ -5,22 +5,22 @@ 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.68.1"
|
version = "0.71.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path="../nu-test-support", version = "0.68.1" }
|
nu-test-support = { path="../nu-test-support", version = "0.71.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.68.1" }
|
nu-command = { path = "../nu-command", version = "0.71.0" }
|
||||||
rstest = {version = "0.15.0", default-features = false}
|
rstest = {version = "0.15.0", default-features = false}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.68.1" }
|
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.68.1" }
|
nu-path = { path = "../nu-path", version = "0.71.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.68.1" }
|
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.68.1" }
|
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.68.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
||||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
|
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
chrono = "0.4.21"
|
chrono = "0.4.21"
|
||||||
@ -30,9 +30,9 @@ fuzzy-matcher = "0.3.7"
|
|||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.1.0", features = ["fancy"] }
|
miette = { version = "5.1.0", features = ["fancy-no-backtrace"] }
|
||||||
strip-ansi-escapes = "0.1.1"
|
percent-encoding = "2"
|
||||||
sysinfo = "0.25.2"
|
sysinfo = "0.26.2"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -15,7 +15,6 @@ pub fn evaluate_commands(
|
|||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
table_mode: Option<Value>,
|
table_mode: Option<Value>,
|
||||||
) -> Result<Option<i64>> {
|
) -> Result<Option<i64>> {
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
@ -68,9 +67,7 @@ pub fn evaluate_commands(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
Ok(exit_code)
|
Ok(exit_code)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl CommandCompletion {
|
|||||||
.matches_str(&x.to_string_lossy(), prefix)),
|
.matches_str(&x.to_string_lossy(), prefix)),
|
||||||
Some(true)
|
Some(true)
|
||||||
)
|
)
|
||||||
&& is_executable::is_executable(&item.path())
|
&& is_executable::is_executable(item.path())
|
||||||
{
|
{
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
executables.push(name);
|
executables.push(name);
|
||||||
|
@ -60,10 +60,10 @@ impl NuCompleter {
|
|||||||
fn external_completion(
|
fn external_completion(
|
||||||
&self,
|
&self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
spans: Vec<String>,
|
spans: &[String],
|
||||||
offset: usize,
|
offset: usize,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> 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(&block.captures);
|
||||||
@ -75,9 +75,9 @@ impl NuCompleter {
|
|||||||
var_id,
|
var_id,
|
||||||
Value::List {
|
Value::List {
|
||||||
vals: spans
|
vals: spans
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|it| Value::String {
|
.map(|it| Value::String {
|
||||||
val: it,
|
val: it.to_string(),
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
@ -109,13 +109,13 @@ impl NuCompleter {
|
|||||||
offset,
|
offset,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return Some(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => println!("failed to eval completer block: {}", err),
|
Err(err) => println!("failed to eval completer block: {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![]
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
@ -123,7 +123,8 @@ impl NuCompleter {
|
|||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||||
let initial_line = line.to_string();
|
let initial_line = line.to_string();
|
||||||
new_line.push(b'a');
|
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||||
|
new_line.insert(alias_total_offset + pos, b'a');
|
||||||
let pos = offset + pos;
|
let pos = offset + pos;
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
@ -169,9 +170,10 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses the prefix
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
prefix.remove(pos - (flat.0.start - span_offset));
|
let index = pos - (flat.0.start - span_offset);
|
||||||
|
prefix.drain(index..);
|
||||||
|
|
||||||
// Variables completion
|
// Variables completion
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
@ -211,7 +213,11 @@ impl NuCompleter {
|
|||||||
// We got no results for internal completion
|
// We got no results for internal completion
|
||||||
// now we can check if external completer is set and use it
|
// now we can check if external completer is set and use it
|
||||||
if let Some(block_id) = config.external_completer {
|
if let Some(block_id) = config.external_completer {
|
||||||
return self.external_completion(block_id, spans, offset, new_span);
|
if let Some(external_result) =
|
||||||
|
self.external_completion(block_id, &spans, offset, new_span)
|
||||||
|
{
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,6 +344,15 @@ impl NuCompleter {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to complete using an external completer (if set)
|
||||||
|
if let Some(block_id) = config.external_completer {
|
||||||
|
if let Some(external_result) =
|
||||||
|
self.external_completion(block_id, &spans, offset, new_span)
|
||||||
|
{
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for file completion
|
// Check for file completion
|
||||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||||
out = self.process_completion(
|
out = self.process_completion(
|
||||||
@ -352,12 +367,6 @@ impl NuCompleter {
|
|||||||
if !out.is_empty() {
|
if !out.is_empty() {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to complete using an exnternal compelter (if set)
|
|
||||||
if let Some(block_id) = config.external_completer {
|
|
||||||
return self
|
|
||||||
.external_completion(block_id, spans, offset, new_span);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -422,7 +431,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
|
|||||||
}
|
}
|
||||||
// Push the rest to names vector.
|
// Push the rest to names vector.
|
||||||
if pos < input.len() {
|
if pos < input.len() {
|
||||||
vec_names.push((&input[pos..]).to_owned());
|
vec_names.push(input[pos..].to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
for name in &vec_names {
|
for name in &vec_names {
|
||||||
|
@ -23,7 +23,6 @@ pub fn read_plugin_file(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
plugin_file: Option<Spanned<String>>,
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
is_perf_true: bool,
|
|
||||||
) {
|
) {
|
||||||
// Reading signatures from signature file
|
// Reading signatures from signature file
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
@ -31,7 +30,7 @@ pub fn read_plugin_file(
|
|||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
let plugin_path = engine_state.plugin_signatures.clone();
|
||||||
if let Some(plugin_path) = plugin_path {
|
if let Some(plugin_path) = plugin_path {
|
||||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
let plugin_filename = plugin_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -44,10 +43,8 @@ pub fn read_plugin_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn add_plugin_file(
|
pub fn add_plugin_file(
|
||||||
@ -80,7 +77,7 @@ pub fn eval_config_contents(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) {
|
) {
|
||||||
if config_path.exists() & config_path.is_file() {
|
if config_path.exists() & config_path.is_file() {
|
||||||
let config_filename = config_path.to_string_lossy().to_owned();
|
let config_filename = config_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&config_path) {
|
if let Ok(contents) = std::fs::read(&config_path) {
|
||||||
eval_source(
|
eval_source(
|
||||||
|
@ -19,7 +19,6 @@ pub fn evaluate_file(
|
|||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
@ -54,9 +53,7 @@ pub fn evaluate_file(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -372,7 +372,7 @@ impl DescriptionMenu {
|
|||||||
let description = self
|
let description = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.skip(self.skipped_rows)
|
.skip(self.skipped_rows)
|
||||||
.take(self.working_details.description_rows)
|
.take(self.working_details.description_rows)
|
||||||
@ -610,7 +610,7 @@ impl Menu for DescriptionMenu {
|
|||||||
let description_rows = self
|
let description_rows = self
|
||||||
.get_value()
|
.get_value()
|
||||||
.and_then(|suggestion| suggestion.description)
|
.and_then(|suggestion| suggestion.description)
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub struct NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: Option<String>,
|
default_vi_insert_prompt_indicator: Option<String>,
|
||||||
default_vi_normal_prompt_indicator: Option<String>,
|
default_vi_normal_prompt_indicator: Option<String>,
|
||||||
default_multiline_indicator: Option<String>,
|
default_multiline_indicator: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NushellPrompt {
|
impl Default for NushellPrompt {
|
||||||
@ -34,6 +35,7 @@ impl NushellPrompt {
|
|||||||
default_vi_insert_prompt_indicator: None,
|
default_vi_insert_prompt_indicator: None,
|
||||||
default_vi_normal_prompt_indicator: None,
|
default_vi_normal_prompt_indicator: None,
|
||||||
default_multiline_indicator: None,
|
default_multiline_indicator: None,
|
||||||
|
render_right_prompt_on_last_line: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +43,13 @@ impl NushellPrompt {
|
|||||||
self.left_prompt_string = prompt_string;
|
self.left_prompt_string = prompt_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
pub fn update_prompt_right(
|
||||||
|
&mut self,
|
||||||
|
prompt_string: Option<String>,
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
|
) {
|
||||||
self.right_prompt_string = prompt_string;
|
self.right_prompt_string = prompt_string;
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||||
@ -68,6 +75,7 @@ impl NushellPrompt {
|
|||||||
prompt_indicator_string: Option<String>,
|
prompt_indicator_string: Option<String>,
|
||||||
prompt_multiline_indicator_string: Option<String>,
|
prompt_multiline_indicator_string: Option<String>,
|
||||||
prompt_vi: (Option<String>, Option<String>),
|
prompt_vi: (Option<String>, Option<String>),
|
||||||
|
render_right_prompt_on_last_line: bool,
|
||||||
) {
|
) {
|
||||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||||
|
|
||||||
@ -78,6 +86,8 @@ impl NushellPrompt {
|
|||||||
|
|
||||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||||
|
|
||||||
|
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||||
@ -162,4 +172,8 @@ impl Prompt for NushellPrompt {
|
|||||||
prefix, history_search.term
|
prefix, history_search.term
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn right_prompt_on_last_line(&self) -> bool {
|
||||||
|
self.render_right_prompt_on_last_line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ fn get_prompt_string(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
stack
|
stack
|
||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
@ -44,14 +43,12 @@ fn get_prompt_string(
|
|||||||
block,
|
block,
|
||||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||||
);
|
);
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
info!(
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
match ret_val {
|
match ret_val {
|
||||||
Ok(ret_val) => Some(ret_val),
|
Ok(ret_val) => Some(ret_val),
|
||||||
@ -90,17 +87,10 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
nu_prompt: &'prompt mut NushellPrompt,
|
nu_prompt: &'prompt mut NushellPrompt,
|
||||||
is_perf_true: bool,
|
|
||||||
) -> &'prompt dyn Prompt {
|
) -> &'prompt dyn Prompt {
|
||||||
let mut stack = stack.clone();
|
let mut stack = stack.clone();
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
||||||
PROMPT_COMMAND,
|
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now that we have the prompt string lets ansify it.
|
// Now that we have the prompt string lets ansify it.
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
@ -116,45 +106,20 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
left_prompt_string
|
left_prompt_string
|
||||||
};
|
};
|
||||||
|
|
||||||
let right_prompt_string = get_prompt_string(
|
let right_prompt_string =
|
||||||
PROMPT_COMMAND_RIGHT,
|
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_indicator_string = get_prompt_string(
|
let prompt_indicator_string =
|
||||||
PROMPT_INDICATOR,
|
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_multiline_string = get_prompt_string(
|
let prompt_multiline_string =
|
||||||
PROMPT_MULTILINE_INDICATOR,
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_insert_string = get_prompt_string(
|
let prompt_vi_insert_string =
|
||||||
PROMPT_INDICATOR_VI_INSERT,
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt_vi_normal_string = get_prompt_string(
|
let prompt_vi_normal_string =
|
||||||
PROMPT_INDICATOR_VI_NORMAL,
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
||||||
config,
|
|
||||||
engine_state,
|
|
||||||
&mut stack,
|
|
||||||
is_perf_true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
@ -163,12 +128,11 @@ pub(crate) fn update_prompt<'prompt>(
|
|||||||
prompt_indicator_string,
|
prompt_indicator_string,
|
||||||
prompt_multiline_string,
|
prompt_multiline_string,
|
||||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||||
|
config.render_right_prompt_on_last_line,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_val = nu_prompt as &dyn Prompt;
|
let ret_val = nu_prompt as &dyn Prompt;
|
||||||
if is_perf_true {
|
|
||||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ pub(crate) fn add_menus(
|
|||||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
for menu in create_menus(&value, config)? {
|
for menu in create_menus(&value)? {
|
||||||
line_editor =
|
line_editor =
|
||||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||||
}
|
}
|
||||||
@ -491,7 +491,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]),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -666,6 +666,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
KeyCode::Char(char)
|
KeyCode::Char(char)
|
||||||
}
|
}
|
||||||
|
"space" => KeyCode::Char(' '),
|
||||||
"down" => KeyCode::Down,
|
"down" => KeyCode::Down,
|
||||||
"up" => KeyCode::Up,
|
"up" => KeyCode::Up,
|
||||||
"left" => KeyCode::Left,
|
"left" => KeyCode::Left,
|
||||||
@ -821,6 +822,8 @@ fn event_from_record(
|
|||||||
"ctrld" => ReedlineEvent::CtrlD,
|
"ctrld" => ReedlineEvent::CtrlD,
|
||||||
"ctrlc" => ReedlineEvent::CtrlC,
|
"ctrlc" => ReedlineEvent::CtrlC,
|
||||||
"enter" => ReedlineEvent::Enter,
|
"enter" => ReedlineEvent::Enter,
|
||||||
|
"submit" => ReedlineEvent::Submit,
|
||||||
|
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||||
"esc" | "escape" => ReedlineEvent::Esc,
|
"esc" | "escape" => ReedlineEvent::Esc,
|
||||||
"up" => ReedlineEvent::Up,
|
"up" => ReedlineEvent::Up,
|
||||||
"down" => ReedlineEvent::Down,
|
"down" => ReedlineEvent::Down,
|
||||||
@ -961,6 +964,7 @@ fn edit_from_record(
|
|||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftBefore(char)
|
EditCommand::MoveLeftBefore(char)
|
||||||
}
|
}
|
||||||
|
"complete" => EditCommand::Complete,
|
||||||
e => {
|
e => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
"reedline EditCommand".to_string(),
|
"reedline EditCommand".to_string(),
|
||||||
|
@ -11,17 +11,19 @@ use log::{info, trace, warn};
|
|||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_color_config::get_color_config;
|
use nu_color_config::get_color_config;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::{lex, parse};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::PathMember,
|
ast::PathMember,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
||||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||||
Spanned, Type, Value, VarId,
|
Spanned, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
use reedline::{DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
|
||||||
use std::io::{self, Write};
|
use std::{
|
||||||
use std::{sync::atomic::Ordering, time::Instant};
|
io::{self, Write},
|
||||||
use strip_ansi_escapes::strip;
|
sync::atomic::Ordering,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
use sysinfo::SystemExt;
|
use sysinfo::SystemExt;
|
||||||
|
|
||||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||||
@ -38,7 +40,6 @@ pub fn evaluate_repl(
|
|||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
nushell_path: &str,
|
nushell_path: &str,
|
||||||
is_perf_true: bool,
|
|
||||||
prerun_command: Option<Spanned<String>>,
|
prerun_command: Option<Spanned<String>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||||
@ -48,7 +49,7 @@ pub fn evaluate_repl(
|
|||||||
if !atty::is(atty::Stream::Stdin) {
|
if !atty::is(atty::Stream::Stdin) {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::NotFound,
|
std::io::ErrorKind::NotFound,
|
||||||
"Nushell launched as interactive REPL but STDIN is not a TTY, either launch in a valid terminal or provide arguments to invoke a script!",
|
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
|
||||||
))
|
))
|
||||||
.into_diagnostic();
|
.into_diagnostic();
|
||||||
}
|
}
|
||||||
@ -57,14 +58,12 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let mut nu_prompt = NushellPrompt::new();
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
info!(
|
||||||
"translate environment vars {}:{}:{}",
|
"translate environment vars {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
@ -89,31 +88,32 @@ pub fn evaluate_repl(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
info!(
|
||||||
"load config initially {}:{}:{}",
|
"load config initially {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Get the config once for the history `max_history_size`
|
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||||
// Updating that will not be possible in one session
|
|
||||||
|
let mut line_editor = Reedline::create();
|
||||||
|
|
||||||
|
// Now that reedline is created, get the history session id and store it in engine_state
|
||||||
|
let hist_sesh = match line_editor.get_history_session_id() {
|
||||||
|
Some(id) => i64::from(id),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
engine_state.history_session_id = hist_sesh;
|
||||||
|
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
|
||||||
}
|
|
||||||
let mut line_editor = Reedline::create();
|
|
||||||
let history_path = crate::config_files::get_history_path(
|
let history_path = crate::config_files::get_history_path(
|
||||||
nushell_path,
|
nushell_path,
|
||||||
engine_state.config.history_file_format,
|
engine_state.config.history_file_format,
|
||||||
);
|
);
|
||||||
if let Some(history_path) = history_path.as_deref() {
|
if let Some(history_path) = history_path.as_deref() {
|
||||||
if is_perf_true {
|
|
||||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||||
HistoryFileFormat::PlainText => Box::new(
|
HistoryFileFormat::PlainText => Box::new(
|
||||||
@ -139,15 +139,7 @@ pub fn evaluate_repl(
|
|||||||
if use_ansi {
|
if use_ansi {
|
||||||
println!("{}", banner);
|
println!("{}", banner);
|
||||||
} else {
|
} else {
|
||||||
let stripped_string = {
|
println!("{}", nu_utils::strip_ansi_string_likely(banner));
|
||||||
if let Ok(bytes) = strip(&banner) {
|
|
||||||
String::from_utf8_lossy(&bytes).to_string()
|
|
||||||
} else {
|
|
||||||
banner
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{}", stripped_string);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,14 +155,12 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
info!(
|
||||||
"load config each loop {}:{}:{}",
|
"load config each loop {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||||
|
|
||||||
@ -191,15 +181,11 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
let color_hm = get_color_config(config);
|
let color_hm = get_color_config(config);
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||||
line_editor = line_editor
|
line_editor = line_editor
|
||||||
.with_highlighter(Box::new(NuHighlighter {
|
.with_highlighter(Box::new(NuHighlighter {
|
||||||
@ -256,18 +242,14 @@ pub fn evaluate_repl(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if config.sync_history_on_enter {
|
if config.sync_history_on_enter {
|
||||||
if is_perf_true {
|
|
||||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = line_editor.sync_history() {
|
if let Err(e) = line_editor.sync_history() {
|
||||||
warn!("Failed to sync history: {}", e);
|
warn!("Failed to sync history: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
// Changing the line editor based on the found keybindings
|
// Changing the line editor based on the found keybindings
|
||||||
line_editor = match create_keybindings(config) {
|
line_editor = match create_keybindings(config) {
|
||||||
@ -291,14 +273,12 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
||||||
}
|
|
||||||
|
|
||||||
// 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, vec![], &hook) {
|
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,25 +293,23 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let prompt =
|
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
|
||||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
|
||||||
|
|
||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
if is_perf_true {
|
|
||||||
info!(
|
info!(
|
||||||
"finished setup, starting repl {}:{}:{}",
|
"finished setup, starting repl {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let input = line_editor.read_line(prompt);
|
let input = line_editor.read_line(prompt);
|
||||||
let shell_integration = config.shell_integration;
|
let shell_integration = config.shell_integration;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
Ok(Signal::Success(s)) => {
|
||||||
|
let hostname = sys.host_name();
|
||||||
let history_supports_meta =
|
let history_supports_meta =
|
||||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||||
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
|
||||||
@ -339,7 +317,7 @@ pub fn evaluate_repl(
|
|||||||
line_editor
|
line_editor
|
||||||
.update_last_command_context(&|mut c| {
|
.update_last_command_context(&|mut c| {
|
||||||
c.start_timestamp = Some(chrono::Utc::now());
|
c.start_timestamp = Some(chrono::Utc::now());
|
||||||
c.hostname = sys.host_name();
|
c.hostname = hostname.clone();
|
||||||
|
|
||||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||||
c
|
c
|
||||||
@ -347,10 +325,16 @@ pub fn evaluate_repl(
|
|||||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex")
|
||||||
|
.replace(line_editor.current_buffer_contents().to_string());
|
||||||
|
|
||||||
// Right before we start running the code the user gave us,
|
// Right before we start running the code the user gave us,
|
||||||
// fire the "pre_execution" hook
|
// fire the "pre_execution" hook
|
||||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,9 +347,13 @@ pub fn evaluate_repl(
|
|||||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||||
// Check if this is a single call to a directory, if so auto-cd
|
// Check if this is a single call to a directory, if so auto-cd
|
||||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||||
let path = nu_path::expand_path_with(&s, &cwd);
|
|
||||||
|
|
||||||
let orig = s.clone();
|
let mut orig = s.clone();
|
||||||
|
if orig.starts_with('`') {
|
||||||
|
orig = trim_quotes_str(&orig).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = nu_path::expand_path_with(&orig, &cwd);
|
||||||
|
|
||||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||||
// We have an auto-cd
|
// We have an auto-cd
|
||||||
@ -435,7 +423,7 @@ pub fn evaluate_repl(
|
|||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else if !s.trim().is_empty() {
|
||||||
trace!("eval source: {}", s);
|
trace!("eval source: {}", s);
|
||||||
|
|
||||||
eval_source(
|
eval_source(
|
||||||
@ -473,6 +461,21 @@ pub fn evaluate_repl(
|
|||||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||||
let path = cwd.as_string()?;
|
let path = cwd.as_string()?;
|
||||||
|
|
||||||
|
// Communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||||
|
run_ansi_sequence(&format!(
|
||||||
|
"\x1b]7;file://{}{}{}\x1b\\",
|
||||||
|
percent_encoding::utf8_percent_encode(
|
||||||
|
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
||||||
|
percent_encoding::CONTROLS
|
||||||
|
),
|
||||||
|
if path.starts_with('/') { "" } else { "/" },
|
||||||
|
percent_encoding::utf8_percent_encode(
|
||||||
|
&path,
|
||||||
|
percent_encoding::CONTROLS
|
||||||
|
)
|
||||||
|
))?;
|
||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||||
path.replace(&p.as_path().display().to_string(), "~")
|
path.replace(&p.as_path().display().to_string(), "~")
|
||||||
@ -489,6 +492,24 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut ops = engine_state
|
||||||
|
.repl_operation_queue
|
||||||
|
.lock()
|
||||||
|
.expect("repl op queue mutex");
|
||||||
|
while let Some(op) = ops.pop_front() {
|
||||||
|
match op {
|
||||||
|
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
||||||
|
EditCommand::MoveToEnd,
|
||||||
|
EditCommand::InsertString(s),
|
||||||
|
]),
|
||||||
|
ReplOperation::Insert(s) => {
|
||||||
|
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
||||||
|
}
|
||||||
|
ReplOperation::Replace(s) => line_editor
|
||||||
|
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlC) => {
|
Ok(Signal::CtrlC) => {
|
||||||
// `Reedline` clears the line content. New prompt is shown
|
// `Reedline` clears the line content. New prompt is shown
|
||||||
@ -528,7 +549,7 @@ fn get_banner(engine_state: &mut EngineState, stack: &mut Stack) -> String {
|
|||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
None,
|
None,
|
||||||
"(date now) - ('05/10/2019' | into datetime)",
|
"(date now) - ('2019-05-10 09:59:12-0700' | into datetime)",
|
||||||
) {
|
) {
|
||||||
Ok(Value::Duration { val, .. }) => format_duration(val),
|
Ok(Value::Duration { val, .. }) => format_duration(val),
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
@ -545,13 +566,13 @@ Our {}GitHub{} repository is at {}https://github.com/nushell/nushell{}
|
|||||||
Our {}Documentation{} is located at {}http://nushell.sh{}
|
Our {}Documentation{} is located at {}http://nushell.sh{}
|
||||||
{}Tweet{} us at {}@nu_shell{}
|
{}Tweet{} us at {}@nu_shell{}
|
||||||
|
|
||||||
{}Nushell{} has been around for:
|
It's been this long since {}Nushell{}'s first commit:
|
||||||
{}
|
{}
|
||||||
|
|
||||||
{}You can disable this banner using the {}config nu{}{} command
|
{}You can disable this banner using the {}config nu{}{} command
|
||||||
to modify the config.nu file and setting show_banner to false.
|
to modify the config.nu file and setting show_banner to false.
|
||||||
|
|
||||||
let-env config {{
|
let-env config = {{
|
||||||
show_banner: false
|
show_banner: false
|
||||||
...
|
...
|
||||||
}}{}
|
}}{}
|
||||||
@ -612,9 +633,7 @@ pub fn eval_string_with_input(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(delta) {
|
engine_state.merge_delta(delta)?;
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_as_pipeline_data = match input {
|
let input_as_pipeline_data = match input {
|
||||||
Some(input) => PipelineData::Value(input, None),
|
Some(input) => PipelineData::Value(input, None),
|
||||||
@ -667,6 +686,7 @@ pub fn eval_env_change_hook(
|
|||||||
eval_hook(
|
eval_hook(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
|
None,
|
||||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||||
hook_value,
|
hook_value,
|
||||||
)?;
|
)?;
|
||||||
@ -692,15 +712,17 @@ pub fn eval_env_change_hook(
|
|||||||
pub fn eval_hook(
|
pub fn eval_hook(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
input: Option<PipelineData>,
|
||||||
arguments: Vec<(String, Value)>,
|
arguments: Vec<(String, Value)>,
|
||||||
value: &Value,
|
value: &Value,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let value_span = value.span()?;
|
let value_span = value.span()?;
|
||||||
|
|
||||||
let condition_path = PathMember::String {
|
let condition_path = PathMember::String {
|
||||||
val: "condition".to_string(),
|
val: "condition".to_string(),
|
||||||
span: value_span,
|
span: value_span,
|
||||||
};
|
};
|
||||||
|
let mut output = PipelineData::new(Span::new(0, 0));
|
||||||
|
|
||||||
let code_path = PathMember::String {
|
let code_path = PathMember::String {
|
||||||
val: "code".to_string(),
|
val: "code".to_string(),
|
||||||
@ -710,7 +732,7 @@ pub fn eval_hook(
|
|||||||
match value {
|
match value {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for val in vals {
|
for val in vals {
|
||||||
eval_hook(engine_state, stack, arguments.clone(), val)?
|
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
@ -726,6 +748,7 @@ pub fn eval_hook(
|
|||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
block_id,
|
||||||
|
None,
|
||||||
arguments.clone(),
|
arguments.clone(),
|
||||||
block_span,
|
block_span,
|
||||||
) {
|
) {
|
||||||
@ -805,7 +828,9 @@ pub fn eval_hook(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(_) => {}
|
Ok(pipeline_data) => {
|
||||||
|
output = pipeline_data;
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
@ -820,7 +845,14 @@ pub fn eval_hook(
|
|||||||
span: block_span,
|
span: block_span,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
|
run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
block_span,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
@ -837,7 +869,17 @@ pub fn eval_hook(
|
|||||||
span: block_span,
|
span: block_span,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
|
output = PipelineData::Value(
|
||||||
|
run_hook_block(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
*block_id,
|
||||||
|
input,
|
||||||
|
arguments,
|
||||||
|
*block_span,
|
||||||
|
)?,
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue(
|
return Err(ShellError::UnsupportedConfigValue(
|
||||||
@ -851,19 +893,20 @@ pub fn eval_hook(
|
|||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||||
engine_state.merge_env(stack, cwd)?;
|
engine_state.merge_env(stack, cwd)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_hook_block(
|
pub fn run_hook_block(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
optional_input: Option<PipelineData>,
|
||||||
arguments: Vec<(String, Value)>,
|
arguments: Vec<(String, Value)>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let input = PipelineData::new(span);
|
let input = optional_input.unwrap_or_else(|| PipelineData::new(span));
|
||||||
|
|
||||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::get_shape_color;
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
|
use nu_protocol::ast::{Argument, Block, Expr, Expression};
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
use nu_protocol::Config;
|
use nu_protocol::{Config, Span};
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
|
|
||||||
pub struct NuHighlighter {
|
pub struct NuHighlighter {
|
||||||
@ -15,10 +16,12 @@ impl Highlighter for NuHighlighter {
|
|||||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||||
trace!("highlighting: {}", line);
|
trace!("highlighting: {}", line);
|
||||||
|
|
||||||
let (shapes, global_span_offset) = {
|
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
|
let block = {
|
||||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
let (shapes, global_span_offset) = {
|
||||||
let shapes = flatten_block(&working_set, &block);
|
let shapes = flatten_block(&working_set, &block);
|
||||||
(shapes, self.engine_state.next_span_start())
|
(shapes, self.engine_state.next_span_start())
|
||||||
};
|
};
|
||||||
@ -26,6 +29,15 @@ impl Highlighter for NuHighlighter {
|
|||||||
let mut output = StyledText::default();
|
let mut output = StyledText::default();
|
||||||
let mut last_seen_span = global_span_offset;
|
let mut last_seen_span = global_span_offset;
|
||||||
|
|
||||||
|
let global_cursor_offset = _cursor + global_span_offset;
|
||||||
|
let matching_brackets_pos = find_matching_brackets(
|
||||||
|
line,
|
||||||
|
&working_set,
|
||||||
|
&block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
);
|
||||||
|
|
||||||
for shape in &shapes {
|
for shape in &shapes {
|
||||||
if shape.0.end <= last_seen_span
|
if shape.0.end <= last_seen_span
|
||||||
|| last_seen_span < global_span_offset
|
|| last_seen_span < global_span_offset
|
||||||
@ -44,166 +56,71 @@ impl Highlighter for NuHighlighter {
|
|||||||
let next_token = line
|
let next_token = line
|
||||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
macro_rules! add_colored_token_with_bracket_highlight {
|
||||||
|
($shape:expr, $span:expr, $text:expr) => {{
|
||||||
|
let spans = split_span_by_highlight_positions(
|
||||||
|
line,
|
||||||
|
&$span,
|
||||||
|
&matching_brackets_pos,
|
||||||
|
global_span_offset,
|
||||||
|
);
|
||||||
|
spans.iter().for_each(|(part, highlight)| {
|
||||||
|
let start = part.start - $span.start;
|
||||||
|
let end = part.end - $span.start;
|
||||||
|
let text = (&next_token[start..end]).to_string();
|
||||||
|
let mut style = get_shape_color($shape.to_string(), &self.config);
|
||||||
|
if *highlight {
|
||||||
|
style = get_matching_brackets_style(style, &self.config);
|
||||||
|
}
|
||||||
|
output.push((style, text));
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_colored_token {
|
||||||
|
($shape:expr, $text:expr) => {
|
||||||
|
output.push((get_shape_color($shape.to_string(), &self.config), $text))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match shape.1 {
|
match shape.1 {
|
||||||
FlatShape::Garbage => output.push((
|
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Garbage
|
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Binary => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::Bool => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Int => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Nothing => output.push((
|
FlatShape::Float => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Nothing
|
FlatShape::Range => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::External => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Binary => {
|
FlatShape::Literal => add_colored_token!(shape.1, next_token),
|
||||||
// nushell ?
|
FlatShape::Operator => add_colored_token!(shape.1, next_token),
|
||||||
output.push((
|
FlatShape::Signature => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::String => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
|
||||||
))
|
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
|
||||||
}
|
|
||||||
FlatShape::Bool => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Int => {
|
|
||||||
// nushell Int
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Float => {
|
|
||||||
// nushell Decimal
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Range => output.push((
|
|
||||||
// nushell DotDot ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::InternalCall => output.push((
|
|
||||||
// nushell InternalCommand
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::External => {
|
|
||||||
// nushell ExternalCommand
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::ExternalArg => {
|
|
||||||
// nushell ExternalWord
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Literal => {
|
|
||||||
// nushell ?
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Operator => output.push((
|
|
||||||
// nushell Operator
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Signature => output.push((
|
|
||||||
// nushell ?
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::String => {
|
|
||||||
// nushell String
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::StringInterpolation => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::DateTime => {
|
|
||||||
// nushell ???
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::List => {
|
FlatShape::List => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Table => {
|
FlatShape::Table => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Record => {
|
FlatShape::Record => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatShape::Block => {
|
FlatShape::Block => {
|
||||||
// nushell ???
|
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
FlatShape::Filepath => output.push((
|
|
||||||
// nushell Path
|
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
FlatShape::Directory => add_colored_token!(shape.1, next_token),
|
||||||
next_token,
|
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||||
)),
|
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||||
FlatShape::Directory => output.push((
|
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||||
// nushell Directory
|
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::GlobPattern => output.push((
|
|
||||||
// nushell GlobPattern
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Variable => output.push((
|
|
||||||
// nushell Variable
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
FlatShape::Flag => {
|
|
||||||
// nushell Flag
|
|
||||||
output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
FlatShape::Custom(..) => output.push((
|
|
||||||
get_shape_color(shape.1.to_string(), &self.config),
|
|
||||||
next_token,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
last_seen_span = shape.0.end;
|
last_seen_span = shape.0.end;
|
||||||
}
|
}
|
||||||
@ -216,3 +133,297 @@ impl Highlighter for NuHighlighter {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_span_by_highlight_positions(
|
||||||
|
line: &str,
|
||||||
|
span: &Span,
|
||||||
|
highlight_positions: &Vec<usize>,
|
||||||
|
global_span_offset: usize,
|
||||||
|
) -> Vec<(Span, bool)> {
|
||||||
|
let mut start = span.start;
|
||||||
|
let mut result: Vec<(Span, bool)> = Vec::new();
|
||||||
|
for pos in highlight_positions {
|
||||||
|
if start <= *pos && pos < &span.end {
|
||||||
|
if start < *pos {
|
||||||
|
result.push((Span { start, end: *pos }, false));
|
||||||
|
}
|
||||||
|
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
||||||
|
let end = span_str
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.map(|c| pos + get_char_length(c))
|
||||||
|
.unwrap_or(pos + 1);
|
||||||
|
result.push((Span { start: *pos, end }, true));
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < span.end {
|
||||||
|
result.push((
|
||||||
|
Span {
|
||||||
|
start,
|
||||||
|
end: span.end,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_brackets(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Vec<usize> {
|
||||||
|
const BRACKETS: &str = "{}[]()";
|
||||||
|
|
||||||
|
// calculate first bracket position
|
||||||
|
let global_end_offset = line.len() + global_span_offset;
|
||||||
|
let global_bracket_pos =
|
||||||
|
if global_cursor_offset == global_end_offset && global_end_offset > global_span_offset {
|
||||||
|
// cursor is at the end of a non-empty string -- find block end at the previous position
|
||||||
|
if let Some(last_char) = line.chars().last() {
|
||||||
|
global_cursor_offset - get_char_length(last_char)
|
||||||
|
} else {
|
||||||
|
global_cursor_offset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// cursor is in the middle of a string -- find block end at the current position
|
||||||
|
global_cursor_offset
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that position contains bracket
|
||||||
|
let match_idx = global_bracket_pos - global_span_offset;
|
||||||
|
if match_idx >= line.len()
|
||||||
|
|| !BRACKETS.contains(get_char_at_index(line, match_idx).unwrap_or_default())
|
||||||
|
{
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// find matching bracket by finding matching block end
|
||||||
|
let matching_block_end = find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
block,
|
||||||
|
global_span_offset,
|
||||||
|
global_bracket_pos,
|
||||||
|
);
|
||||||
|
if let Some(pos) = matching_block_end {
|
||||||
|
let matching_idx = pos - global_span_offset;
|
||||||
|
if BRACKETS.contains(get_char_at_index(line, matching_idx).unwrap_or_default()) {
|
||||||
|
return if global_bracket_pos < pos {
|
||||||
|
vec![global_bracket_pos, pos]
|
||||||
|
} else {
|
||||||
|
vec![pos, global_bracket_pos]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_block(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Block,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
for p in &block.pipelines {
|
||||||
|
for e in &p.expressions {
|
||||||
|
if e.span.contains(global_cursor_offset) {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
e,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_block_end_in_expr(
|
||||||
|
line: &str,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expression: &Expression,
|
||||||
|
global_span_offset: usize,
|
||||||
|
global_cursor_offset: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
macro_rules! find_in_expr_or_continue {
|
||||||
|
($inner_expr:ident) => {
|
||||||
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
$inner_expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
) {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
|
||||||
|
{
|
||||||
|
let expr_first = expression.span.start;
|
||||||
|
let span_str = &line
|
||||||
|
[expression.span.start - global_span_offset..expression.span.end - global_span_offset];
|
||||||
|
let expr_last = span_str
|
||||||
|
.chars()
|
||||||
|
.last()
|
||||||
|
.map(|c| expression.span.end - get_char_length(c))
|
||||||
|
.unwrap_or(expression.span.start);
|
||||||
|
|
||||||
|
return match &expression.expr {
|
||||||
|
Expr::Bool(_) => None,
|
||||||
|
Expr::Int(_) => None,
|
||||||
|
Expr::Float(_) => None,
|
||||||
|
Expr::Binary(_) => None,
|
||||||
|
Expr::Range(..) => None,
|
||||||
|
Expr::Var(_) => None,
|
||||||
|
Expr::VarDecl(_) => None,
|
||||||
|
Expr::ExternalCall(..) => None,
|
||||||
|
Expr::Operator(_) => None,
|
||||||
|
Expr::UnaryNot(_) => None,
|
||||||
|
Expr::Keyword(..) => None,
|
||||||
|
Expr::ValueWithUnit(..) => None,
|
||||||
|
Expr::DateTime(_) => None,
|
||||||
|
Expr::Filepath(_) => None,
|
||||||
|
Expr::Directory(_) => None,
|
||||||
|
Expr::GlobPattern(_) => None,
|
||||||
|
Expr::String(_) => None,
|
||||||
|
Expr::CellPath(_) => None,
|
||||||
|
Expr::ImportPattern(_) => None,
|
||||||
|
Expr::Overlay(_) => None,
|
||||||
|
Expr::Signature(_) => None,
|
||||||
|
Expr::Nothing => None,
|
||||||
|
Expr::Garbage => None,
|
||||||
|
|
||||||
|
Expr::Table(hdr, rows) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at table end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at table start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside table
|
||||||
|
for inner_expr in hdr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
for row in rows {
|
||||||
|
for inner_expr in row {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Record(exprs) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at record end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at record start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside record
|
||||||
|
for (k, v) in exprs {
|
||||||
|
find_in_expr_or_continue!(k);
|
||||||
|
find_in_expr_or_continue!(v);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Call(call) => {
|
||||||
|
for arg in &call.arguments {
|
||||||
|
let opt_expr = match arg {
|
||||||
|
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||||
|
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(inner_expr) = opt_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
&b.head,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
),
|
||||||
|
|
||||||
|
Expr::BinaryOp(lhs, op, rhs) => {
|
||||||
|
find_in_expr_or_continue!(lhs);
|
||||||
|
find_in_expr_or_continue!(op);
|
||||||
|
find_in_expr_or_continue!(rhs);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Block(block_id)
|
||||||
|
| Expr::RowCondition(block_id)
|
||||||
|
| Expr::Subexpression(block_id) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at block end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at block start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside block
|
||||||
|
let nested_block = working_set.get_block(*block_id);
|
||||||
|
find_matching_block_end_in_block(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
nested_block,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::StringInterpolation(inner_expr) => {
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::List(inner_expr) => {
|
||||||
|
if expr_last == global_cursor_offset {
|
||||||
|
// cursor is at list end
|
||||||
|
Some(expr_first)
|
||||||
|
} else if expr_first == global_cursor_offset {
|
||||||
|
// cursor is at list start
|
||||||
|
Some(expr_last)
|
||||||
|
} else {
|
||||||
|
// cursor is inside list
|
||||||
|
for inner_expr in inner_expr {
|
||||||
|
find_in_expr_or_continue!(inner_expr);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_at_index(s: &str, index: usize) -> Option<char> {
|
||||||
|
s[index..].chars().next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char_length(c: char) -> usize {
|
||||||
|
c.to_string().len()
|
||||||
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use log::trace;
|
use crate::repl::eval_hook;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use nu_protocol::CliError;
|
use nu_protocol::CliError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, ShellError, Span, Value,
|
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
@ -204,8 +204,6 @@ pub fn eval_source(
|
|||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
trace!("eval_source");
|
|
||||||
|
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let (output, err) = parse(
|
let (output, err) = parse(
|
||||||
@ -231,24 +229,42 @@ pub fn eval_source(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||||
Ok(mut pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
let config = engine_state.get_config();
|
||||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
let result;
|
||||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
if let PipelineData::ExternalStream {
|
||||||
} else {
|
stdout: stream,
|
||||||
set_last_exit_code(stack, 0);
|
stderr: stderr_stream,
|
||||||
|
exit_code,
|
||||||
|
..
|
||||||
|
} = pipeline_data
|
||||||
|
{
|
||||||
|
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
||||||
|
} else if let Some(hook) = config.hooks.display_output.clone() {
|
||||||
|
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
|
||||||
|
Err(err) => {
|
||||||
|
result = Err(err);
|
||||||
|
}
|
||||||
|
Ok(val) => {
|
||||||
|
result = val.print(engine_state, stack, false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
set_last_exit_code(stack, 0);
|
result = pipeline_data.print(engine_state, stack, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
match result {
|
||||||
|
Err(err) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Ok(exit_code) => {
|
||||||
|
set_last_exit_code(stack, exit_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -34,8 +34,20 @@ fn completer_strings() -> NuCompleter {
|
|||||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variables_dollar_sign_with_varialblecompletion() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let target_dir = "$ ";
|
||||||
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
|
assert_eq!(7, suggestions.len());
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
|
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
let suggestions = completer.complete("tst --", 6);
|
let suggestions = completer.complete("tst --", 6);
|
||||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||||
// dbg!(&expected, &suggestions);
|
// dbg!(&expected, &suggestions);
|
||||||
@ -43,28 +55,30 @@ fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn variables_completions_single_dash_argument(mut completer: NuCompleter) {
|
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||||
let suggestions = completer.complete("tst -", 5);
|
let suggestions = completer.complete("tst -", 5);
|
||||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn variables_completions_command(mut completer_strings: NuCompleter) {
|
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||||
let suggestions = completer_strings.complete("my-command ", 9);
|
let suggestions = completer_strings.complete("my-c ", 4);
|
||||||
let expected: Vec<String> = vec!["my-command".into()];
|
let expected: Vec<String> = vec!["my-command".into()];
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn variables_completions_subcommands(mut completer_strings: NuCompleter) {
|
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||||
let suggestions = completer_strings.complete("my-command ", 11);
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn variables_completions_subcommands_2(mut completer_strings: NuCompleter) {
|
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||||
|
mut completer_strings: NuCompleter,
|
||||||
|
) {
|
||||||
let suggestions = completer_strings.complete("my-command ", 11);
|
let suggestions = completer_strings.complete("my-command ", 11);
|
||||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
@ -100,7 +114,7 @@ fn external_completer_trailing_space() {
|
|||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
let input = "gh alias ".to_string();
|
let input = "gh alias ".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(&block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
@ -112,7 +126,7 @@ fn external_completer_no_trailing_space() {
|
|||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
let input = "gh alias".to_string();
|
let input = "gh alias".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(&block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(2, suggestions.len());
|
assert_eq!(2, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||||
@ -123,7 +137,7 @@ fn external_completer_pass_flags() {
|
|||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "let external_completer = {|spans| $spans}";
|
||||||
let input = "gh api --".to_string();
|
let input = "gh api --".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(&block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
assert_eq!(3, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||||
@ -168,7 +182,7 @@ fn file_completions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_ls_completion() {
|
fn command_ls_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -200,7 +214,7 @@ fn command_ls_completion() {
|
|||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn command_open_completion() {
|
fn command_open_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -233,7 +247,7 @@ fn command_open_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_rm_completion() {
|
fn command_rm_with_globcompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -266,7 +280,7 @@ fn command_rm_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_cp_completion() {
|
fn command_cp_with_globcompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -299,7 +313,7 @@ fn command_cp_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_save_completion() {
|
fn command_save_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -332,7 +346,7 @@ fn command_save_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_touch_completion() {
|
fn command_touch_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -365,7 +379,7 @@ fn command_touch_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_watch_completion() {
|
fn command_watch_with_filecompletion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
@ -431,7 +445,7 @@ fn flag_completions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn folder_completions() {
|
fn folder_with_directorycompletions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, dir_str, engine, stack) = new_engine();
|
let (dir, dir_str, engine, stack) = new_engine();
|
||||||
|
|
||||||
@ -623,7 +637,7 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
|||||||
// Instatiate a new completer
|
// Instatiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||||
|
|
||||||
completer.complete(&input, input.len())
|
completer.complete(input, input.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -658,3 +672,63 @@ fn unknown_command_completion() {
|
|||||||
|
|
||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||||
|
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||||
|
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||||
|
let suggestions = completer.complete("tst -h | ls", 5);
|
||||||
|
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||||
|
match_suggestions(expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filecompletions_triggers_after_cursor() {
|
||||||
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
|
||||||
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||||
|
|
||||||
|
let suggestions = completer.complete("cp test_c", 3);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a\\".to_string(),
|
||||||
|
"test_b\\".to_string(),
|
||||||
|
"another\\".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder\\".to_string(),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"nushell".to_string(),
|
||||||
|
"test_a/".to_string(),
|
||||||
|
"test_b/".to_string(),
|
||||||
|
"another/".to_string(),
|
||||||
|
"custom_completion.nu".to_string(),
|
||||||
|
".hidden_file".to_string(),
|
||||||
|
".hidden_folder/".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
|
}
|
||||||
|
@ -104,9 +104,7 @@ pub fn merge_input(
|
|||||||
(block, working_set.render())
|
(block, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(delta) {
|
engine_state.merge_delta(delta)?;
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(eval_block(
|
assert!(eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
|
@ -5,11 +5,11 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.68.1"
|
version = "0.71.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
nu-json = { path = "../nu-json", version = "0.68.1" }
|
nu-json = { path = "../nu-json", version = "0.71.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.68.1" }
|
nu-table = { path = "../nu-table", version = "0.71.0" }
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
mod color_config;
|
mod color_config;
|
||||||
|
mod matching_brackets_style;
|
||||||
mod nu_style;
|
mod nu_style;
|
||||||
mod shape_color;
|
mod shape_color;
|
||||||
|
|
||||||
pub use color_config::*;
|
pub use color_config::*;
|
||||||
|
pub use matching_brackets_style::*;
|
||||||
pub use nu_style::*;
|
pub use nu_style::*;
|
||||||
pub use shape_color::*;
|
pub use shape_color::*;
|
||||||
|
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
30
crates/nu-color-config/src/matching_brackets_style.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::color_config::lookup_ansi_color_style;
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_protocol::Config;
|
||||||
|
|
||||||
|
pub fn get_matching_brackets_style(default_style: Style, conf: &Config) -> Style {
|
||||||
|
const MATCHING_BRACKETS_CONFIG_KEY: &str = "shape_matching_brackets";
|
||||||
|
|
||||||
|
match conf.color_config.get(MATCHING_BRACKETS_CONFIG_KEY) {
|
||||||
|
Some(int_color) => match int_color.as_string() {
|
||||||
|
Ok(int_color) => merge_styles(default_style, lookup_ansi_color_style(&int_color)),
|
||||||
|
Err(_) => default_style,
|
||||||
|
},
|
||||||
|
None => default_style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_styles(base: Style, extra: Style) -> Style {
|
||||||
|
Style {
|
||||||
|
foreground: extra.foreground.or(base.foreground),
|
||||||
|
background: extra.background.or(base.background),
|
||||||
|
is_bold: extra.is_bold || base.is_bold,
|
||||||
|
is_dimmed: extra.is_dimmed || base.is_dimmed,
|
||||||
|
is_italic: extra.is_italic || base.is_italic,
|
||||||
|
is_underline: extra.is_underline || base.is_underline,
|
||||||
|
is_blink: extra.is_blink || base.is_blink,
|
||||||
|
is_reverse: extra.is_reverse || base.is_reverse,
|
||||||
|
is_hidden: extra.is_hidden || base.is_hidden,
|
||||||
|
is_strikethrough: extra.is_strikethrough || base.is_strikethrough,
|
||||||
|
}
|
||||||
|
}
|
@ -5,27 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.68.1"
|
version = "0.71.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.68.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.71.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.68.1" }
|
nu-engine = { path = "../nu-engine", version = "0.71.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.68.1" }
|
nu-glob = { path = "../nu-glob", version = "0.71.0" }
|
||||||
nu-json = { path = "../nu-json", version = "0.68.1" }
|
nu-json = { path = "../nu-json", version = "0.71.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.68.1" }
|
nu-parser = { path = "../nu-parser", version = "0.71.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.68.1" }
|
nu-path = { path = "../nu-path", version = "0.71.0" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.68.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.71.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.68.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.71.0" }
|
||||||
nu-system = { path = "../nu-system", version = "0.68.1" }
|
nu-system = { path = "../nu-system", version = "0.71.0" }
|
||||||
nu-table = { path = "../nu-table", version = "0.68.1" }
|
nu-table = { path = "../nu-table", version = "0.71.0" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.68.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.71.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.68.1" }
|
nu-utils = { path = "../nu-utils", version = "0.71.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.68.1" }
|
|
||||||
nu-ansi-term = "0.46.0"
|
nu-ansi-term = "0.46.0"
|
||||||
num-format = { version = "0.4.0" }
|
num-format = { version = "0.4.3" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
@ -55,7 +54,7 @@ is-root = "0.1.2"
|
|||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
lscolors = { version = "0.12.0", features = ["crossterm"]}
|
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
|
||||||
md5 = { package = "md-5", version = "0.10.0" }
|
md5 = { package = "md-5", version = "0.10.0" }
|
||||||
meval = "0.2.0"
|
meval = "0.2.0"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
@ -70,6 +69,7 @@ rayon = "1.5.1"
|
|||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||||
roxmltree = "0.14.0"
|
roxmltree = "0.14.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
same-file = "1.0.6"
|
||||||
serde = { version="1.0.123", features=["derive"] }
|
serde = { version="1.0.123", features=["derive"] }
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
@ -77,8 +77,7 @@ serde_yaml = "0.9.4"
|
|||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
shadow-rs = { version = "0.16.1", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
strip-ansi-escapes = "0.1.1"
|
sysinfo = "0.26.2"
|
||||||
sysinfo = "0.25.2"
|
|
||||||
terminal_size = "0.2.1"
|
terminal_size = "0.2.1"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
titlecase = "2.0.0"
|
titlecase = "2.0.0"
|
||||||
@ -87,14 +86,18 @@ unicode-segmentation = "1.8.0"
|
|||||||
url = "2.2.1"
|
url = "2.2.1"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
which = { version = "4.3.0", optional = true }
|
which = { version = "4.3.0", optional = true }
|
||||||
reedline = { version = "0.11.0", features = ["bashisms", "sqlite"]}
|
reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]}
|
||||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
wax = { version = "0.5.0" }
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
sqlparser = { version = "0.23.0", features = ["serde"], optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "2.0.0"
|
umask = "2.0.0"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
@ -115,6 +118,7 @@ features = [
|
|||||||
"dtype-struct",
|
"dtype-struct",
|
||||||
"dtype-categorical",
|
"dtype-categorical",
|
||||||
"dynamic_groupby",
|
"dynamic_groupby",
|
||||||
|
"ipc",
|
||||||
"is_in",
|
"is_in",
|
||||||
"json",
|
"json",
|
||||||
"lazy",
|
"lazy",
|
||||||
@ -131,9 +135,8 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
version = "0.37.0"
|
version = "0.42.0"
|
||||||
features = [
|
features = [
|
||||||
"alloc",
|
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
@ -150,6 +153,8 @@ database = ["sqlparser", "rusqlite"]
|
|||||||
shadow-rs = { version = "0.16.1", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
nu-test-support = { path = "../nu-test-support", version = "0.71.0" }
|
||||||
|
|
||||||
hamcrest2 = "0.3.0"
|
hamcrest2 = "0.3.0"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
proptest = "1.0.0"
|
proptest = "1.0.0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -10,12 +10,12 @@ struct Arguments {
|
|||||||
added_data: Vec<u8>,
|
added_data: Vec<u8>,
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
end: bool,
|
end: bool,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,12 +62,8 @@ impl Command for BytesAdd {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
||||||
let end = call.has_flag("end");
|
let end = call.has_flag("end");
|
||||||
|
|
||||||
@ -75,7 +71,7 @@ impl Command for BytesAdd {
|
|||||||
added_data,
|
added_data,
|
||||||
index,
|
index,
|
||||||
end,
|
end,
|
||||||
column_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
@ -118,7 +114,25 @@ impl Command for BytesAdd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
|
fn add(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => add_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_impl(input: &[u8], args: &Arguments, span: Span) -> Value {
|
||||||
match args.index {
|
match args.index {
|
||||||
None => {
|
None => {
|
||||||
if args.end {
|
if args.end {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -15,12 +15,12 @@ struct Arguments {
|
|||||||
start: isize,
|
start: isize,
|
||||||
end: isize,
|
end: isize,
|
||||||
arg_span: Span,
|
arg_span: Span,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,17 +141,13 @@ impl Command for BytesAt {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let range: Value = call.req(engine_state, stack, 0)?;
|
let range: Value = call.req(engine_state, stack, 0)?;
|
||||||
let (start, end, arg_span) = parse_range(range, call.head)?;
|
let (start, end, arg_span) = parse_range(range, call.head)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
arg_span,
|
arg_span,
|
||||||
column_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
@ -228,7 +224,25 @@ impl Command for BytesAt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
fn at(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => at_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
let len: isize = input.len() as isize;
|
let len: isize = input.len() as isize;
|
||||||
|
|
||||||
let start: isize = if arg.start < 0 {
|
let start: isize = if arg.start < 0 {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
|
|||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +53,11 @@ impl Command for BytesEndsWith {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
pattern,
|
pattern,
|
||||||
column_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
@ -96,10 +92,24 @@ impl Command for BytesEndsWith {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
Value::Bool {
|
match val {
|
||||||
val: input.ends_with(pattern),
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Bool {
|
||||||
|
val: val.ends_with(&args.pattern),
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
span,
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::{Call, CellPath};
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
@ -10,12 +10,12 @@ struct Arguments {
|
|||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
end: bool,
|
end: bool,
|
||||||
all: bool,
|
all: bool,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,17 +60,13 @@ impl Command for BytesIndexOf {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
pattern,
|
pattern,
|
||||||
end: call.has_flag("end"),
|
end: call.has_flag("end"),
|
||||||
all: call.has_flag("all"),
|
all: call.has_flag("all"),
|
||||||
column_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
@ -126,7 +122,25 @@ impl Command for BytesIndexOf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => index_of_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
if arg.all {
|
if arg.all {
|
||||||
search_all_index(input, &arg.pattern, arg.end, span)
|
search_all_index(input, &arg.pattern, arg.end, span)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -9,16 +9,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BytesLen;
|
pub struct BytesLen;
|
||||||
|
|
||||||
struct Arguments {
|
|
||||||
column_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.column_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command for BytesLen {
|
impl Command for BytesLen {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"bytes length"
|
"bytes length"
|
||||||
@ -49,13 +39,8 @@ impl Command for BytesLen {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments { column_paths };
|
|
||||||
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +63,24 @@ impl Command for BytesLen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
|
fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
Value::Int {
|
match val {
|
||||||
val: input.len() as i64,
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Int {
|
||||||
|
val: val.len() as i64,
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
span,
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,6 @@ mod replace;
|
|||||||
mod reverse;
|
mod reverse;
|
||||||
mod starts_with;
|
mod starts_with;
|
||||||
|
|
||||||
use nu_protocol::ast::CellPath;
|
|
||||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use add::BytesAdd;
|
pub use add::BytesAdd;
|
||||||
pub use at::BytesAt;
|
pub use at::BytesAt;
|
||||||
pub use build_::BytesBuild;
|
pub use build_::BytesBuild;
|
||||||
@ -28,71 +23,3 @@ pub use remove::BytesRemove;
|
|||||||
pub use replace::BytesReplace;
|
pub use replace::BytesReplace;
|
||||||
pub use reverse::BytesReverse;
|
pub use reverse::BytesReverse;
|
||||||
pub use starts_with::BytesStartsWith;
|
pub use starts_with::BytesStartsWith;
|
||||||
|
|
||||||
trait BytesArgument {
|
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
|
|
||||||
fn operate<C, A>(
|
|
||||||
cmd: C,
|
|
||||||
mut arg: A,
|
|
||||||
input: PipelineData,
|
|
||||||
span: Span,
|
|
||||||
ctrlc: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Result<PipelineData, ShellError>
|
|
||||||
where
|
|
||||||
A: BytesArgument + Send + Sync + 'static,
|
|
||||||
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
|
|
||||||
{
|
|
||||||
match arg.take_column_paths() {
|
|
||||||
None => input.map(
|
|
||||||
move |v| match v {
|
|
||||||
Value::Binary {
|
|
||||||
val,
|
|
||||||
span: val_span,
|
|
||||||
} => cmd(&val, &arg, val_span),
|
|
||||||
other => Value::Error {
|
|
||||||
error: ShellError::UnsupportedInput(
|
|
||||||
format!(
|
|
||||||
"Input's type is {}. This command only works with bytes.",
|
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ctrlc,
|
|
||||||
),
|
|
||||||
Some(column_paths) => {
|
|
||||||
let arg = Arc::new(arg);
|
|
||||||
input.map(
|
|
||||||
move |mut v| {
|
|
||||||
for path in &column_paths {
|
|
||||||
let opt = arg.clone();
|
|
||||||
let r = v.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| {
|
|
||||||
match old {
|
|
||||||
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
|
|
||||||
other => Value::Error {
|
|
||||||
error: ShellError::UnsupportedInput(
|
|
||||||
format!(
|
|
||||||
"Input's type is {}. This command only works with bytes.",
|
|
||||||
other.get_type()
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
),
|
|
||||||
}}}),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v
|
|
||||||
},
|
|
||||||
ctrlc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -9,13 +9,13 @@ use nu_protocol::{
|
|||||||
struct Arguments {
|
struct Arguments {
|
||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
end: bool,
|
end: bool,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
all: bool,
|
all: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,12 +55,8 @@ impl Command for BytesRemove {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
if pattern_to_remove.item.is_empty() {
|
if pattern_to_remove.item.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
@ -73,7 +69,7 @@ impl Command for BytesRemove {
|
|||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
pattern: pattern_to_remove,
|
pattern: pattern_to_remove,
|
||||||
end: call.has_flag("end"),
|
end: call.has_flag("end"),
|
||||||
column_paths,
|
cell_paths,
|
||||||
all: call.has_flag("all"),
|
all: call.has_flag("all"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,7 +131,25 @@ impl Command for BytesRemove {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => remove_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let remove_all = arg.all;
|
let remove_all = arg.all;
|
||||||
let input_len = input.len();
|
let input_len = input.len();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -9,13 +9,13 @@ use nu_protocol::{
|
|||||||
struct Arguments {
|
struct Arguments {
|
||||||
find: Vec<u8>,
|
find: Vec<u8>,
|
||||||
replace: Vec<u8>,
|
replace: Vec<u8>,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
all: bool,
|
all: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,12 +55,8 @@ impl Command for BytesReplace {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||||
if find.item.is_empty() {
|
if find.item.is_empty() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
@ -72,7 +68,7 @@ impl Command for BytesReplace {
|
|||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
find: find.item,
|
find: find.item,
|
||||||
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
||||||
column_paths,
|
cell_paths,
|
||||||
all: call.has_flag("all"),
|
all: call.has_flag("all"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +122,25 @@ impl Command for BytesReplace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => replace_impl(val, args, *val_span),
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||||
let mut replaced = vec![];
|
let mut replaced = vec![];
|
||||||
let replace_all = arg.all;
|
let replace_all = arg.all;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -6,16 +6,6 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
|||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
struct Arguments {
|
|
||||||
column_paths: Option<Vec<CellPath>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
||||||
self.column_paths.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
||||||
pub struct BytesReverse;
|
pub struct BytesReverse;
|
||||||
@ -50,13 +40,8 @@ impl Command for BytesReverse {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments { column_paths };
|
|
||||||
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +67,28 @@ impl Command for BytesReverse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value {
|
fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
let mut reversed_input = input.to_vec();
|
match val {
|
||||||
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => {
|
||||||
|
let mut reversed_input = val.to_vec();
|
||||||
reversed_input.reverse();
|
reversed_input.reverse();
|
||||||
Value::Binary {
|
Value::Binary {
|
||||||
val: reversed_input,
|
val: reversed_input,
|
||||||
|
span: *val_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
span,
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{operate, BytesArgument};
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
@ -8,12 +8,12 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap
|
|||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
pattern: Vec<u8>,
|
pattern: Vec<u8>,
|
||||||
column_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesArgument for Arguments {
|
impl CmdArgument for Arguments {
|
||||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
self.column_paths.take()
|
self.cell_paths.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +53,11 @@ impl Command for BytesStartsWith {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let column_paths = if column_paths.is_empty() {
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(column_paths)
|
|
||||||
};
|
|
||||||
let arg = Arguments {
|
let arg = Arguments {
|
||||||
pattern,
|
pattern,
|
||||||
column_paths,
|
cell_paths,
|
||||||
};
|
};
|
||||||
operate(
|
operate(
|
||||||
starts_with,
|
starts_with,
|
||||||
@ -102,10 +98,24 @@ impl Command for BytesStartsWith {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
Value::Bool {
|
match val {
|
||||||
val: input.starts_with(pattern),
|
Value::Binary {
|
||||||
|
val,
|
||||||
|
span: val_span,
|
||||||
|
} => Value::Bool {
|
||||||
|
val: val.starts_with(&args.pattern),
|
||||||
|
span: *val_span,
|
||||||
|
},
|
||||||
|
other => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!(
|
||||||
|
"Input's type is {}. This command only works with bytes.",
|
||||||
|
other.get_type()
|
||||||
|
),
|
||||||
span,
|
span,
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,8 +215,8 @@ fn histogram_impl(
|
|||||||
const MAX_FREQ_COUNT: f64 = 100.0;
|
const MAX_FREQ_COUNT: f64 = 100.0;
|
||||||
for (val, count) in counter.into_iter() {
|
for (val, count) in counter.into_iter() {
|
||||||
let quantile = match calc_method {
|
let quantile = match calc_method {
|
||||||
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
|
PercentageCalcMethod::Normalize => count as f64 / total_cnt as f64,
|
||||||
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
|
PercentageCalcMethod::Relative => count as f64 / max_cnt as f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let percentage = format!("{:.2}%", quantile * 100_f64);
|
let percentage = format!("{:.2}%", quantile * 100_f64);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -96,31 +97,12 @@ fn fmt(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => fmt_it(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(*val, span),
|
Value::Filesize { val, .. } => fmt_it(*val, span),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -100,7 +101,7 @@ fn into_binary(
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
|
||||||
@ -120,28 +121,11 @@ fn into_binary(
|
|||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
_ => input.map(
|
_ => {
|
||||||
move |v| {
|
let arg = CellPathOnlyArgs::from(cell_paths);
|
||||||
if column_paths.is_empty() {
|
operate(action, arg, input, call.head, engine_state.ctrlc.clone())
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int_to_endian(n: i64) -> Vec<u8> {
|
fn int_to_endian(n: i64) -> Vec<u8> {
|
||||||
@ -160,7 +144,7 @@ fn float_to_endian(n: f64) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { .. } => input.clone(),
|
Value::Binary { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Binary {
|
Value::Int { val, .. } => Value::Binary {
|
||||||
@ -180,7 +164,7 @@ pub fn action(input: &Value, span: Span) -> Value {
|
|||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Bool { val, .. } => Value::Binary {
|
Value::Bool { val, .. } => Value::Binary {
|
||||||
val: int_to_endian(if *val { 1i64 } else { 0 }),
|
val: int_to_endian(i64::from(*val)),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
Value::Date { val, .. } => Value::Binary {
|
Value::Date { val, .. } => Value::Binary {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -108,28 +109,9 @@ fn into_bool(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
||||||
@ -154,7 +136,7 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Bool { .. } => input.clone(),
|
Value::Bool { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::Bool {
|
Value::Int { val, .. } => Value::Bool {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use crate::{generate_strftime_list, parse_date_from_string};
|
use crate::{generate_strftime_list, parse_date_from_string};
|
||||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
@ -5,14 +6,20 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
timezone: Option<Spanned<String>>,
|
zone_options: Option<Spanned<Zone>>,
|
||||||
offset: Option<Spanned<i64>>,
|
format_options: Option<DatetimeFormat>,
|
||||||
format: Option<String>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
column_paths: Vec<CellPath>,
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case it may be confused with chrono::TimeZone
|
// In case it may be confused with chrono::TimeZone
|
||||||
@ -95,7 +102,36 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
if call.has_flag("list") {
|
||||||
|
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
// if zone-offset is specified, then zone will be neglected
|
||||||
|
let timezone = call.get_flag::<Spanned<String>>(engine_state, stack, "timezone")?;
|
||||||
|
let zone_options =
|
||||||
|
match &call.get_flag::<Spanned<i64>>(engine_state, stack, "offset")? {
|
||||||
|
Some(zone_offset) => Some(Spanned {
|
||||||
|
item: Zone::new(zone_offset.item),
|
||||||
|
span: zone_offset.span,
|
||||||
|
}),
|
||||||
|
None => timezone.as_ref().map(|zone| Spanned {
|
||||||
|
item: Zone::from_string(zone.item.clone()),
|
||||||
|
span: zone.span,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let format_options = call
|
||||||
|
.get_flag::<String>(engine_state, stack, "format")?
|
||||||
|
.as_ref()
|
||||||
|
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
format_options,
|
||||||
|
zone_options,
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
@ -162,72 +198,9 @@ impl Command for SubCommand {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DatetimeFormat(String);
|
struct DatetimeFormat(String);
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
let timezone = &args.zone_options;
|
||||||
stack: &mut Stack,
|
let dateformat = &args.format_options;
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let options = Arguments {
|
|
||||||
timezone: call.get_flag(engine_state, stack, "timezone")?,
|
|
||||||
offset: call.get_flag(engine_state, stack, "offset")?,
|
|
||||||
format: call.get_flag(engine_state, stack, "format")?,
|
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if zone-offset is specified, then zone will be neglected
|
|
||||||
let zone_options = match &options.offset {
|
|
||||||
Some(zone_offset) => Some(Spanned {
|
|
||||||
item: Zone::new(zone_offset.item),
|
|
||||||
span: zone_offset.span,
|
|
||||||
}),
|
|
||||||
None => options.timezone.as_ref().map(|zone| Spanned {
|
|
||||||
item: Zone::from_string(zone.item.clone()),
|
|
||||||
span: zone.span,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let list_flag = call.has_flag("list");
|
|
||||||
|
|
||||||
let format_options = options
|
|
||||||
.format
|
|
||||||
.as_ref()
|
|
||||||
.map(|fmt| DatetimeFormat(fmt.to_string()));
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if options.column_paths.is_empty() && !list_flag {
|
|
||||||
action(&v, &zone_options, &format_options, head)
|
|
||||||
} else if list_flag {
|
|
||||||
generate_strftime_list(head, true)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &options.column_paths {
|
|
||||||
let zone_options = zone_options.clone();
|
|
||||||
let format_options = format_options.clone();
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, &zone_options, &format_options, head)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(
|
|
||||||
input: &Value,
|
|
||||||
timezone: &Option<Spanned<Zone>>,
|
|
||||||
dateformat: &Option<DatetimeFormat>,
|
|
||||||
head: Span,
|
|
||||||
) -> Value {
|
|
||||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
Value::Int { val, .. } => Ok(*val),
|
Value::Int { val, .. } => Ok(*val),
|
||||||
@ -359,7 +332,12 @@ mod tests {
|
|||||||
fn takes_a_date_format() {
|
fn takes_a_date_format() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -371,7 +349,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_iso8601_date_format() {
|
fn takes_iso8601_date_format() {
|
||||||
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
let date_str = Value::test_string("2020-08-04T16:39:18+00:00");
|
||||||
let actual = action(&date_str, &None, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -387,7 +370,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -404,7 +392,12 @@ mod tests {
|
|||||||
item: Zone::East(8),
|
item: Zone::East(8),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_int, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_int, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -421,7 +414,12 @@ mod tests {
|
|||||||
item: Zone::Local,
|
item: Zone::Local,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Local.timestamp(1614434140, 0).into(),
|
val: Local.timestamp(1614434140, 0).into(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
@ -433,8 +431,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp_without_timezone() {
|
fn takes_timestamp_without_timezone() {
|
||||||
let date_str = Value::test_string("1614434140");
|
let date_str = Value::test_string("1614434140");
|
||||||
let timezone_option = None;
|
let args = Arguments {
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
zone_options: None,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::Date {
|
let expected = Value::Date {
|
||||||
val: Utc.timestamp(1614434140, 0).into(),
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
@ -451,7 +453,12 @@ mod tests {
|
|||||||
item: Zone::Utc,
|
item: Zone::Utc,
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
});
|
});
|
||||||
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: None,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -460,7 +467,12 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
|
||||||
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
|
||||||
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
|
||||||
let actual = action(&date_str, &None, &fmt_options, Span::test_data());
|
let args = Arguments {
|
||||||
|
zone_options: None,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -36,7 +37,9 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
operate(engine_state, stack, call, input)
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -72,37 +75,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action(input: &Value, head: Span) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::String { val: s, span } => {
|
Value::String { val: s, span } => {
|
||||||
let other = s.trim();
|
let other = s.trim();
|
||||||
@ -163,7 +136,7 @@ mod tests {
|
|||||||
let word = Value::test_string("3.1415");
|
let word = Value::test_string("3.1415");
|
||||||
let expected = Value::test_float(3.1415);
|
let expected = Value::test_float(3.1415);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data());
|
let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data());
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +144,11 @@ mod tests {
|
|||||||
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
fn communicates_parsing_error_given_an_invalid_decimallike_string() {
|
||||||
let decimal_str = Value::test_string("11.6anra");
|
let decimal_str = Value::test_string("11.6anra");
|
||||||
|
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error);
|
assert_eq!(actual.get_type(), Error);
|
||||||
}
|
}
|
||||||
@ -180,7 +157,11 @@ mod tests {
|
|||||||
fn int_to_decimal() {
|
fn int_to_decimal() {
|
||||||
let decimal_str = Value::test_int(10);
|
let decimal_str = Value::test_int(10);
|
||||||
let expected = Value::test_float(10.0);
|
let expected = Value::test_float(10.0);
|
||||||
let actual = action(&decimal_str, Span::test_data());
|
let actual = action(
|
||||||
|
&decimal_str,
|
||||||
|
&CellPathOnlyArgs::from(vec![]),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
@ -114,14 +114,21 @@ impl Command for SubCommand {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert string to a named duration",
|
description: "Convert string to the requested duration as a string",
|
||||||
example: "'7min' | into duration --convert sec",
|
example: "'7min' | into duration --convert sec",
|
||||||
result: Some(Value::String {
|
result: Some(Value::String {
|
||||||
val: "420 sec".to_string(),
|
val: "420 sec".to_string(),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert duration to the requested duration as a string",
|
||||||
|
example: "420sec | into duration --convert min",
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "7 min".to_string(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,18 +142,20 @@ fn into_duration(
|
|||||||
let head = call.head;
|
let head = call.head;
|
||||||
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
let float_precision = config.float_precision as usize;
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
action(&v, &convert_to_unit, head)
|
action(&v, &convert_to_unit, float_precision, head)
|
||||||
} else {
|
} else {
|
||||||
let mut ret = v;
|
let mut ret = v;
|
||||||
for path in &column_paths {
|
for path in &column_paths {
|
||||||
let d = convert_to_unit.clone();
|
let d = convert_to_unit.clone();
|
||||||
let r = ret.update_cell_path(
|
let r = ret.update_cell_path(
|
||||||
&path.members,
|
&path.members,
|
||||||
Box::new(move |old| action(old, &d, head)),
|
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||||
);
|
);
|
||||||
if let Err(error) = r {
|
if let Err(error) = r {
|
||||||
return Value::Error { error };
|
return Value::Error { error };
|
||||||
@ -166,127 +175,129 @@ fn convert_str_from_unit_to_unit(
|
|||||||
to_unit: &str,
|
to_unit: &str,
|
||||||
span: Span,
|
span: Span,
|
||||||
value_span: Span,
|
value_span: Span,
|
||||||
) -> Result<i64, ShellError> {
|
) -> Result<f64, ShellError> {
|
||||||
match (from_unit, to_unit) {
|
match (from_unit, to_unit) {
|
||||||
("ns", "ns") => Ok(val),
|
("ns", "ns") => Ok(val as f64),
|
||||||
("ns", "us") => Ok(val / 1000),
|
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||||
("ns", "ms") => Ok(val / 1000 / 1000),
|
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
("ns", "sec") => Ok(val / 1000 / 1000 / 1000),
|
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||||
("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60),
|
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||||
("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60),
|
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24),
|
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7),
|
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30),
|
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
("ns", "dec") => {
|
||||||
|
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||||
|
}
|
||||||
|
|
||||||
("us", "ns") => Ok(val * 1000),
|
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||||
("us", "us") => Ok(val),
|
("us", "us") => Ok(val as f64),
|
||||||
("us", "ms") => Ok(val / 1000),
|
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||||
("us", "sec") => Ok(val / 1000 / 1000),
|
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||||
("us", "min") => Ok(val / 1000 / 1000 / 60),
|
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||||
("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60),
|
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||||
("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24),
|
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7),
|
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30),
|
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365),
|
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365),
|
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
("ms", "ns") => Ok(val * 1000 * 1000),
|
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
("ms", "us") => Ok(val * 1000),
|
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||||
("ms", "ms") => Ok(val),
|
("ms", "ms") => Ok(val as f64),
|
||||||
("ms", "sec") => Ok(val / 1000),
|
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||||
("ms", "min") => Ok(val / 1000 / 60),
|
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||||
("ms", "hr") => Ok(val / 1000 / 60 / 60),
|
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||||
("ms", "day") => Ok(val / 1000 / 60 / 60 / 24),
|
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||||
("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7),
|
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30),
|
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365),
|
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365),
|
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
("sec", "ns") => Ok(val * 1000 * 1000 * 1000),
|
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||||
("sec", "us") => Ok(val * 1000 * 1000),
|
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||||
("sec", "ms") => Ok(val * 1000),
|
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||||
("sec", "sec") => Ok(val),
|
("sec", "sec") => Ok(val as f64),
|
||||||
("sec", "min") => Ok(val / 60),
|
("sec", "min") => Ok(val as f64 / 60.0),
|
||||||
("sec", "hr") => Ok(val / 60 / 60),
|
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||||
("sec", "day") => Ok(val / 60 / 60 / 24),
|
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||||
("sec", "wk") => Ok(val / 60 / 60 / 24 / 7),
|
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||||
("sec", "month") => Ok(val / 60 / 60 / 24 / 30),
|
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||||
("sec", "yr") => Ok(val / 60 / 60 / 24 / 365),
|
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365),
|
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60),
|
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||||
("min", "us") => Ok(val * 1000 * 1000 * 60),
|
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||||
("min", "ms") => Ok(val * 1000 * 60),
|
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||||
("min", "sec") => Ok(val * 60),
|
("min", "sec") => Ok(val as f64 * 60.0),
|
||||||
("min", "min") => Ok(val),
|
("min", "min") => Ok(val as f64),
|
||||||
("min", "hr") => Ok(val / 60),
|
("min", "hr") => Ok(val as f64 / 60.0),
|
||||||
("min", "day") => Ok(val / 60 / 24),
|
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||||
("min", "wk") => Ok(val / 60 / 24 / 7),
|
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||||
("min", "month") => Ok(val / 60 / 24 / 30),
|
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||||
("min", "yr") => Ok(val / 60 / 24 / 365),
|
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||||
("min", "dec") => Ok(val / 10 / 60 / 24 / 365),
|
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60),
|
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60),
|
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||||
("hr", "ms") => Ok(val * 1000 * 60 * 60),
|
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||||
("hr", "sec") => Ok(val * 60 * 60),
|
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||||
("hr", "min") => Ok(val * 60),
|
("hr", "min") => Ok(val as f64 * 60.0),
|
||||||
("hr", "hr") => Ok(val),
|
("hr", "hr") => Ok(val as f64),
|
||||||
("hr", "day") => Ok(val / 24),
|
("hr", "day") => Ok(val as f64 / 24.0),
|
||||||
("hr", "wk") => Ok(val / 24 / 7),
|
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||||
("hr", "month") => Ok(val / 24 / 30),
|
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||||
("hr", "yr") => Ok(val / 24 / 365),
|
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||||
("hr", "dec") => Ok(val / 10 / 24 / 365),
|
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||||
|
|
||||||
("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24),
|
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24),
|
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
("day", "ms") => Ok(val * 1000 * 60 * 60 * 24),
|
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||||
("day", "sec") => Ok(val * 60 * 60 * 24),
|
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||||
("day", "min") => Ok(val * 60 * 24),
|
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||||
("day", "hr") => Ok(val * 24),
|
("day", "hr") => Ok(val as f64 * 24.0),
|
||||||
("day", "day") => Ok(val),
|
("day", "day") => Ok(val as f64),
|
||||||
("day", "wk") => Ok(val / 7),
|
("day", "wk") => Ok(val as f64 / 7.0),
|
||||||
("day", "month") => Ok(val / 30),
|
("day", "month") => Ok(val as f64 / 30.0),
|
||||||
("day", "yr") => Ok(val / 365),
|
("day", "yr") => Ok(val as f64 / 365.0),
|
||||||
("day", "dec") => Ok(val / 10 / 365),
|
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||||
|
|
||||||
("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7),
|
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7),
|
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7),
|
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
("wk", "sec") => Ok(val * 60 * 60 * 24 * 7),
|
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||||
("wk", "min") => Ok(val * 60 * 24 * 7),
|
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||||
("wk", "hr") => Ok(val * 24 * 7),
|
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||||
("wk", "day") => Ok(val * 7),
|
("wk", "day") => Ok(val as f64 * 7.0),
|
||||||
("wk", "wk") => Ok(val),
|
("wk", "wk") => Ok(val as f64),
|
||||||
("wk", "month") => Ok(val / 4),
|
("wk", "month") => Ok(val as f64 / 4.0),
|
||||||
("wk", "yr") => Ok(val / 52),
|
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||||
("wk", "dec") => Ok(val / 10 / 52),
|
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||||
|
|
||||||
("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30),
|
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30),
|
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30),
|
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
("month", "sec") => Ok(val * 60 * 60 * 24 * 30),
|
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||||
("month", "min") => Ok(val * 60 * 24 * 30),
|
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||||
("month", "hr") => Ok(val * 24 * 30),
|
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||||
("month", "day") => Ok(val * 30),
|
("month", "day") => Ok(val as f64 * 30.0),
|
||||||
("month", "wk") => Ok(val * 4),
|
("month", "wk") => Ok(val as f64 * 4.0),
|
||||||
("month", "month") => Ok(val),
|
("month", "month") => Ok(val as f64),
|
||||||
("month", "yr") => Ok(val / 12),
|
("month", "yr") => Ok(val as f64 / 12.0),
|
||||||
("month", "dec") => Ok(val / 10 / 12),
|
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||||
|
|
||||||
("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365),
|
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365),
|
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365),
|
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
("yr", "sec") => Ok(val * 60 * 60 * 24 * 365),
|
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||||
("yr", "min") => Ok(val * 60 * 24 * 365),
|
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||||
("yr", "hr") => Ok(val * 24 * 365),
|
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||||
("yr", "day") => Ok(val * 365),
|
("yr", "day") => Ok(val as f64 * 365.0),
|
||||||
("yr", "wk") => Ok(val * 52),
|
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||||
("yr", "month") => Ok(val * 12),
|
("yr", "month") => Ok(val as f64 * 12.0),
|
||||||
("yr", "yr") => Ok(val),
|
("yr", "yr") => Ok(val as f64),
|
||||||
("yr", "dec") => Ok(val / 10),
|
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||||
|
|
||||||
_ => Err(ShellError::CantConvertWithValue(
|
_ => Err(ShellError::CantConvertWithValue(
|
||||||
"string duration".to_string(),
|
"string duration".to_string(),
|
||||||
@ -315,9 +326,6 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
|||||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
|
|
||||||
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
|
||||||
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,9 +361,6 @@ fn string_to_unit_duration(
|
|||||||
Unit::Hour => return Ok(("hr", x)),
|
Unit::Hour => return Ok(("hr", x)),
|
||||||
Unit::Day => return Ok(("day", x)),
|
Unit::Day => return Ok(("day", x)),
|
||||||
Unit::Week => return Ok(("wk", x)),
|
Unit::Week => return Ok(("wk", x)),
|
||||||
Unit::Month => return Ok(("month", x)), //30 days to a month
|
|
||||||
Unit::Year => return Ok(("yr", x)), //365 days to a year
|
|
||||||
Unit::Decade => return Ok(("dec", x)), //365 days to a year
|
|
||||||
|
|
||||||
_ => return Ok(("ns", 0)),
|
_ => return Ok(("ns", 0)),
|
||||||
}
|
}
|
||||||
@ -375,19 +380,41 @@ fn string_to_unit_duration(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span) -> Value {
|
fn action(
|
||||||
|
input: &Value,
|
||||||
|
convert_to_unit: &Option<Spanned<String>>,
|
||||||
|
float_precision: usize,
|
||||||
|
span: Span,
|
||||||
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Duration {
|
Value::Duration {
|
||||||
val: _val_num,
|
val: val_num,
|
||||||
span: _value_span,
|
span: value_span,
|
||||||
} => {
|
} => {
|
||||||
if let Some(_to_unit) = convert_to_unit {
|
if let Some(to_unit) = convert_to_unit {
|
||||||
Value::Error {
|
let from_unit = "ns";
|
||||||
error: ShellError::UnsupportedInput(
|
let duration = *val_num;
|
||||||
"Cannot convert from a Value::Duration right now. Try making it a string."
|
match convert_str_from_unit_to_unit(
|
||||||
.into(),
|
duration,
|
||||||
|
from_unit,
|
||||||
|
&to_unit.item,
|
||||||
span,
|
span,
|
||||||
),
|
*value_span,
|
||||||
|
) {
|
||||||
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
input.clone()
|
input.clone()
|
||||||
@ -408,10 +435,19 @@ fn action(input: &Value, convert_to_unit: &Option<Spanned<String>>, span: Span)
|
|||||||
span,
|
span,
|
||||||
*value_span,
|
*value_span,
|
||||||
) {
|
) {
|
||||||
Ok(d) => Value::String {
|
Ok(d) => {
|
||||||
|
if d.fract() == 0.0 {
|
||||||
|
Value::String {
|
||||||
val: format!("{} {}", d, &to_unit.item),
|
val: format!("{} {}", d, &to_unit.item),
|
||||||
span: *value_span,
|
span: *value_span,
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: format!("{:.float_precision$} {}", d, &to_unit.item),
|
||||||
|
span: *value_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => Value::Error { error: e },
|
Err(e) => Value::Error { error: e },
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -456,7 +492,7 @@ mod test {
|
|||||||
let expected = Value::Duration { val: 3, span };
|
let expected = Value::Duration { val: 3, span };
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +506,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +520,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,7 +534,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,7 +548,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,7 +562,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +576,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,7 +590,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let convert_duration = None;
|
let convert_duration = None;
|
||||||
|
|
||||||
let actual = action(&word, &convert_duration, span);
|
let actual = action(&word, &convert_duration, 2, span);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CellPathOnlyArgs};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -38,7 +39,9 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
into_filesize(engine_state, stack, call, input)
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
let args = CellPathOnlyArgs::from(cell_paths);
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -84,37 +87,7 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_filesize(
|
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r =
|
|
||||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span) -> Value {
|
|
||||||
if let Ok(value_span) = input.span() {
|
if let Ok(value_span) = input.span() {
|
||||||
match input {
|
match input {
|
||||||
Value::Filesize { .. } => input.clone(),
|
Value::Filesize { .. } => input.clone(),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -6,11 +7,17 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
radix: Option<Value>,
|
radix: u32,
|
||||||
column_paths: Vec<CellPath>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
little_endian: bool,
|
little_endian: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -46,7 +53,29 @@ impl Command for SubCommand {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
into_int(engine_state, stack, call, input)
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
let radix = call.get_flag::<Value>(engine_state, stack, "radix")?;
|
||||||
|
let radix: u32 = match radix {
|
||||||
|
Some(Value::Int { val, span }) => {
|
||||||
|
if !(2..=36).contains(&val) {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"Radix must lie in the range [2, 36]".to_string(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
val as u32
|
||||||
|
}
|
||||||
|
Some(_) => 10,
|
||||||
|
None => 10,
|
||||||
|
};
|
||||||
|
let args = Arguments {
|
||||||
|
radix,
|
||||||
|
little_endian: call.has_flag("little-endian"),
|
||||||
|
cell_paths,
|
||||||
|
};
|
||||||
|
operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -121,59 +150,9 @@ impl Command for SubCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_int(
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
engine_state: &EngineState,
|
let radix = args.radix;
|
||||||
stack: &mut Stack,
|
let little_endian = args.little_endian;
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
|
|
||||||
let options = Arguments {
|
|
||||||
radix: call.get_flag(engine_state, stack, "radix")?,
|
|
||||||
little_endian: call.has_flag("little-endian"),
|
|
||||||
column_paths: call.rest(engine_state, stack, 0)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let radix: u32 = match options.radix {
|
|
||||||
Some(Value::Int { val, .. }) => val as u32,
|
|
||||||
Some(_) => 10,
|
|
||||||
None => 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(val) = &options.radix {
|
|
||||||
if !(2..=36).contains(&radix) {
|
|
||||||
return Err(ShellError::UnsupportedInput(
|
|
||||||
"Radix must lie in the range [2, 36]".to_string(),
|
|
||||||
val.span()?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if options.column_paths.is_empty() {
|
|
||||||
action(&v, head, radix, options.little_endian)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &options.column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, head, radix, options.little_endian)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.ctrlc.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val: _, .. } => {
|
Value::Int { val: _, .. } => {
|
||||||
if radix == 10 {
|
if radix == 10 {
|
||||||
@ -401,21 +380,45 @@ mod test {
|
|||||||
let word = Value::test_string("10");
|
let word = Value::test_string("10");
|
||||||
let expected = Value::test_int(10);
|
let expected = Value::test_int(10);
|
||||||
|
|
||||||
let actual = action(&word, Span::test_data(), 10, false);
|
let actual = action(
|
||||||
|
&word,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_binary_to_integer() {
|
fn turns_binary_to_integer() {
|
||||||
let s = Value::test_string("0b101");
|
let s = Value::test_string("0b101");
|
||||||
let actual = action(&s, Span::test_data(), 10, false);
|
let actual = action(
|
||||||
|
&s,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, Value::test_int(5));
|
assert_eq!(actual, Value::test_int(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn turns_hex_to_integer() {
|
fn turns_hex_to_integer() {
|
||||||
let s = Value::test_string("0xFF");
|
let s = Value::test_string("0xFF");
|
||||||
let actual = action(&s, Span::test_data(), 16, false);
|
let actual = action(
|
||||||
|
&s,
|
||||||
|
&Arguments {
|
||||||
|
radix: 16,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
assert_eq!(actual, Value::test_int(255));
|
assert_eq!(actual, Value::test_int(255));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +426,15 @@ mod test {
|
|||||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||||
let integer_str = Value::test_string("36anra");
|
let integer_str = Value::test_string("36anra");
|
||||||
|
|
||||||
let actual = action(&integer_str, Span::test_data(), 10, false);
|
let actual = action(
|
||||||
|
&integer_str,
|
||||||
|
&Arguments {
|
||||||
|
radix: 10,
|
||||||
|
cell_paths: None,
|
||||||
|
little_endian: false,
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(actual.get_type(), Error)
|
assert_eq!(actual.get_type(), Error)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, CellPath},
|
ast::{Call, CellPath},
|
||||||
@ -8,6 +9,19 @@ use nu_protocol::{
|
|||||||
use nu_utils::get_system_locale;
|
use nu_utils::get_system_locale;
|
||||||
use num_format::ToFormattedString;
|
use num_format::ToFormattedString;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
decimals_value: Option<i64>,
|
||||||
|
decimals: bool,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -149,9 +163,6 @@ fn string_helper(
|
|||||||
let decimals = call.has_flag("decimals");
|
let decimals = call.has_flag("decimals");
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let config = engine_state.get_config().clone();
|
|
||||||
|
|
||||||
if let Some(decimal_val) = decimals_value {
|
if let Some(decimal_val) = decimals_value {
|
||||||
if decimals && decimal_val.is_negative() {
|
if decimals && decimal_val.is_negative() {
|
||||||
return Err(ShellError::UnsupportedInput(
|
return Err(ShellError::UnsupportedInput(
|
||||||
@ -160,6 +171,15 @@ fn string_helper(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
let config = engine_state.get_config().clone();
|
||||||
|
let args = Arguments {
|
||||||
|
decimals_value,
|
||||||
|
decimals,
|
||||||
|
cell_paths,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
|
||||||
@ -179,45 +199,18 @@ fn string_helper(
|
|||||||
}
|
}
|
||||||
.into_pipeline_data())
|
.into_pipeline_data())
|
||||||
}
|
}
|
||||||
_ => input.map(
|
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, head, decimals, decimals_value, false, &config)
|
|
||||||
} else {
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let config = config.clone();
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| {
|
|
||||||
action(old, head, decimals, decimals_value, false, &config)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::Error { error };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret
|
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||||
}
|
let decimals = args.decimals;
|
||||||
},
|
let digits = args.decimals_value;
|
||||||
engine_state.ctrlc.clone(),
|
let config = &args.config;
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(
|
|
||||||
input: &Value,
|
|
||||||
span: Span,
|
|
||||||
decimals: bool,
|
|
||||||
digits: Option<i64>,
|
|
||||||
group_digits: bool,
|
|
||||||
config: &Config,
|
|
||||||
) -> Value {
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
let decimal_value = digits.unwrap_or(0) as usize;
|
let decimal_value = digits.unwrap_or(0) as usize;
|
||||||
let res = format_int(*val, group_digits, decimal_value);
|
let res = format_int(*val, false, decimal_value);
|
||||||
Value::String { val: res, span }
|
Value::String { val: res, span }
|
||||||
}
|
}
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
|
@ -49,10 +49,17 @@ impl Command for Alias {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Alias ll to ls -l",
|
description: "Alias ll to ls -l",
|
||||||
example: "alias ll = ls -l",
|
example: "alias ll = ls -l",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Make an alias that makes a list of all custom commands",
|
||||||
|
example: "alias customs = ($nu.scope.commands | where is_custom | get command)",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
84
crates/nu-command/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::ReplOperation;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::Category;
|
||||||
|
use nu_protocol::IntoPipelineData;
|
||||||
|
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commandline;
|
||||||
|
|
||||||
|
impl Command for Commandline {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("commandline")
|
||||||
|
.switch(
|
||||||
|
"append",
|
||||||
|
"appends the string to the end of the buffer",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"insert",
|
||||||
|
"inserts the string into the buffer at the cursor position",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"replace",
|
||||||
|
"replaces the current contents of the buffer (default)",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"cmd",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the string to perform the operation with",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"View or modify the current command line input buffer"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||||
|
let mut ops = engine_state
|
||||||
|
.repl_operation_queue
|
||||||
|
.lock()
|
||||||
|
.expect("repl op queue mutex");
|
||||||
|
ops.push_back(if call.has_flag("append") {
|
||||||
|
ReplOperation::Append(cmd.as_string()?)
|
||||||
|
} else if call.has_flag("insert") {
|
||||||
|
ReplOperation::Insert(cmd.as_string()?)
|
||||||
|
} else {
|
||||||
|
ReplOperation::Replace(cmd.as_string()?)
|
||||||
|
});
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
} else if let Some(ref cmd) = *engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex")
|
||||||
|
{
|
||||||
|
Ok(Value::String {
|
||||||
|
val: cmd.clone(),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -22,7 +23,7 @@ impl Command for Do {
|
|||||||
.required("block", SyntaxShape::Any, "the block to run")
|
.required("block", SyntaxShape::Any, "the block to run")
|
||||||
.switch(
|
.switch(
|
||||||
"ignore-errors",
|
"ignore-errors",
|
||||||
"ignore errors as the block runs",
|
"ignore shell errors as the block runs",
|
||||||
Some('i'),
|
Some('i'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
@ -102,9 +103,75 @@ impl Command for Do {
|
|||||||
Err(_) => Ok(PipelineData::new(call.head)),
|
Err(_) => Ok(PipelineData::new(call.head)),
|
||||||
}
|
}
|
||||||
} else if capture_errors {
|
} else if capture_errors {
|
||||||
|
// collect stdout and stderr and check exit code.
|
||||||
|
// if exit code is not 0, return back ShellError.
|
||||||
match result {
|
match result {
|
||||||
Ok(x) => Ok(x),
|
Ok(PipelineData::ExternalStream {
|
||||||
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
stdout,
|
||||||
|
stderr,
|
||||||
|
exit_code,
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
}) => {
|
||||||
|
// collect all output first.
|
||||||
|
let mut stderr_ctrlc = None;
|
||||||
|
let stderr_msg = match stderr {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(stderr_stream) => {
|
||||||
|
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||||
|
stderr_stream.into_string().map(|s| s.item)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stdout_ctrlc = None;
|
||||||
|
let stdout_msg = match stdout {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(stdout_stream) => {
|
||||||
|
stdout_ctrlc = stdout_stream.ctrlc.clone();
|
||||||
|
stdout_stream.into_string().map(|s| s.item)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut exit_code_ctrlc = None;
|
||||||
|
let exit_code: Vec<Value> = match exit_code {
|
||||||
|
None => vec![],
|
||||||
|
Some(exit_code_stream) => {
|
||||||
|
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||||
|
exit_code_stream.into_iter().collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||||
|
// if exit_code is not 0, it indicates error occured, return back Err.
|
||||||
|
if *code != 0 {
|
||||||
|
return Err(ShellError::ExternalCommand(
|
||||||
|
"External command runs to failed".to_string(),
|
||||||
|
stderr_msg,
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// construct pipeline data to our caller
|
||||||
|
Ok(PipelineData::ExternalStream {
|
||||||
|
stdout: Some(RawStream::new(
|
||||||
|
Box::new(vec![Ok(stdout_msg.into_bytes())].into_iter()),
|
||||||
|
stdout_ctrlc,
|
||||||
|
span,
|
||||||
|
)),
|
||||||
|
stderr: Some(RawStream::new(
|
||||||
|
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||||
|
stderr_ctrlc,
|
||||||
|
span,
|
||||||
|
)),
|
||||||
|
exit_code: Some(ListStream::from_stream(
|
||||||
|
exit_code.into_iter(),
|
||||||
|
exit_code_ctrlc,
|
||||||
|
)),
|
||||||
|
span,
|
||||||
|
metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(other) => Ok(other),
|
||||||
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result
|
result
|
||||||
@ -119,10 +186,15 @@ impl Command for Do {
|
|||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::test_string("hello")),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block and ignore errors",
|
description: "Run the block and ignore shell errors",
|
||||||
example: r#"do -i { thisisnotarealcommand }"#,
|
example: r#"do -i { thisisnotarealcommand }"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Abort the pipeline if a program returns a non-zero exit code",
|
||||||
|
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Run the block, with a positional parameter",
|
description: "Run the block, with a positional parameter",
|
||||||
example: r#"do {|x| 100 + $x } 50"#,
|
example: r#"do {|x| 100 + $x } 50"#,
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
|||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -14,7 +14,7 @@ impl Command for Echo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Echo the arguments back to the user."
|
"Returns its arguments, ignoring the piped-in value."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -24,7 +24,9 @@ impl Command for Echo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
|
r#"When given no arguments, it returns an empty string. When given one argument,
|
||||||
|
it returns it. Otherwise, it returns a list of the arguments. There is usually
|
||||||
|
little reason to use this over just writing the values as-is."#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -61,13 +63,17 @@ impl Command for Echo {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Put a hello message in the pipeline",
|
description: "Put a list of numbers in the pipeline. This is the same as [1 2 3].",
|
||||||
example: "echo 'hello'",
|
example: "echo 1 2 3",
|
||||||
result: Some(Value::test_string("hello")),
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Print the value of the special '$nu' variable",
|
description:
|
||||||
example: "echo $nu",
|
"Returns the piped-in value, by using the special $in variable to obtain it.",
|
||||||
|
example: "echo $in",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -59,4 +59,8 @@ impl Command for ExportCommand {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,4 +51,8 @@ impl Command for ExportAlias {
|
|||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["aka", "abbr", "module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,4 +55,8 @@ impl Command for ExportDef {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,4 +81,8 @@ export def-env cd_with_fallback [arg = ""] {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExportEnvModule;
|
|
||||||
|
|
||||||
impl Command for ExportEnvModule {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"export env"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Export a block from a module that will be evaluated as an environment variable when imported."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
|
||||||
Signature::build("export env")
|
|
||||||
.required(
|
|
||||||
"name",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"name of the environment variable",
|
|
||||||
)
|
|
||||||
.required(
|
|
||||||
"block",
|
|
||||||
SyntaxShape::Block(Some(vec![])),
|
|
||||||
"body of the environment variable definition",
|
|
||||||
)
|
|
||||||
.category(Category::Core)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
r#"This command is a parser keyword. For details, check:
|
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
_engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
//TODO: Add the env to stack
|
|
||||||
Ok(PipelineData::new(call.head))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Import and evaluate environment variable from a module",
|
|
||||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
|
||||||
result: Some(Value::String {
|
|
||||||
val: "BAZ".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,4 +47,8 @@ impl Command for ExportExtern {
|
|||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["signature", "module", "declare"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,4 +53,8 @@ impl Command for ExportUse {
|
|||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["reexport", "import", "module"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,11 +362,13 @@ pub fn highlight_search_string(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// strip haystack to remove existing ansi style
|
||||||
|
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
let style = Style::new().fg(White).on(Red);
|
let style = Style::new().fg(White).on(Red);
|
||||||
let mut highlighted = String::new();
|
let mut highlighted = String::new();
|
||||||
|
|
||||||
for cap in regex.captures_iter(haystack) {
|
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
||||||
match cap {
|
match cap {
|
||||||
Ok(capture) => {
|
Ok(capture) => {
|
||||||
let start = match capture.get(0) {
|
let start = match capture.get(0) {
|
||||||
@ -379,10 +381,10 @@ pub fn highlight_search_string(
|
|||||||
};
|
};
|
||||||
highlighted.push_str(
|
highlighted.push_str(
|
||||||
&string_style
|
&string_style
|
||||||
.paint(&haystack[last_match_end..start])
|
.paint(&stripped_haystack[last_match_end..start])
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
|
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||||
last_match_end = end;
|
last_match_end = end;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -397,6 +399,10 @@ pub fn highlight_search_string(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
|
highlighted.push_str(
|
||||||
|
&string_style
|
||||||
|
.paint(&stripped_haystack[last_match_end..])
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
Ok(highlighted)
|
Ok(highlighted)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
use nu_protocol::ast::{Call, Expr, Expression};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -40,12 +40,15 @@ This command is a parser keyword. For details, check:
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let import_pattern = if let Some(Expression {
|
let env_var_name = if let Some(Expression {
|
||||||
expr: Expr::ImportPattern(pat),
|
expr: Expr::ImportPattern(pat),
|
||||||
..
|
..
|
||||||
}) = call.positional_nth(0)
|
}) = call.positional_nth(0)
|
||||||
{
|
{
|
||||||
pat
|
Spanned {
|
||||||
|
item: String::from_utf8_lossy(&pat.head.name).to_string(),
|
||||||
|
span: pat.head.span,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
"Unexpected import".into(),
|
"Unexpected import".into(),
|
||||||
@ -56,78 +59,7 @@ This command is a parser keyword. For details, check:
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let head_name_str = if let Ok(s) = String::from_utf8(import_pattern.head.name.clone()) {
|
stack.remove_env_var(engine_state, &env_var_name.item);
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
|
|
||||||
// The first word is a module
|
|
||||||
let module = engine_state.get_module(module_id);
|
|
||||||
|
|
||||||
let env_vars_to_hide = if import_pattern.members.is_empty() {
|
|
||||||
module.env_vars_with_head(&import_pattern.head.name)
|
|
||||||
} else {
|
|
||||||
match &import_pattern.members[0] {
|
|
||||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
|
||||||
ImportPatternMember::Name { name, span } => {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
if let Some((name, id)) =
|
|
||||||
module.env_var_with_head(name, &import_pattern.head.name)
|
|
||||||
{
|
|
||||||
output.push((name, id));
|
|
||||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
|
||||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
|
||||||
String::from_utf8_lossy(name).into(),
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
ImportPatternMember::List { names } => {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
for (name, span) in names {
|
|
||||||
if let Some((name, id)) =
|
|
||||||
module.env_var_with_head(name, &import_pattern.head.name)
|
|
||||||
{
|
|
||||||
output.push((name, id));
|
|
||||||
} else if !(module.has_alias(name) || module.has_decl(name)) {
|
|
||||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
|
||||||
String::from_utf8_lossy(name).into(),
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (name, _) in env_vars_to_hide {
|
|
||||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NonUtf8(import_pattern.span()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if stack.remove_env_var(engine_state, &name).is_none() {
|
|
||||||
return Err(ShellError::NotFound(
|
|
||||||
call.positional_nth(0)
|
|
||||||
.expect("already checked for present positional")
|
|
||||||
.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !import_pattern.hidden.contains(&import_pattern.head.name)
|
|
||||||
&& stack.remove_env_var(engine_state, &head_name_str).is_none()
|
|
||||||
{
|
|
||||||
// TODO: we may want to error in the future
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(PipelineData::new(call.head))
|
Ok(PipelineData::new(call.head))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod alias;
|
mod alias;
|
||||||
mod ast;
|
mod ast;
|
||||||
|
mod commandline;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod def;
|
mod def;
|
||||||
mod def_env;
|
mod def_env;
|
||||||
@ -11,7 +12,6 @@ mod export;
|
|||||||
mod export_alias;
|
mod export_alias;
|
||||||
mod export_def;
|
mod export_def;
|
||||||
mod export_def_env;
|
mod export_def_env;
|
||||||
mod export_env;
|
|
||||||
mod export_extern;
|
mod export_extern;
|
||||||
mod export_use;
|
mod export_use;
|
||||||
mod extern_;
|
mod extern_;
|
||||||
@ -30,6 +30,7 @@ mod version;
|
|||||||
|
|
||||||
pub use alias::Alias;
|
pub use alias::Alias;
|
||||||
pub use ast::Ast;
|
pub use ast::Ast;
|
||||||
|
pub use commandline::Commandline;
|
||||||
pub use debug::Debug;
|
pub use debug::Debug;
|
||||||
pub use def::Def;
|
pub use def::Def;
|
||||||
pub use def_env::DefEnv;
|
pub use def_env::DefEnv;
|
||||||
@ -41,7 +42,6 @@ pub use export::ExportCommand;
|
|||||||
pub use export_alias::ExportAlias;
|
pub use export_alias::ExportAlias;
|
||||||
pub use export_def::ExportDef;
|
pub use export_def::ExportDef;
|
||||||
pub use export_def_env::ExportDefEnv;
|
pub use export_def_env::ExportDefEnv;
|
||||||
pub use export_env::ExportEnvModule;
|
|
||||||
pub use export_extern::ExportExtern;
|
pub use export_extern::ExportExtern;
|
||||||
pub use export_use::ExportUse;
|
pub use export_use::ExportUse;
|
||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
|
@ -55,8 +55,8 @@ impl Command for Module {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Define an environment variable in a module and evaluate it",
|
description: "Define an environment variable in a module",
|
||||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||||
result: Some(Value::String {
|
result: Some(Value::String {
|
||||||
val: "BAZ".to_string(),
|
val: "BAZ".to_string(),
|
||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
|
@ -20,13 +20,13 @@ impl Command for OverlayHide {
|
|||||||
.optional("name", SyntaxShape::String, "Overlay to hide")
|
.optional("name", SyntaxShape::String, "Overlay to hide")
|
||||||
.switch(
|
.switch(
|
||||||
"keep-custom",
|
"keep-custom",
|
||||||
"Keep all newly added symbols within the next activated overlay",
|
"Keep all newly added commands and aliases in the next activated overlay",
|
||||||
Some('k'),
|
Some('k'),
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"keep-env",
|
"keep-env",
|
||||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||||
"List of environment variables to keep from the hidden overlay",
|
"List of environment variables to keep in the next activated overlay",
|
||||||
Some('e'),
|
Some('e'),
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
@ -67,23 +67,7 @@ impl Command for OverlayHide {
|
|||||||
let keep_env: Option<Vec<Spanned<String>>> =
|
let keep_env: Option<Vec<Spanned<String>>> =
|
||||||
call.get_flag(engine_state, stack, "keep-env")?;
|
call.get_flag(engine_state, stack, "keep-env")?;
|
||||||
|
|
||||||
let env_vars_to_keep = if call.has_flag("keep-custom") {
|
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
|
||||||
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
|
|
||||||
let overlay_frame = engine_state.get_overlay(overlay_id);
|
|
||||||
let origin_module = engine_state.get_module(overlay_frame.origin);
|
|
||||||
|
|
||||||
stack
|
|
||||||
.get_overlay_env_vars(engine_state, &overlay_name.item)
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
|
||||||
overlay_name.item,
|
|
||||||
overlay_name.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if let Some(env_var_names_to_keep) = keep_env {
|
|
||||||
let mut env_vars_to_keep = vec![];
|
let mut env_vars_to_keep = vec![];
|
||||||
|
|
||||||
for name in env_var_names_to_keep.into_iter() {
|
for name in env_var_names_to_keep.into_iter() {
|
||||||
@ -110,22 +94,25 @@ impl Command for OverlayHide {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Hide an overlay created from a module",
|
description: "Keep a custom command after hiding the overlay",
|
||||||
example: r#"module spam { export def foo [] { "foo" } }
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
overlay use spam
|
overlay use spam
|
||||||
overlay hide spam"#,
|
def bar [] { "bar" }
|
||||||
|
overlay hide spam --keep-custom
|
||||||
|
bar
|
||||||
|
"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Hide an overlay created from a file",
|
description: "Hide an overlay created from a file",
|
||||||
example: r#"echo 'export alias f = "foo"' | save spam.nu
|
example: r#"'export alias f = "foo"' | save spam.nu
|
||||||
overlay use spam.nu
|
overlay use spam.nu
|
||||||
overlay hide spam"#,
|
overlay hide spam"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Hide the last activated overlay",
|
description: "Hide the last activated overlay",
|
||||||
example: r#"module spam { export env FOO { "foo" } }
|
example: r#"module spam { export-env { let-env FOO = "foo" } }
|
||||||
overlay use spam
|
overlay use spam
|
||||||
overlay hide"#,
|
overlay hide"#,
|
||||||
result: None,
|
result: None,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
|
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
|
||||||
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::ast::{Call, Expr};
|
use nu_protocol::ast::{Call, Expr};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -55,7 +56,8 @@ impl Command for OverlayUse {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
|
||||||
|
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
|
||||||
|
|
||||||
let origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
|
let origin_module_id = if let Some(overlay_expr) = call.positional_nth(0) {
|
||||||
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
||||||
@ -121,28 +123,6 @@ impl Command for OverlayUse {
|
|||||||
|
|
||||||
let module = engine_state.get_module(module_id);
|
let module = engine_state.get_module(module_id);
|
||||||
|
|
||||||
for (name, block_id) in module.env_vars() {
|
|
||||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NonUtf8(call.head));
|
|
||||||
};
|
|
||||||
|
|
||||||
let block = engine_state.get_block(block_id);
|
|
||||||
|
|
||||||
let val = eval_block(
|
|
||||||
engine_state,
|
|
||||||
caller_stack,
|
|
||||||
block,
|
|
||||||
PipelineData::new(call.head),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)?
|
|
||||||
.into_value(call.head);
|
|
||||||
|
|
||||||
caller_stack.add_env_var(name, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the export-env block (if any) and keep its environment
|
// Evaluate the export-env block (if any) and keep its environment
|
||||||
if let Some(block_id) = module.env_block {
|
if let Some(block_id) = module.env_block {
|
||||||
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
|
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
|
||||||
@ -191,19 +171,26 @@ impl Command for OverlayUse {
|
|||||||
description: "Create an overlay from a module",
|
description: "Create an overlay from a module",
|
||||||
example: r#"module spam { export def foo [] { "foo" } }
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
overlay use spam
|
overlay use spam
|
||||||
|
foo"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Create an overlay from a module and rename it",
|
||||||
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
|
overlay use spam as spam_new
|
||||||
foo"#,
|
foo"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Create an overlay with a prefix",
|
description: "Create an overlay with a prefix",
|
||||||
example: r#"echo 'export def foo { "foo" }'
|
example: r#"'export def foo { "foo" }'
|
||||||
overlay use --prefix spam
|
overlay use --prefix spam
|
||||||
spam foo"#,
|
spam foo"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Create an overlay from a file",
|
description: "Create an overlay from a file",
|
||||||
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
|
example: r#"'export-env { let-env FOO = "foo" }' | save spam.nu
|
||||||
overlay use spam.nu
|
overlay use spam.nu
|
||||||
$env.FOO"#,
|
$env.FOO"#,
|
||||||
result: None,
|
result: None,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::{eval_block, find_in_dirs_env, redirect_env};
|
||||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember};
|
use nu_protocol::ast::{Call, Expr, Expression};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
@ -35,9 +35,9 @@ impl Command for Use {
|
|||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
caller_stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let import_pattern = if let Some(Expression {
|
let import_pattern = if let Some(Expression {
|
||||||
expr: Expr::ImportPattern(pat),
|
expr: Expr::ImportPattern(pat),
|
||||||
@ -58,68 +58,47 @@ impl Command for Use {
|
|||||||
if let Some(module_id) = import_pattern.head.id {
|
if let Some(module_id) = import_pattern.head.id {
|
||||||
let module = engine_state.get_module(module_id);
|
let module = engine_state.get_module(module_id);
|
||||||
|
|
||||||
let env_vars_to_use = if import_pattern.members.is_empty() {
|
// Evaluate the export-env block if there is one
|
||||||
module.env_vars_with_head(&import_pattern.head.name)
|
if let Some(block_id) = module.env_block {
|
||||||
} else {
|
|
||||||
match &import_pattern.members[0] {
|
|
||||||
ImportPatternMember::Glob { .. } => module.env_vars(),
|
|
||||||
ImportPatternMember::Name { name, span } => {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
if let Some(id) = module.get_env_var_id(name) {
|
|
||||||
output.push((name.clone(), id));
|
|
||||||
} else if !module.has_decl(name) && !module.has_alias(name) {
|
|
||||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
|
||||||
String::from_utf8_lossy(name).into(),
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
ImportPatternMember::List { names } => {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
for (name, span) in names {
|
|
||||||
if let Some(id) = module.get_env_var_id(name) {
|
|
||||||
output.push((name.clone(), id));
|
|
||||||
} else if !module.has_decl(name) && !module.has_alias(name) {
|
|
||||||
return Err(ShellError::EnvVarNotFoundAtRuntime(
|
|
||||||
String::from_utf8_lossy(name).into(),
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (name, block_id) in env_vars_to_use {
|
|
||||||
let name = if let Ok(s) = String::from_utf8(name.clone()) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NonUtf8(import_pattern.head.span));
|
|
||||||
};
|
|
||||||
|
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let val = eval_block(
|
// See if the module is a file
|
||||||
engine_state,
|
let module_arg_str = String::from_utf8_lossy(
|
||||||
stack,
|
engine_state.get_span_contents(&import_pattern.head.span),
|
||||||
block,
|
);
|
||||||
PipelineData::new(call.head),
|
let maybe_parent = if let Some(path) =
|
||||||
false,
|
find_in_dirs_env(&module_arg_str, engine_state, caller_stack)?
|
||||||
true,
|
{
|
||||||
)?
|
path.parent().map(|p| p.to_path_buf()).or(None)
|
||||||
.into_value(call.head);
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
stack.add_env_var(name, val);
|
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||||
|
|
||||||
|
// If so, set the currently evaluated directory (file-relative PWD)
|
||||||
|
if let Some(parent) = maybe_parent {
|
||||||
|
let file_pwd = Value::String {
|
||||||
|
val: parent.to_string_lossy().to_string(),
|
||||||
|
span: call.head,
|
||||||
|
};
|
||||||
|
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the block (discard the result)
|
||||||
|
let _ = eval_block(
|
||||||
|
engine_state,
|
||||||
|
&mut callee_stack,
|
||||||
|
block,
|
||||||
|
input,
|
||||||
|
call.redirect_stdout,
|
||||||
|
call.redirect_stderr,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Merge the block's environment to the current stack
|
||||||
|
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: This is a workaround since call.positional[0].span points at 0 for some reason
|
|
||||||
// when this error is triggered
|
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
format!(
|
format!(
|
||||||
"Could not import from '{}'",
|
"Could not import from '{}'",
|
||||||
@ -145,14 +124,6 @@ impl Command for Use {
|
|||||||
span: Span::test_data(),
|
span: Span::test_data(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
description: "Define an environment variable in a module and evaluate it",
|
|
||||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
|
||||||
result: Some(Value::String {
|
|
||||||
val: "BAZ".to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
description: "Define a custom command that participates in the environment in a module and call it",
|
description: "Define a custom command that participates in the environment in a module and call it",
|
||||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||||
|
@ -59,7 +59,7 @@ pub fn version(
|
|||||||
|
|
||||||
cols.push("branch".to_string());
|
cols.push("branch".to_string());
|
||||||
vals.push(Value::String {
|
vals.push(Value::String {
|
||||||
val: shadow_rs::branch(),
|
val: shadow::BRANCH.to_string(),
|
||||||
span: call.head,
|
span: call.head,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ impl Command for AndDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||||
match query.body {
|
match *query.body {
|
||||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
|
@ -113,7 +113,7 @@ fn alias_db(
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
Some(statement) => match statement {
|
Some(statement) => match statement {
|
||||||
Statement::Query(query) => match &mut query.body {
|
Statement::Query(query) => match &mut *query.body {
|
||||||
SetExpr::Select(select) => {
|
SetExpr::Select(select) => {
|
||||||
select.as_mut().from.iter_mut().for_each(|table| {
|
select.as_mut().from.iter_mut().for_each(|table| {
|
||||||
let new_alias = Some(TableAlias {
|
let new_alias = Some(TableAlias {
|
@ -17,7 +17,7 @@ pub fn value_into_table_factor(
|
|||||||
Ok(TableFactor::Table {
|
Ok(TableFactor::Table {
|
||||||
name: ObjectName(vec![ident]),
|
name: ObjectName(vec![ident]),
|
||||||
alias,
|
alias,
|
||||||
args: Vec::new(),
|
args: None,
|
||||||
with_hints: Vec::new(),
|
with_hints: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -96,12 +96,12 @@ fn create_statement(
|
|||||||
) -> Result<Statement, ShellError> {
|
) -> Result<Statement, ShellError> {
|
||||||
let query = Query {
|
let query = Query {
|
||||||
with: None,
|
with: None,
|
||||||
body: SetExpr::Select(Box::new(create_select(
|
body: Box::new(SetExpr::Select(Box::new(create_select(
|
||||||
connection,
|
connection,
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
call,
|
call,
|
||||||
)?)),
|
)?))),
|
||||||
order_by: Vec::new(),
|
order_by: Vec::new(),
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
@ -121,18 +121,18 @@ fn modify_statement(
|
|||||||
) -> Result<Statement, ShellError> {
|
) -> Result<Statement, ShellError> {
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Query(ref mut query) => {
|
Statement::Query(ref mut query) => {
|
||||||
match query.body {
|
match *query.body {
|
||||||
SetExpr::Select(ref mut select) => {
|
SetExpr::Select(ref mut select) => {
|
||||||
let table = create_table(connection, engine_state, stack, call)?;
|
let table = create_table(connection, engine_state, stack, call)?;
|
||||||
select.from.push(table);
|
select.from.push(table);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(
|
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
|
||||||
connection,
|
connection,
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
call,
|
call,
|
||||||
)?));
|
)?)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,6 +167,7 @@ fn create_select(
|
|||||||
distribute_by: Vec::new(),
|
distribute_by: Vec::new(),
|
||||||
sort_by: Vec::new(),
|
sort_by: Vec::new(),
|
||||||
having: None,
|
having: None,
|
||||||
|
qualify: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ impl Command for GroupByDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Group by query"
|
"Group-by query"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -104,7 +104,7 @@ impl Command for GroupByDb {
|
|||||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||||
match db.statement.as_mut() {
|
match db.statement.as_mut() {
|
||||||
Some(statement) => match statement {
|
Some(statement) => match statement {
|
||||||
Statement::Query(ref mut query) => match &mut query.body {
|
Statement::Query(ref mut query) => match &mut *query.body {
|
||||||
SetExpr::Select(ref mut select) => select.group_by = expressions,
|
SetExpr::Select(ref mut select) => select.group_by = expressions,
|
||||||
s => {
|
s => {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
|
@ -15,13 +15,12 @@ impl Command for ToDataBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Converts into an open db connection"
|
"Converts the input into an open db connection"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
"This function is used as type hint for parser, specially if the query is not started with 'from table'"
|
"This function is used as a hint to Nushell to optimize the pipeline for database queries."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.input_type(Type::Any)
|
.input_type(Type::Any)
|
||||||
@ -35,7 +34,7 @@ impl Command for ToDataBase {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Converts an open file into a db object",
|
description: "Converts an open file into a db object.",
|
||||||
example: "open db.sqlite | into db",
|
example: "open db.sqlite | into db",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
@ -248,14 +248,8 @@ fn nu_value_to_string(value: Value, separator: &str, config: &Config) -> String
|
|||||||
}
|
}
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
// don't store ansi escape sequences in the database
|
// don't store ansi escape sequences in the database
|
||||||
let stripped = {
|
|
||||||
match strip_ansi_escapes::strip(&val) {
|
|
||||||
Ok(item) => String::from_utf8(item).unwrap_or(val),
|
|
||||||
Err(_) => val,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// escape single quotes
|
// escape single quotes
|
||||||
stripped.replace('\'', "''")
|
nu_utils::strip_ansi_unlikely(&val).replace('\'', "''")
|
||||||
}
|
}
|
||||||
Value::List { vals: val, .. } => val
|
Value::List { vals: val, .. } => val
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -146,7 +146,7 @@ fn modify_statement(
|
|||||||
) -> Result<Statement, ShellError> {
|
) -> Result<Statement, ShellError> {
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Query(ref mut query) => {
|
Statement::Query(ref mut query) => {
|
||||||
match &mut query.body {
|
match &mut *query.body {
|
||||||
SetExpr::Select(ref mut select) => {
|
SetExpr::Select(ref mut select) => {
|
||||||
modify_from(connection, select, engine_state, stack, call)?
|
modify_from(connection, select, engine_state, stack, call)?
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
// Conversions between value and sqlparser objects
|
// Conversions between value and sqlparser objects
|
||||||
pub mod conversions;
|
pub mod conversions;
|
||||||
|
|
||||||
mod alias;
|
|
||||||
mod and;
|
mod and;
|
||||||
|
mod as_;
|
||||||
mod collect;
|
mod collect;
|
||||||
mod describe;
|
mod describe;
|
||||||
mod from;
|
mod from_table;
|
||||||
mod group_by;
|
mod group_by;
|
||||||
|
mod into_db;
|
||||||
mod into_sqlite;
|
mod into_sqlite;
|
||||||
mod join;
|
mod join;
|
||||||
mod limit;
|
mod limit;
|
||||||
mod open;
|
mod open_db;
|
||||||
mod or;
|
mod or;
|
||||||
mod order_by;
|
mod order_by;
|
||||||
mod query;
|
mod query_db;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod select;
|
mod select;
|
||||||
mod to_db;
|
|
||||||
mod where_;
|
mod where_;
|
||||||
|
|
||||||
// Temporal module to create Query objects
|
// Temporal module to create Query objects
|
||||||
mod testing;
|
mod testing_db;
|
||||||
use testing::TestingDb;
|
use testing_db::TestingDb;
|
||||||
|
|
||||||
use alias::AliasDb;
|
|
||||||
use and::AndDb;
|
use and::AndDb;
|
||||||
|
use as_::AliasDb;
|
||||||
use collect::CollectDb;
|
use collect::CollectDb;
|
||||||
pub(crate) use describe::DescribeDb;
|
pub(crate) use describe::DescribeDb;
|
||||||
pub(crate) use from::FromDb;
|
pub(crate) use from_table::FromDb;
|
||||||
use group_by::GroupByDb;
|
use group_by::GroupByDb;
|
||||||
|
pub(crate) use into_db::ToDataBase;
|
||||||
use into_sqlite::IntoSqliteDb;
|
use into_sqlite::IntoSqliteDb;
|
||||||
use join::JoinDb;
|
use join::JoinDb;
|
||||||
use limit::LimitDb;
|
use limit::LimitDb;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use open::OpenDb;
|
use open_db::OpenDb;
|
||||||
use or::OrDb;
|
use or::OrDb;
|
||||||
use order_by::OrderByDb;
|
use order_by::OrderByDb;
|
||||||
use query::QueryDb;
|
use query_db::QueryDb;
|
||||||
use schema::SchemaDb;
|
use schema::SchemaDb;
|
||||||
pub(crate) use select::ProjectionDb;
|
pub(crate) use select::ProjectionDb;
|
||||||
pub(crate) use to_db::ToDataBase;
|
|
||||||
use where_::WhereDb;
|
use where_::WhereDb;
|
||||||
|
|
||||||
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||||
|
@ -125,7 +125,7 @@ impl Command for OrDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||||
match query.body {
|
match *query.body {
|
||||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
|
@ -108,7 +108,7 @@ impl Command for ProjectionDb {
|
|||||||
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
|
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
|
||||||
let query = Query {
|
let query = Query {
|
||||||
with: None,
|
with: None,
|
||||||
body: SetExpr::Select(Box::new(create_select(expressions))),
|
body: Box::new(SetExpr::Select(Box::new(create_select(expressions)))),
|
||||||
order_by: Vec::new(),
|
order_by: Vec::new(),
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
@ -126,10 +126,11 @@ fn modify_statement(
|
|||||||
) -> Result<Statement, ShellError> {
|
) -> Result<Statement, ShellError> {
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Query(ref mut query) => {
|
Statement::Query(ref mut query) => {
|
||||||
match query.body {
|
match *query.body {
|
||||||
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
|
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
|
||||||
_ => {
|
_ => {
|
||||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(expressions)));
|
query.as_mut().body =
|
||||||
|
Box::new(SetExpr::Select(Box::new(create_select(expressions))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,6 +160,7 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
|
|||||||
distribute_by: Vec::new(),
|
distribute_by: Vec::new(),
|
||||||
sort_by: Vec::new(),
|
sort_by: Vec::new(),
|
||||||
having: None,
|
having: None,
|
||||||
|
qualify: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,10 +99,10 @@ impl Command for WhereDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modify_query(query: &mut Box<Query>, expression: Expr) {
|
fn modify_query(query: &mut Box<Query>, expression: Expr) {
|
||||||
match query.body {
|
match *query.body {
|
||||||
SetExpr::Select(ref mut select) => modify_select(select, expression),
|
SetExpr::Select(ref mut select) => modify_select(select, expression),
|
||||||
_ => {
|
_ => {
|
||||||
query.as_mut().body = SetExpr::Select(Box::new(create_select(expression)));
|
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(expression))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -125,6 +125,7 @@ fn create_select(expression: Expr) -> Select {
|
|||||||
distribute_by: Vec::new(),
|
distribute_by: Vec::new(),
|
||||||
sort_by: Vec::new(),
|
sort_by: Vec::new(),
|
||||||
having: None,
|
having: None,
|
||||||
|
qualify: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +132,7 @@ impl Command for FunctionExpr {
|
|||||||
args,
|
args,
|
||||||
over: None,
|
over: None,
|
||||||
distinct: call.has_flag("distinct"),
|
distinct: call.has_flag("distinct"),
|
||||||
|
special: false,
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
@ -124,7 +124,8 @@ impl CustomValue for ExprDb {
|
|||||||
| Operator::ShiftLeft
|
| Operator::ShiftLeft
|
||||||
| Operator::ShiftRight
|
| Operator::ShiftRight
|
||||||
| Operator::StartsWith
|
| Operator::StartsWith
|
||||||
| Operator::EndsWith => Err(ShellError::UnsupportedOperator(operator, op)),
|
| Operator::EndsWith
|
||||||
|
| Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let expr = Expr::BinaryOp {
|
let expr = Expr::BinaryOp {
|
||||||
@ -339,7 +340,7 @@ impl ExprDb {
|
|||||||
Expr::TypedString { .. } => todo!(),
|
Expr::TypedString { .. } => todo!(),
|
||||||
Expr::MapAccess { .. } => todo!(),
|
Expr::MapAccess { .. } => todo!(),
|
||||||
Expr::Case { .. } => todo!(),
|
Expr::Case { .. } => todo!(),
|
||||||
Expr::Exists(_) => todo!(),
|
Expr::Exists { .. } => todo!(),
|
||||||
Expr::Subquery(_) => todo!(),
|
Expr::Subquery(_) => todo!(),
|
||||||
Expr::ListAgg(_) => todo!(),
|
Expr::ListAgg(_) => todo!(),
|
||||||
Expr::GroupingSets(_) => todo!(),
|
Expr::GroupingSets(_) => todo!(),
|
||||||
@ -348,6 +349,25 @@ impl ExprDb {
|
|||||||
Expr::Tuple(_) => todo!(),
|
Expr::Tuple(_) => todo!(),
|
||||||
Expr::ArrayIndex { .. } => todo!(),
|
Expr::ArrayIndex { .. } => todo!(),
|
||||||
Expr::Array(_) => todo!(),
|
Expr::Array(_) => todo!(),
|
||||||
|
Expr::JsonAccess { .. } => todo!(),
|
||||||
|
Expr::CompositeAccess { .. } => todo!(),
|
||||||
|
Expr::IsFalse(_) => todo!(),
|
||||||
|
Expr::IsNotFalse(_) => todo!(),
|
||||||
|
Expr::IsTrue(_) => todo!(),
|
||||||
|
Expr::IsNotTrue(_) => todo!(),
|
||||||
|
Expr::IsUnknown(_) => todo!(),
|
||||||
|
Expr::IsNotUnknown(_) => todo!(),
|
||||||
|
Expr::Like { .. } => todo!(),
|
||||||
|
Expr::ILike { .. } => todo!(),
|
||||||
|
Expr::SimilarTo { .. } => todo!(),
|
||||||
|
Expr::AnyOp(_) => todo!(),
|
||||||
|
Expr::AllOp(_) => todo!(),
|
||||||
|
Expr::SafeCast { .. } => todo!(),
|
||||||
|
Expr::AtTimeZone { .. } => todo!(),
|
||||||
|
Expr::Position { .. } => todo!(),
|
||||||
|
Expr::Overlay { .. } => todo!(),
|
||||||
|
Expr::AggregateExpressionWithFilter { .. } => todo!(),
|
||||||
|
Expr::ArraySubquery(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,6 @@ impl Command for ColumnsDF {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_collect)]
|
|
||||||
fn command(
|
fn command(
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
|
@ -56,7 +56,6 @@ impl Command for DataTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_collect)]
|
|
||||||
fn command(
|
fn command(
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
_stack: &mut Stack,
|
||||||
|
@ -13,11 +13,15 @@ mod last;
|
|||||||
mod list;
|
mod list;
|
||||||
mod melt;
|
mod melt;
|
||||||
mod open;
|
mod open;
|
||||||
|
mod query_df;
|
||||||
mod rename;
|
mod rename;
|
||||||
mod sample;
|
mod sample;
|
||||||
mod shape;
|
mod shape;
|
||||||
mod slice;
|
mod slice;
|
||||||
|
mod sql_context;
|
||||||
|
mod sql_expr;
|
||||||
mod take;
|
mod take;
|
||||||
|
mod to_arrow;
|
||||||
mod to_csv;
|
mod to_csv;
|
||||||
mod to_df;
|
mod to_df;
|
||||||
mod to_nu;
|
mod to_nu;
|
||||||
@ -41,11 +45,15 @@ pub use last::LastDF;
|
|||||||
pub use list::ListDF;
|
pub use list::ListDF;
|
||||||
pub use melt::MeltDF;
|
pub use melt::MeltDF;
|
||||||
pub use open::OpenDataFrame;
|
pub use open::OpenDataFrame;
|
||||||
|
pub use query_df::QueryDf;
|
||||||
pub use rename::RenameDF;
|
pub use rename::RenameDF;
|
||||||
pub use sample::SampleDF;
|
pub use sample::SampleDF;
|
||||||
pub use shape::ShapeDF;
|
pub use shape::ShapeDF;
|
||||||
pub use slice::SliceDF;
|
pub use slice::SliceDF;
|
||||||
|
pub use sql_context::SQLContext;
|
||||||
|
pub use sql_expr::parse_sql_expr;
|
||||||
pub use take::TakeDF;
|
pub use take::TakeDF;
|
||||||
|
pub use to_arrow::ToArrow;
|
||||||
pub use to_csv::ToCSV;
|
pub use to_csv::ToCSV;
|
||||||
pub use to_df::ToDataFrame;
|
pub use to_df::ToDataFrame;
|
||||||
pub use to_nu::ToNu;
|
pub use to_nu::ToNu;
|
||||||
@ -79,11 +87,13 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
|||||||
ListDF,
|
ListDF,
|
||||||
MeltDF,
|
MeltDF,
|
||||||
OpenDataFrame,
|
OpenDataFrame,
|
||||||
|
QueryDf,
|
||||||
RenameDF,
|
RenameDF,
|
||||||
SampleDF,
|
SampleDF,
|
||||||
ShapeDF,
|
ShapeDF,
|
||||||
SliceDF,
|
SliceDF,
|
||||||
TakeDF,
|
TakeDF,
|
||||||
|
ToArrow,
|
||||||
ToCSV,
|
ToCSV,
|
||||||
ToDataFrame,
|
ToDataFrame,
|
||||||
ToNu,
|
ToNu,
|
||||||
|
@ -9,8 +9,8 @@ use nu_protocol::{
|
|||||||
use std::{fs::File, io::BufReader, path::PathBuf};
|
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||||
|
|
||||||
use polars::prelude::{
|
use polars::prelude::{
|
||||||
CsvEncoding, CsvReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy, ParquetReader,
|
CsvEncoding, CsvReader, IpcReader, JsonReader, LazyCsvReader, LazyFrame, ParallelStrategy,
|
||||||
ScanArgsParquet, SerReader,
|
ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -22,7 +22,7 @@ impl Command for OpenDataFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Opens csv, json or parquet file to create dataframe"
|
"Opens csv, json, arrow, or parquet file to create dataframe"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
@ -33,6 +33,12 @@ impl Command for OpenDataFrame {
|
|||||||
"file path to load values from",
|
"file path to load values from",
|
||||||
)
|
)
|
||||||
.switch("lazy", "creates a lazy dataframe", Some('l'))
|
.switch("lazy", "creates a lazy dataframe", Some('l'))
|
||||||
|
.named(
|
||||||
|
"type",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"File type: csv, tsv, json, parquet, arrow. If omitted, derive from file extension",
|
||||||
|
Some('t'),
|
||||||
|
)
|
||||||
.named(
|
.named(
|
||||||
"delimiter",
|
"delimiter",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -93,15 +99,33 @@ fn command(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
match file.item.extension() {
|
let type_option: Option<Spanned<String>> = call.get_flag(engine_state, stack, "type")?;
|
||||||
Some(e) => match e.to_str() {
|
|
||||||
Some("csv") | Some("tsv") => from_csv(engine_state, stack, call),
|
let type_id = match &type_option {
|
||||||
Some("parquet") => from_parquet(engine_state, stack, call),
|
Some(ref t) => Some((t.item.to_owned(), "Invalid type", t.span)),
|
||||||
Some("json") => from_json(engine_state, stack, call),
|
None => match file.item.extension() {
|
||||||
_ => Err(ShellError::FileNotFoundCustom(
|
Some(e) => Some((
|
||||||
"Not a csv, tsv, parquet or json file".into(),
|
e.to_string_lossy().into_owned(),
|
||||||
|
"Invalid extension",
|
||||||
file.span,
|
file.span,
|
||||||
)),
|
)),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match type_id {
|
||||||
|
Some((e, msg, blamed)) => match e.as_str() {
|
||||||
|
"csv" | "tsv" => from_csv(engine_state, stack, call),
|
||||||
|
"parquet" => from_parquet(engine_state, stack, call),
|
||||||
|
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
|
||||||
|
"json" => from_json(engine_state, stack, call),
|
||||||
|
_ => Err(ShellError::FileNotFoundCustom(
|
||||||
|
format!(
|
||||||
|
"{}. Supported values: csv, tsv, parquet, ipc, arrow, json",
|
||||||
|
msg
|
||||||
|
),
|
||||||
|
blamed,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
None => Err(ShellError::FileNotFoundCustom(
|
None => Err(ShellError::FileNotFoundCustom(
|
||||||
"File without extension".into(),
|
"File without extension".into(),
|
||||||
@ -177,6 +201,70 @@ fn from_parquet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_ipc(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
if call.has_flag("lazy") {
|
||||||
|
let file: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let args = ScanArgsIpc {
|
||||||
|
n_rows: None,
|
||||||
|
cache: true,
|
||||||
|
rechunk: false,
|
||||||
|
row_count: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
||||||
|
.map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"IPC reader error".into(),
|
||||||
|
format!("{:?}", e),
|
||||||
|
Some(call.head),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
df.into_value(call.head)
|
||||||
|
} else {
|
||||||
|
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 = IpcReader::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(
|
||||||
|
"IPC reader error".into(),
|
||||||
|
format!("{:?}", e),
|
||||||
|
Some(call.head),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
Ok(df.into_value(call.head))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from_json(
|
fn from_json(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
106
crates/nu-command/src/dataframe/eager/query_df.rs
Normal file
106
crates/nu-command/src/dataframe/eager/query_df.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use super::super::values::NuDataFrame;
|
||||||
|
use crate::dataframe::values::Column;
|
||||||
|
use crate::dataframe::{eager::SQLContext, values::NuLazyFrame};
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// attribution:
|
||||||
|
// sql_context.rs, and sql_expr.rs were copied from polars-sql. thank you.
|
||||||
|
// maybe we should just use the crate at some point but it's not published yet.
|
||||||
|
// https://github.com/pola-rs/polars/tree/master/polars-sql
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct QueryDf;
|
||||||
|
|
||||||
|
impl Command for QueryDf {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"query df"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Query dataframe using SQL. Note: The dataframe is always named 'df' in your query's from clause."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required("sql", SyntaxShape::String, "sql query")
|
||||||
|
.input_type(Type::Custom("dataframe".into()))
|
||||||
|
.output_type(Type::Custom("dataframe".into()))
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["dataframe", "sql", "search"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Query dataframe using SQL",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | into df | query df 'select a from df'",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_columns(vec![Column::new(
|
||||||
|
"a".to_string(),
|
||||||
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
|
)])
|
||||||
|
.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> {
|
||||||
|
command(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let sql_query: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
|
let mut ctx = SQLContext::new();
|
||||||
|
ctx.register("df", &df.df);
|
||||||
|
let df_sql = ctx.execute(&sql_query).map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Dataframe Error".into(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(call.head),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let lazy = NuLazyFrame::new(false, df_sql);
|
||||||
|
|
||||||
|
let eager = lazy.collect(call.head)?;
|
||||||
|
let value = Value::CustomValue {
|
||||||
|
val: Box::new(eager),
|
||||||
|
span: call.head,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(value, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
test_dataframe(vec![Box::new(QueryDf {})])
|
||||||
|
}
|
||||||
|
}
|
220
crates/nu-command/src/dataframe/eager/sql_context.rs
Normal file
220
crates/nu-command/src/dataframe/eager/sql_context.rs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
use crate::dataframe::eager::sql_expr::parse_sql_expr;
|
||||||
|
use polars::error::PolarsError;
|
||||||
|
use polars::prelude::{col, DataFrame, DataType, IntoLazy, LazyFrame};
|
||||||
|
use sqlparser::ast::{
|
||||||
|
Expr as SqlExpr, Select, SelectItem, SetExpr, Statement, TableFactor, Value as SQLValue,
|
||||||
|
};
|
||||||
|
use sqlparser::dialect::GenericDialect;
|
||||||
|
use sqlparser::parser::Parser;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SQLContext {
|
||||||
|
table_map: HashMap<String, LazyFrame>,
|
||||||
|
dialect: GenericDialect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SQLContext {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
table_map: HashMap::new(),
|
||||||
|
dialect: GenericDialect::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, name: &str, df: &DataFrame) {
|
||||||
|
self.table_map.insert(name.to_owned(), df.clone().lazy());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
|
||||||
|
// Determine involved dataframe
|
||||||
|
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
|
||||||
|
let tbl = select_stmt.from.get(0).ok_or_else(|| {
|
||||||
|
PolarsError::NotFound("No table found in select statement".to_string())
|
||||||
|
})?;
|
||||||
|
let mut alias_map = HashMap::new();
|
||||||
|
let tbl_name = match &tbl.relation {
|
||||||
|
TableFactor::Table { name, alias, .. } => {
|
||||||
|
let tbl_name = name
|
||||||
|
.0
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
PolarsError::NotFound("No table found in select statement".to_string())
|
||||||
|
})?
|
||||||
|
.value
|
||||||
|
.to_string();
|
||||||
|
if self.table_map.contains_key(&tbl_name) {
|
||||||
|
if let Some(alias) = alias {
|
||||||
|
alias_map.insert(alias.name.value.clone(), tbl_name.to_owned());
|
||||||
|
};
|
||||||
|
tbl_name
|
||||||
|
} else {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!("Table name {tbl_name} was not found").into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Support bare table, optional with alias for now
|
||||||
|
_ => return Err(PolarsError::ComputeError("Not implemented".into())),
|
||||||
|
};
|
||||||
|
let df = &self.table_map[&tbl_name];
|
||||||
|
let mut raw_projection_before_alias: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut contain_wildcard = false;
|
||||||
|
// Filter Expression
|
||||||
|
let df = match select_stmt.selection.as_ref() {
|
||||||
|
Some(expr) => {
|
||||||
|
let filter_expression = parse_sql_expr(expr)?;
|
||||||
|
df.clone().filter(filter_expression)
|
||||||
|
}
|
||||||
|
None => df.clone(),
|
||||||
|
};
|
||||||
|
// Column Projections
|
||||||
|
let projection = select_stmt
|
||||||
|
.projection
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, select_item)| {
|
||||||
|
Ok(match select_item {
|
||||||
|
SelectItem::UnnamedExpr(expr) => {
|
||||||
|
let expr = parse_sql_expr(expr)?;
|
||||||
|
raw_projection_before_alias.insert(format!("{:?}", expr), i);
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
SelectItem::ExprWithAlias { expr, alias } => {
|
||||||
|
let expr = parse_sql_expr(expr)?;
|
||||||
|
raw_projection_before_alias.insert(format!("{:?}", expr), i);
|
||||||
|
expr.alias(&alias.value)
|
||||||
|
}
|
||||||
|
SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => {
|
||||||
|
contain_wildcard = true;
|
||||||
|
col("*")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, PolarsError>>()?;
|
||||||
|
// Check for group by
|
||||||
|
// After projection since there might be number.
|
||||||
|
let group_by = select_stmt
|
||||||
|
.group_by
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|e|match e {
|
||||||
|
SqlExpr::Value(SQLValue::Number(idx, _)) => {
|
||||||
|
let idx = match idx.parse::<usize>() {
|
||||||
|
Ok(0)| Err(_) => Err(
|
||||||
|
PolarsError::ComputeError(
|
||||||
|
format!("Group-By Error: Only positive number or expression are supported, got {idx}").into()
|
||||||
|
)),
|
||||||
|
Ok(idx) => Ok(idx)
|
||||||
|
}?;
|
||||||
|
Ok(projection[idx].clone())
|
||||||
|
}
|
||||||
|
SqlExpr::Value(_) => Err(
|
||||||
|
PolarsError::ComputeError("Group-By Error: Only positive number or expression are supported".into())
|
||||||
|
),
|
||||||
|
_ => parse_sql_expr(e)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.collect::<Result<Vec<_>, PolarsError>>()?;
|
||||||
|
|
||||||
|
let df = if group_by.is_empty() {
|
||||||
|
df.select(projection)
|
||||||
|
} else {
|
||||||
|
// check groupby and projection due to difference between SQL and polars
|
||||||
|
// Return error on wild card, shouldn't process this
|
||||||
|
if contain_wildcard {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
"Group-By Error: Can't process wildcard in group-by".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Default polars group by will have group by columns at the front
|
||||||
|
// need some container to contain position of group by columns and its position
|
||||||
|
// at the final agg projection, check the schema for the existence of group by column
|
||||||
|
// and its projections columns, keeping the original index
|
||||||
|
let (exclude_expr, groupby_pos): (Vec<_>, Vec<_>) = group_by
|
||||||
|
.iter()
|
||||||
|
.map(|expr| raw_projection_before_alias.get(&format!("{:?}", expr)))
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, proj_p)| proj_p.is_some())
|
||||||
|
.map(|(gb_p, proj_p)| (*proj_p.unwrap_or(&0), (*proj_p.unwrap_or(&0), gb_p)))
|
||||||
|
.unzip();
|
||||||
|
let (agg_projection, agg_proj_pos): (Vec<_>, Vec<_>) = projection
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| !exclude_expr.contains(i))
|
||||||
|
.enumerate()
|
||||||
|
.map(|(agg_pj, (proj_p, expr))| (expr.clone(), (proj_p, agg_pj + group_by.len())))
|
||||||
|
.unzip();
|
||||||
|
let agg_df = df.groupby(group_by).agg(agg_projection);
|
||||||
|
let mut final_proj_pos = groupby_pos
|
||||||
|
.into_iter()
|
||||||
|
.chain(agg_proj_pos.into_iter())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
final_proj_pos.sort_by(|(proj_pa, _), (proj_pb, _)| proj_pa.cmp(proj_pb));
|
||||||
|
let final_proj = final_proj_pos
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, shm_p)| {
|
||||||
|
col(agg_df
|
||||||
|
.clone()
|
||||||
|
// FIXME: had to do this mess to get get_index to work, not sure why. need help
|
||||||
|
.collect()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.schema()
|
||||||
|
.get_index(shm_p)
|
||||||
|
.unwrap_or((&"".to_string(), &DataType::Null))
|
||||||
|
.0)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
agg_df.select(final_proj)
|
||||||
|
};
|
||||||
|
Ok(df)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(&self, query: &str) -> Result<LazyFrame, PolarsError> {
|
||||||
|
let ast = Parser::parse_sql(&self.dialect, query)
|
||||||
|
.map_err(|e| PolarsError::ComputeError(format!("{:?}", e).into()))?;
|
||||||
|
if ast.len() != 1 {
|
||||||
|
Err(PolarsError::ComputeError(
|
||||||
|
"One and only one statement at a time please".into(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let ast = ast
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| PolarsError::NotFound("No statement found".to_string()))?;
|
||||||
|
Ok(match ast {
|
||||||
|
Statement::Query(query) => {
|
||||||
|
let rs = match &*query.body {
|
||||||
|
SetExpr::Select(select_stmt) => self.execute_select(select_stmt)?,
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
"INSERT, UPDATE is not supported for polars".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match &query.limit {
|
||||||
|
Some(SqlExpr::Value(SQLValue::Number(nrow, _))) => {
|
||||||
|
let nrow = nrow.parse().map_err(|err| {
|
||||||
|
PolarsError::ComputeError(
|
||||||
|
format!("Conversion Error: {:?}", err).into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
rs.limit(nrow)
|
||||||
|
}
|
||||||
|
None => rs,
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
"Only support number argument to LIMIT clause".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!("Statement type {:?} is not supported", ast).into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
191
crates/nu-command/src/dataframe/eager/sql_expr.rs
Normal file
191
crates/nu-command/src/dataframe/eager/sql_expr.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use polars::error::PolarsError;
|
||||||
|
use polars::prelude::{col, lit, DataType, Expr, LiteralValue, Result, TimeUnit};
|
||||||
|
|
||||||
|
use sqlparser::ast::{
|
||||||
|
BinaryOperator as SQLBinaryOperator, DataType as SQLDataType, Expr as SqlExpr,
|
||||||
|
Function as SQLFunction, Value as SqlValue, WindowSpec,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
||||||
|
Ok(match data_type {
|
||||||
|
SQLDataType::Char(_)
|
||||||
|
| SQLDataType::Varchar(_)
|
||||||
|
| SQLDataType::Uuid
|
||||||
|
| SQLDataType::Clob(_)
|
||||||
|
| SQLDataType::Text
|
||||||
|
| SQLDataType::String => DataType::Utf8,
|
||||||
|
SQLDataType::Float(_) => DataType::Float32,
|
||||||
|
SQLDataType::Real => DataType::Float32,
|
||||||
|
SQLDataType::Double => DataType::Float64,
|
||||||
|
SQLDataType::TinyInt(_) => DataType::Int8,
|
||||||
|
SQLDataType::UnsignedTinyInt(_) => DataType::UInt8,
|
||||||
|
SQLDataType::SmallInt(_) => DataType::Int16,
|
||||||
|
SQLDataType::UnsignedSmallInt(_) => DataType::UInt16,
|
||||||
|
SQLDataType::Int(_) => DataType::Int32,
|
||||||
|
SQLDataType::UnsignedInt(_) => DataType::UInt32,
|
||||||
|
SQLDataType::BigInt(_) => DataType::Int64,
|
||||||
|
SQLDataType::UnsignedBigInt(_) => DataType::UInt64,
|
||||||
|
|
||||||
|
SQLDataType::Boolean => DataType::Boolean,
|
||||||
|
SQLDataType::Date => DataType::Date,
|
||||||
|
SQLDataType::Time => DataType::Time,
|
||||||
|
SQLDataType::Timestamp => DataType::Datetime(TimeUnit::Milliseconds, None),
|
||||||
|
SQLDataType::Interval => DataType::Duration(TimeUnit::Milliseconds),
|
||||||
|
SQLDataType::Array(inner_type) => {
|
||||||
|
DataType::List(Box::new(map_sql_polars_datatype(inner_type)?))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!(
|
||||||
|
"SQL Datatype {:?} was not supported in polars-sql yet!",
|
||||||
|
data_type
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_(expr: Expr, data_type: &SQLDataType) -> Result<Expr> {
|
||||||
|
let polars_type = map_sql_polars_datatype(data_type)?;
|
||||||
|
Ok(expr.cast(polars_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary_op_(left: Expr, right: Expr, op: &SQLBinaryOperator) -> Result<Expr> {
|
||||||
|
Ok(match op {
|
||||||
|
SQLBinaryOperator::Plus => left + right,
|
||||||
|
SQLBinaryOperator::Minus => left - right,
|
||||||
|
SQLBinaryOperator::Multiply => left * right,
|
||||||
|
SQLBinaryOperator::Divide => left / right,
|
||||||
|
SQLBinaryOperator::Modulo => left % right,
|
||||||
|
SQLBinaryOperator::StringConcat => left.cast(DataType::Utf8) + right.cast(DataType::Utf8),
|
||||||
|
SQLBinaryOperator::Gt => left.gt(right),
|
||||||
|
SQLBinaryOperator::Lt => left.lt(right),
|
||||||
|
SQLBinaryOperator::GtEq => left.gt_eq(right),
|
||||||
|
SQLBinaryOperator::LtEq => left.lt_eq(right),
|
||||||
|
SQLBinaryOperator::Eq => left.eq(right),
|
||||||
|
SQLBinaryOperator::NotEq => left.eq(right).not(),
|
||||||
|
SQLBinaryOperator::And => left.and(right),
|
||||||
|
SQLBinaryOperator::Or => left.or(right),
|
||||||
|
SQLBinaryOperator::Xor => left.xor(right),
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!("SQL Operator {:?} was not supported in polars-sql yet!", op).into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn literal_expr(value: &SqlValue) -> Result<Expr> {
|
||||||
|
Ok(match value {
|
||||||
|
SqlValue::Number(s, _) => {
|
||||||
|
// Check for existence of decimal separator dot
|
||||||
|
if s.contains('.') {
|
||||||
|
s.parse::<f64>().map(lit).map_err(|_| {
|
||||||
|
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
s.parse::<i64>().map(lit).map_err(|_| {
|
||||||
|
PolarsError::ComputeError(format!("Can't parse literal {:?}", s).into())
|
||||||
|
})
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
SqlValue::SingleQuotedString(s) => lit(s.clone()),
|
||||||
|
SqlValue::NationalStringLiteral(s) => lit(s.clone()),
|
||||||
|
SqlValue::HexStringLiteral(s) => lit(s.clone()),
|
||||||
|
SqlValue::DoubleQuotedString(s) => lit(s.clone()),
|
||||||
|
SqlValue::Boolean(b) => lit(*b),
|
||||||
|
SqlValue::Null => Expr::Literal(LiteralValue::Null),
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!(
|
||||||
|
"Parsing SQL Value {:?} was not supported in polars-sql yet!",
|
||||||
|
value
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
|
||||||
|
Ok(match expr {
|
||||||
|
SqlExpr::Identifier(e) => col(&e.value),
|
||||||
|
SqlExpr::BinaryOp { left, op, right } => {
|
||||||
|
let left = parse_sql_expr(left)?;
|
||||||
|
let right = parse_sql_expr(right)?;
|
||||||
|
binary_op_(left, right, op)?
|
||||||
|
}
|
||||||
|
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
|
||||||
|
SqlExpr::Cast { expr, data_type } => cast_(parse_sql_expr(expr)?, data_type)?,
|
||||||
|
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
|
||||||
|
SqlExpr::Value(value) => literal_expr(value)?,
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!(
|
||||||
|
"Expression: {:?} was not supported in polars-sql yet!",
|
||||||
|
expr
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_window_spec(expr: Expr, window_spec: &Option<WindowSpec>) -> Result<Expr> {
|
||||||
|
Ok(match &window_spec {
|
||||||
|
Some(window_spec) => {
|
||||||
|
// Process for simple window specification, partition by first
|
||||||
|
let partition_by = window_spec
|
||||||
|
.partition_by
|
||||||
|
.iter()
|
||||||
|
.map(parse_sql_expr)
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
expr.over(partition_by)
|
||||||
|
// Order by and Row range may not be supported at the moment
|
||||||
|
}
|
||||||
|
None => expr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
||||||
|
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
||||||
|
// Function name mostly do not have name space, so it mostly take the first args
|
||||||
|
let function_name = sql_function.name.0[0].value.to_lowercase();
|
||||||
|
let args = sql_function
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| match arg {
|
||||||
|
FunctionArg::Named { arg, .. } => arg,
|
||||||
|
FunctionArg::Unnamed(arg) => arg,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(
|
||||||
|
match (
|
||||||
|
function_name.as_str(),
|
||||||
|
args.as_slice(),
|
||||||
|
sql_function.distinct,
|
||||||
|
) {
|
||||||
|
("sum", [FunctionArgExpr::Expr(expr)], false) => {
|
||||||
|
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.sum()
|
||||||
|
}
|
||||||
|
("count", [FunctionArgExpr::Expr(expr)], false) => {
|
||||||
|
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.count()
|
||||||
|
}
|
||||||
|
("count", [FunctionArgExpr::Expr(expr)], true) => {
|
||||||
|
apply_window_spec(parse_sql_expr(expr)?, &sql_function.over)?.n_unique()
|
||||||
|
}
|
||||||
|
// Special case for wildcard args to count function.
|
||||||
|
("count", [FunctionArgExpr::Wildcard], false) => lit(1i32).count(),
|
||||||
|
_ => {
|
||||||
|
return Err(PolarsError::ComputeError(
|
||||||
|
format!(
|
||||||
|
"Function {:?} with args {:?} was not supported in polars-sql yet!",
|
||||||
|
function_name, args
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
94
crates/nu-command/src/dataframe/eager/to_arrow.rs
Normal file
94
crates/nu-command/src/dataframe/eager/to_arrow.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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::prelude::{IpcWriter, SerWriter};
|
||||||
|
|
||||||
|
use super::super::values::NuDataFrame;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToArrow;
|
||||||
|
|
||||||
|
impl Command for ToArrow {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"to arrow"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Saves dataframe to arrow file"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
|
||||||
|
.input_type(Type::Custom("dataframe".into()))
|
||||||
|
.output_type(Type::Any)
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Saves dataframe to arrow file",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | into df | to arrow test.arrow",
|
||||||
|
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 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 mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
|
let mut 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(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
IpcWriter::new(&mut file).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 {
|
||||||
|
val: format!("saved {:?}", &file_name.item),
|
||||||
|
span: file_name.span,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
Value::List {
|
||||||
|
vals: vec![file_value],
|
||||||
|
span: call.head,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
@ -15,7 +15,7 @@ impl Command for ToDataFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Converts a List, Table or Dictionary into a dataframe"
|
"Converts a list, table or record into a dataframe"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -243,7 +243,7 @@ expr_command!(
|
|||||||
"max",
|
"max",
|
||||||
"Creates a max expression",
|
"Creates a max expression",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -274,7 +274,7 @@ expr_command!(
|
|||||||
"min",
|
"min",
|
||||||
"Creates a min expression",
|
"Creates a min expression",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -305,7 +305,7 @@ expr_command!(
|
|||||||
"sum",
|
"sum",
|
||||||
"Creates a sum expression for an aggregation",
|
"Creates a sum expression for an aggregation",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -336,7 +336,7 @@ expr_command!(
|
|||||||
"mean",
|
"mean",
|
||||||
"Creates a mean expression for an aggregation",
|
"Creates a mean expression for an aggregation",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -367,7 +367,7 @@ expr_command!(
|
|||||||
"median",
|
"median",
|
||||||
"Creates a median expression for an aggregation",
|
"Creates a median expression for an aggregation",
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Median aggregation for a group by",
|
description: "Median aggregation for a group-by",
|
||||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -398,7 +398,7 @@ expr_command!(
|
|||||||
"std",
|
"std",
|
||||||
"Creates a std expression for an aggregation",
|
"Creates a std expression for an aggregation",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
@ -429,7 +429,7 @@ expr_command!(
|
|||||||
"var",
|
"var",
|
||||||
"Create a var expression for an aggregation",
|
"Create a var expression for an aggregation",
|
||||||
vec![Example {
|
vec![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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
|
@ -33,7 +33,7 @@ impl Command for ExprQuantile {
|
|||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Quantile aggregation for a group by",
|
description: "Quantile 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]]
|
||||||
| into df
|
| into df
|
||||||
| group-by a
|
| group-by a
|
||||||
|
@ -17,13 +17,13 @@ impl Command for LazyAggregate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Performs a series of aggregations from a group by"
|
"Performs a series of aggregations from a group-by"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.rest(
|
.rest(
|
||||||
"Group by expressions",
|
"Group-by expressions",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"Expression(s) that define the aggregations to be applied",
|
"Expression(s) that define the aggregations to be applied",
|
||||||
)
|
)
|
||||||
|
@ -16,15 +16,15 @@ impl Command for ToLazyGroupBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Creates a groupby object that can be used for other aggregations"
|
"Creates a group-by object that can be used for other aggregations"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.rest(
|
.rest(
|
||||||
"Group by expressions",
|
"Group-by expressions",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"Expression(s) that define the lazy group by",
|
"Expression(s) that define the lazy group-by",
|
||||||
)
|
)
|
||||||
.input_type(Type::Custom("dataframe".into()))
|
.input_type(Type::Custom("dataframe".into()))
|
||||||
.output_type(Type::Custom("dataframe".into()))
|
.output_type(Type::Custom("dataframe".into()))
|
||||||
|
@ -19,6 +19,10 @@ impl Command for ArgMax {
|
|||||||
"Return index for max value in series"
|
"Return index for max value in series"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["argmax", "maximum", "most", "largest", "greatest"]
|
||||||
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.input_type(Type::Custom("dataframe".into()))
|
.input_type(Type::Custom("dataframe".into()))
|
||||||
|
@ -19,6 +19,10 @@ impl Command for ArgMin {
|
|||||||
"Return index for min value in series"
|
"Return index for min value in series"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["argmin", "minimum", "least", "smallest", "lowest"]
|
||||||
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.input_type(Type::Custom("dataframe".into()))
|
.input_type(Type::Custom("dataframe".into()))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user