mirror of
https://github.com/nushell/nushell.git
synced 2025-08-15 14:43:25 +02:00
Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
36427a7434 | |||
daf52ba5c8 | |||
2f7f00001d | |||
ee7334a772 | |||
3fe9c7c00c | |||
43992f5b6f | |||
91e72ae8b4 | |||
da54ed8ea1 | |||
3eabc83c61 | |||
cc4a4a11f0 | |||
d53e16748d | |||
4ead4ce4d6 | |||
31606a8fe1 | |||
7133a04e2f | |||
79a6c78032 | |||
5478bdff0e | |||
a4711af952 | |||
751ef6e8da | |||
038f8f85ed | |||
4245c67ce3 | |||
e56879e588 | |||
c75e7bfbd3 | |||
e8579a9268 | |||
3dead9a001 | |||
0b106789a7 | |||
bf83756562 | |||
06fa1784c1 | |||
e6d673c39e | |||
c4fcd54573 | |||
4e56cd5fc4 | |||
0e3ca7b355 | |||
2b69bd9b6d | |||
3a82c6c88d | |||
61a89c1834 | |||
fcdc7f3d83 | |||
2b70d27cdf | |||
8c2af9941c | |||
f015409253 | |||
f33d952adf | |||
9f4c3a1d10 | |||
4f9c0775d9 | |||
d528bb713b | |||
7cc1a86459 | |||
dfbd98013d | |||
2c9f6acc03 | |||
007d15ed9f | |||
3e37922537 | |||
1274d1f7e3 | |||
da9615f971 | |||
eb8d2d3206 | |||
ee5b5bd39e | |||
d565c9ed01 | |||
18d5d8aae1 | |||
89c0e325fa | |||
7f2beb49db | |||
7203138880 | |||
459f3c0c28 | |||
2e4900f085 | |||
c921eadc6a | |||
00ac34d716 | |||
28a796d5cb | |||
f8698a6c24 | |||
48bca0a058 | |||
f3d92e3fa1 | |||
57dce8a386 | |||
aeb517867e | |||
71baeff287 |
42
.github/pull_request_template.md
vendored
42
.github/pull_request_template.md
vendored
@ -1,40 +1,16 @@
|
||||
<!--
|
||||
if this PR closes one or more issues, you can automatically link the PR with
|
||||
them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g.
|
||||
- this PR should close #xxxx
|
||||
- fixes #xxxx
|
||||
Thank you for improving Nushell!
|
||||
Please, read our contributing guide: https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md
|
||||
|
||||
you can also mention related issues, PRs or discussions!
|
||||
Use the following space to include the motivation and any technical details behind this PR.
|
||||
-->
|
||||
|
||||
# Description
|
||||
## Release notes summary - What our users need to know
|
||||
<!--
|
||||
Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.
|
||||
|
||||
Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.
|
||||
This section will be included as part of our release notes. See the contributing guide for more details.
|
||||
If you're not confident about this, a core team member would be glad to help!
|
||||
-->
|
||||
|
||||
# User-Facing Changes
|
||||
<!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. -->
|
||||
|
||||
# Tests + Formatting
|
||||
<!--
|
||||
Don't forget to add tests that cover your changes.
|
||||
|
||||
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 clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
|
||||
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
|
||||
|
||||
> **Note**
|
||||
> from `nushell` you can also use the `toolkit` as follows
|
||||
> ```bash
|
||||
> use toolkit.nu # or use an `env_change` hook to activate it automatically
|
||||
> toolkit check pr
|
||||
> ```
|
||||
-->
|
||||
|
||||
# After Submitting
|
||||
<!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
|
||||
## Tasks after submitting
|
||||
<!-- Remove any tasks which aren't relevant for your PR, or add your own -->
|
||||
- [ ] Update the [documentation](https://github.com/nushell/nushell.github.io)
|
||||
|
44
.github/workflows/pre-release-checkup.yml
vendored
Normal file
44
.github/workflows/pre-release-checkup.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Checks to perform pre-release (manual)
|
||||
on:
|
||||
- workflow_dispatch
|
||||
|
||||
|
||||
env:
|
||||
NUSHELL_CARGO_PROFILE: ci
|
||||
NU_LOG_LEVEL: DEBUG
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-22.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: Feature power set
|
||||
run: |
|
||||
cargo hack --all --feature-powerset --at-least-one-of rustls-tls,native-tls --mutually-exclusive-features rustls-tls,native-tls --mutually-exclusive-features rustls-tls,static-link-openssl --skip default-no-clipboard,stable,mimalloc check
|
||||
# Don't build fully for now as it will run out of disk space
|
||||
# - name: Build all crates
|
||||
# run: cargo hack --all build --clean-per-run
|
||||
|
||||
- name: Check for clean repo
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "there are changes";
|
||||
git status --porcelain
|
||||
exit 1
|
||||
else
|
||||
echo "no changes in working directory";
|
||||
fi
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.34.0
|
||||
uses: crate-ci/typos@v1.35.4
|
||||
|
@ -3,6 +3,7 @@
|
||||
Welcome to Nushell and thank you for considering contributing!
|
||||
|
||||
## Table of contents
|
||||
- [Tips for submitting PRs](#tips-for-submitting-prs)
|
||||
- [Proposing design changes](#proposing-design-changes)
|
||||
- [Developing](#developing)
|
||||
- [Setup](#setup)
|
||||
@ -20,6 +21,51 @@ More resources can be found in the nascent [developer documentation](devdocs/REA
|
||||
- [Platform support policy](devdocs/PLATFORM_SUPPORT.md)
|
||||
- [Our Rust style](devdocs/rust_style.md)
|
||||
|
||||
## Tips for submitting PRs
|
||||
|
||||
Thank you for improving Nushell! We are always glad to see contributions, and we are absolutely willing to talk through the design or implementation of your PR. Come talk with us in [Discord](https://discordapp.com/invite/NtAbbGn), or create a GitHub discussion or draft PR and we can help you work out the details from there.
|
||||
|
||||
**Please talk to the core team before making major changes!** See the [proposing design changes](#proposing-design-changes) for more details.
|
||||
|
||||
### Release notes section
|
||||
|
||||
In our PR template, we have a "Release notes summary" section which will be included in our release notes for our blog.
|
||||
|
||||
This section should include all information about your change which is relevant to a user of Nushell. You should try to keep it **brief and simple to understand**, and focus on the ways your change directly impacts the user experience. We highly encourage adding examples and, when relevant, screenshots in this section.
|
||||
|
||||
Please make sure to consider both the *intended changes*, such as additions or deliberate breaking changes **and** possible *side effects* that might change how users interact with a command or feature. It's important to think carefully about the ways that your PR might affect any aspect of the user experience, and to document these changes even if they seem minor or aren't directly related to the main purpose of the PR.
|
||||
|
||||
This section might not be relevant for all PRs. If your PR is a work in progress, feel free to write "WIP"/"TODO"/etc in this section. You can also write "N/A" if this is a technical change which doesn't impact the user experience.
|
||||
|
||||
If you're not sure what to put here, or need some help, **a core team member would be glad to help you out**. We may also makes some tweaks to your release notes section. Please don't take it personally, we just want to make sure our release notes are polished and easy to understand. Once the release notes section is ready, we'll add the (TODO label name) label to indicate that the release notes section is ready to be included in the actual release notes.
|
||||
|
||||
### Tests and formatting checks
|
||||
|
||||
Our CI system automatically checks formatting and runs our tests. If you're running into an issue, or just want to make sure everything is ready to go before creating your PR, you can run the checks yourself:
|
||||
|
||||
```nushell
|
||||
use toolkit.nu # or use an `env_change` hook to activate it automatically
|
||||
toolkit check pr
|
||||
```
|
||||
|
||||
Furthermore, you can also runs these checks individually with the subcommands of `toolkit`, or run the underlying commands yourself:
|
||||
|
||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass (on Windows make sure to enable [developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
|
||||
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
|
||||
|
||||
If the checks are passing on your local system, but CI just won't pass, feel free to ask for help from the core team.
|
||||
|
||||
### Linking and mentioning issues
|
||||
|
||||
If your PR closes one or more issues, you can automatically link the PR with them by using one of the [linking keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword):
|
||||
|
||||
- This PR should close #xxxx
|
||||
- Fixes #xxxx
|
||||
|
||||
You can also mention related issues, PRs or discussions!
|
||||
|
||||
## Proposing design changes
|
||||
|
||||
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||
|
336
Cargo.lock
generated
336
Cargo.lock
generated
@ -483,7 +483,7 @@ dependencies = [
|
||||
"hex",
|
||||
"hmac",
|
||||
"http 0.2.12",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"sha2",
|
||||
@ -578,7 +578,7 @@ dependencies = [
|
||||
"aws-smithy-types",
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@ -595,7 +595,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"bytes-utils",
|
||||
"http 0.2.12",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 0.4.6",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
@ -922,6 +922,12 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
@ -1111,6 +1117,16 @@ dependencies = [
|
||||
"supports-color",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "7.1.3"
|
||||
@ -1879,9 +1895,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
checksum = "bf04c5ec15464ace8355a7b440a33aece288993475556d461154d7a62ad9947c"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@ -2279,7 +2295,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
@ -2421,9 +2437,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@ -2448,7 +2464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2459,7 +2475,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
@ -2529,7 +2545,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
@ -2563,10 +2579,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"hyper 1.5.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@ -2600,7 +2616,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.5.1",
|
||||
"pin-project-lite",
|
||||
@ -2954,6 +2970,28 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
@ -3185,9 +3223,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@ -3271,9 +3309,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
|
||||
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -3437,7 +3475,7 @@ dependencies = [
|
||||
"assert-json-diff",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.1",
|
||||
@ -3583,7 +3621,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm",
|
||||
@ -3638,7 +3676,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
@ -3675,7 +3713,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
@ -3687,7 +3725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck",
|
||||
@ -3713,7 +3751,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"miette",
|
||||
@ -3730,7 +3768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-plugin"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"nu-engine",
|
||||
@ -3741,7 +3779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
@ -3753,7 +3791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
@ -3778,7 +3816,9 @@ dependencies = [
|
||||
"fancy-regex",
|
||||
"filesize",
|
||||
"filetime",
|
||||
"fuzzy-matcher",
|
||||
"getrandom 0.2.15",
|
||||
"http 1.3.1",
|
||||
"human-date-parser",
|
||||
"indexmap",
|
||||
"indicatif",
|
||||
@ -3831,7 +3871,7 @@ dependencies = [
|
||||
"rstest",
|
||||
"rstest_reuse",
|
||||
"rusqlite",
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
@ -3849,7 +3889,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.2.0",
|
||||
"update-informer",
|
||||
"ureq 2.12.1",
|
||||
"ureq",
|
||||
"url",
|
||||
"uu_cp",
|
||||
"uu_mkdir",
|
||||
@ -3871,7 +3911,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-derive-value"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error2",
|
||||
@ -3882,7 +3922,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"log",
|
||||
@ -3894,7 +3934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-experimental"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"thiserror 2.0.12",
|
||||
@ -3902,7 +3942,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"anyhow",
|
||||
@ -3926,14 +3966,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"linked-hash-map",
|
||||
@ -3949,7 +3989,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
@ -3976,7 +4016,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
@ -3993,7 +4033,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"omnipath",
|
||||
@ -4003,7 +4043,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix 0.29.0",
|
||||
@ -4019,7 +4059,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-core"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"log",
|
||||
@ -4033,7 +4073,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-engine",
|
||||
@ -4049,7 +4089,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-protocol",
|
||||
"nu-utils",
|
||||
@ -4061,7 +4101,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-lang",
|
||||
@ -4079,7 +4119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
@ -4088,7 +4128,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"bytes",
|
||||
@ -4130,7 +4170,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
@ -4141,7 +4181,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@ -4159,7 +4199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
@ -4172,7 +4212,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width 0.2.0",
|
||||
@ -4180,7 +4220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
@ -4192,7 +4232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"crossterm_winapi",
|
||||
@ -4221,7 +4261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
@ -4231,7 +4271,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"eml-parser",
|
||||
@ -4246,7 +4286,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
@ -4255,7 +4295,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
@ -4264,7 +4304,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_polars"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
@ -4303,7 +4343,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
@ -4318,7 +4358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"serde",
|
||||
@ -4442,7 +4482,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nuon"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nu-engine",
|
||||
@ -4590,7 +4630,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"humantime",
|
||||
"hyper 1.5.1",
|
||||
@ -5819,7 +5859,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.1.0",
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"socket2",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
@ -5837,7 +5877,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"rustc-hash 2.1.0",
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.12",
|
||||
@ -5977,9 +6017,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@ -5987,9 +6027,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
@ -6044,8 +6084,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b627c435d0189363b15f885f1b07193d310ec9e4e39c5627951c6e0f4d02c93a"
|
||||
source = "git+https://github.com/nushell/reedline?branch=main#faee143a688846d98e260407b4e09f653eb31307"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
@ -6137,7 +6176,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.1",
|
||||
@ -6153,7 +6192,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
@ -6412,15 +6451,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.20"
|
||||
version = "0.23.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
|
||||
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"rustls-webpki 0.103.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@ -6469,13 +6508,41 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.10.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
|
||||
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.28",
|
||||
"rustls-native-certs 0.8.1",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.3",
|
||||
"security-framework 3.0.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs 0.26.11",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
@ -6488,9 +6555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.8"
|
||||
version = "0.103.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@ -7173,9 +7240,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.36.0"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aab138f5c1bb35231de19049060a87977ad23e04f2303e953bc5c2947ac7dec4"
|
||||
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
@ -7468,7 +7535,7 @@ version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
|
||||
dependencies = [
|
||||
"rustls 0.23.20",
|
||||
"rustls 0.23.28",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@ -7777,49 +7844,30 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq 3.0.3",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
version = "3.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"cookie_store",
|
||||
"der",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"rustls 0.23.20",
|
||||
"percent-encoding",
|
||||
"rustls 0.23.28",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socks",
|
||||
"url",
|
||||
"webpki-roots 0.26.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217751151c53226090391713e533d9a5e904ba2570dabaaace29032687589c3e"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"cc",
|
||||
"cookie_store",
|
||||
"der",
|
||||
"flate2",
|
||||
"log",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"rustls 0.23.20",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq-proto",
|
||||
"utf-8",
|
||||
"webpki-root-certs 0.26.11",
|
||||
@ -7828,12 +7876,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.3.5"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae239d0a3341aebc94259414d1dc67cfce87d41cbebc816772c91b77902fafa4"
|
||||
checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"http 1.2.0",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
@ -8605,6 +8653,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@ -8632,6 +8689,21 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
@ -8672,6 +8744,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@ -8684,6 +8762,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
@ -8696,6 +8780,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
@ -8714,6 +8804,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
@ -8726,6 +8822,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
@ -8738,6 +8840,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@ -8750,6 +8858,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
60
Cargo.toml
60
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.86.0"
|
||||
version = "0.106.0"
|
||||
rust-version = "1.87.0"
|
||||
version = "0.106.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -83,15 +83,17 @@ csv = "1.3"
|
||||
ctrlc = "3.4"
|
||||
devicons = "0.6.12"
|
||||
dialoguer = { default-features = false, version = "0.11" }
|
||||
fuzzy-matcher = { version = "^0.3.7" }
|
||||
digest = { default-features = false, version = "0.10" }
|
||||
dirs = "5.0"
|
||||
dirs-sys = "0.4"
|
||||
dtparse = "2.0"
|
||||
encoding_rs = "0.8"
|
||||
fancy-regex = "0.14"
|
||||
fancy-regex = "0.16"
|
||||
filesize = "0.2"
|
||||
filetime = "0.2"
|
||||
heck = "0.5.0"
|
||||
http = "1.3.1"
|
||||
human-date-parser = "0.3.0"
|
||||
indexmap = "2.10"
|
||||
indicatif = "0.17"
|
||||
@ -140,7 +142,7 @@ rand = "0.9"
|
||||
getrandom = "0.2" # pick same version that rand requires
|
||||
rand_chacha = "0.9"
|
||||
ratatui = "0.29"
|
||||
rayon = "1.10"
|
||||
rayon = "1.11"
|
||||
reedline = "0.41.0"
|
||||
rmp = "0.8"
|
||||
rmp-serde = "1.3"
|
||||
@ -149,7 +151,10 @@ rstest = { version = "0.23", default-features = false }
|
||||
rstest_reuse = "0.7"
|
||||
rusqlite = "0.31"
|
||||
rust-embed = "8.7.0"
|
||||
rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] }
|
||||
# We have to fix rustls and ureq versions
|
||||
# because we use unversioned api to allow users set up their own
|
||||
# crypto providers (grep for "unversioned")
|
||||
rustls = { version = "=0.23.28", default-features = false, features = ["std", "tls12"] }
|
||||
rustls-native-certs = "0.8"
|
||||
scopeguard = { version = "1.2.0" }
|
||||
serde = { version = "1.0" }
|
||||
@ -172,7 +177,7 @@ update-informer = { version = "1.3.0", default-features = false, features = ["gi
|
||||
umask = "2.1"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-width = "0.2"
|
||||
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
|
||||
ureq = { version = "=3.0.12", default-features = false, features = ["socks-proxy"] }
|
||||
url = "2.2"
|
||||
uu_cp = "0.0.30"
|
||||
uu_mkdir = "0.0.30"
|
||||
@ -198,28 +203,29 @@ webpki-roots = "1.0"
|
||||
# todo = "warn"
|
||||
unchecked_duration_subtraction = "warn"
|
||||
used_underscore_binding = "warn"
|
||||
result_large_err = "allow"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.106.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.106.0" }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.106.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.106.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.106.0", optional = true }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.106.0", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.106.0" }
|
||||
nu-experimental = { path = "./crates/nu-experimental", version = "0.106.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.106.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.106.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.106.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.106.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.106.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.106.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.106.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.106.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.106.0" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.106.2" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.106.2" }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.106.2" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.106.2" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.106.2", optional = true }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.106.2", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.106.2" }
|
||||
nu-experimental = { path = "./crates/nu-experimental", version = "0.106.2" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.106.2" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.106.2" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.106.2" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.106.2" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.106.2" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.106.2" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.106.2" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.106.2" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.106.2" }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
@ -248,9 +254,9 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.106.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.106.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.106.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.106.2" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.106.2" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.106.2" }
|
||||
assert_cmd = "2.0"
|
||||
dirs = { workspace = true }
|
||||
tango-bench = "0.6"
|
||||
@ -337,7 +343,7 @@ bench = false
|
||||
# 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", branch = "main" }
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Run all benchmarks with `cargo bench`
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Security Policy
|
||||
|
||||
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
|
||||
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
|
||||
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
|
||||
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
|
||||
|
||||
@ -11,7 +11,7 @@ Only if you provide a strong reasoning and the necessary resources, will we cons
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
|
||||
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
|
||||
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
|
||||
Please try to answer the following questions:
|
||||
- How can we reach you for further questions?
|
||||
|
@ -5,29 +5,29 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.106.0" }
|
||||
nu-std = { path = "../nu-std", version = "0.106.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.106.2" }
|
||||
nu-std = { path = "../nu-std", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", features = ["os"] }
|
||||
nu-glob = { path = "../nu-glob", version = "0.106.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", features = ["os"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.106.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", features = ["os"] }
|
||||
nu-glob = { path = "../nu-glob", version = "0.106.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.2" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.2", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", features = ["os"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.106.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -348,8 +348,43 @@ impl NuCompleter {
|
||||
for (arg_idx, arg) in call.arguments.iter().enumerate() {
|
||||
let span = arg.span();
|
||||
if span.contains(pos) {
|
||||
// if customized completion specified, it has highest priority
|
||||
if let Some(decl_id) = arg.expr().and_then(|e| e.custom_completion) {
|
||||
// Get custom completion from PositionalArg or Flag
|
||||
let custom_completion_decl_id = {
|
||||
// Check PositionalArg or Flag from Signature
|
||||
let signature = working_set.get_decl(call.decl_id).signature();
|
||||
|
||||
match arg {
|
||||
// For named arguments, check Flag
|
||||
Argument::Named((name, short, value)) => {
|
||||
if value.as_ref().is_none_or(|e| !e.span.contains(pos)) {
|
||||
None
|
||||
} else {
|
||||
// If we're completing the value of the flag,
|
||||
// search for the matching custom completion decl_id (long or short)
|
||||
let flag =
|
||||
signature.get_long_flag(&name.item).or_else(|| {
|
||||
short.as_ref().and_then(|s| {
|
||||
signature.get_short_flag(
|
||||
s.item.chars().next().unwrap_or('_'),
|
||||
)
|
||||
})
|
||||
});
|
||||
flag.and_then(|f| f.custom_completion)
|
||||
}
|
||||
}
|
||||
// For positional arguments, check PositionalArg
|
||||
Argument::Positional(_) => {
|
||||
// Find the right positional argument by index
|
||||
let arg_pos = positional_arg_indices.len();
|
||||
signature
|
||||
.get_positional(arg_pos)
|
||||
.and_then(|pos_arg| pos_arg.custom_completion)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(decl_id) = custom_completion_decl_id {
|
||||
// for `--foo <tab>` and `--foo=<tab>`, the arg span should be trimmed
|
||||
let (new_span, prefix) = if matches!(arg, Argument::Named(_)) {
|
||||
strip_placeholder_with_rsplit(
|
||||
@ -718,7 +753,7 @@ impl NuCompleter {
|
||||
Ok(value) => {
|
||||
log::error!(
|
||||
"External completer returned invalid value of type {}",
|
||||
value.get_type().to_string()
|
||||
value.get_type()
|
||||
);
|
||||
Some(vec![])
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
||||
_ => {
|
||||
log::error!(
|
||||
"Custom completer returned invalid value of type {}",
|
||||
value.get_type().to_string()
|
||||
value.get_type()
|
||||
);
|
||||
return vec![];
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
||||
&mut stack,
|
||||
&old_contents,
|
||||
&old_plugin_file_path.to_string_lossy(),
|
||||
PipelineData::Empty,
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
) != 0
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ fn get_prompt_string(
|
||||
.and_then(|v| match v {
|
||||
Value::Closure { val, .. } => {
|
||||
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
|
||||
.run_with_input(PipelineData::Empty);
|
||||
.run_with_input(PipelineData::empty());
|
||||
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
@ -76,7 +76,7 @@ fn get_prompt_string(
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||
Value::String { .. } => Some(PipelineData::value(v.clone(), None)),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|pipeline_data| {
|
||||
|
@ -159,7 +159,7 @@ pub(crate) fn add_menus(
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let mut temp_stack = Stack::new().collect_value();
|
||||
let input = PipelineData::Empty;
|
||||
let input = PipelineData::empty();
|
||||
menu_eval_results.push(eval_block::<WithoutDebug>(
|
||||
&engine_state,
|
||||
&mut temp_stack,
|
||||
@ -1047,6 +1047,10 @@ fn event_from_record(
|
||||
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||
}
|
||||
"openeditor" => ReedlineEvent::OpenEditor,
|
||||
"vichangemode" => {
|
||||
let mode = extract_value("mode", record, span)?;
|
||||
ReedlineEvent::ViChangeMode(mode.as_str()?.to_owned())
|
||||
}
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a reedline event".into(),
|
||||
|
@ -325,7 +325,19 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
perf!("reset signals", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user, fire the "pre_prompt" hook
|
||||
// Check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
if let Err(error) = hook::eval_env_change_hook(
|
||||
&engine_state.get_config().hooks.env_change.clone(),
|
||||
engine_state,
|
||||
&mut stack,
|
||||
) {
|
||||
report_shell_error(engine_state, &error)
|
||||
}
|
||||
perf!("env-change hook", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, right before we start our prompt and take input from the user, fire the "pre_prompt" hook
|
||||
if let Err(err) = hook::eval_hooks(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
@ -337,18 +349,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
}
|
||||
perf!("pre-prompt hook", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
if let Err(error) = hook::eval_env_change_hook(
|
||||
&engine_state.get_config().hooks.env_change.clone(),
|
||||
engine_state,
|
||||
&mut stack,
|
||||
) {
|
||||
report_shell_error(engine_state, &error)
|
||||
}
|
||||
perf!("env-change hook", start_time, use_color);
|
||||
|
||||
let engine_reference = Arc::new(engine_state.clone());
|
||||
let config = stack.get_config(engine_state);
|
||||
|
||||
|
@ -97,8 +97,11 @@ fn extern_completer() -> NuCompleter {
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
def animals [] { [ "cat", "dog", "eel" ] }
|
||||
def fruits [] { [ "apple", "banana" ] }
|
||||
extern spam [
|
||||
animal: string@animals
|
||||
fruit?: string@fruits
|
||||
...rest: string@animals
|
||||
--foo (-f): string@animals
|
||||
-b: string@animals
|
||||
]
|
||||
@ -1560,9 +1563,7 @@ fn flag_completions() {
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(18, suggestions.len());
|
||||
|
||||
let expected: Vec<_> = vec![
|
||||
"--all",
|
||||
"--directory",
|
||||
@ -1583,9 +1584,12 @@ fn flag_completions() {
|
||||
"-s",
|
||||
"-t",
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// https://github.com/nushell/nushell/issues/16375
|
||||
let suggestions = completer.complete("table -", 7);
|
||||
assert_eq!(20, suggestions.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2137,7 +2141,8 @@ fn run_external_completion_within_pwd(
|
||||
assert!(engine_state.merge_delta(delta).is_ok());
|
||||
|
||||
assert!(
|
||||
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok()
|
||||
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::empty())
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
@ -2261,6 +2266,22 @@ fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_optional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam foo -f bar ", 16);
|
||||
let expected: Vec<_> = vec!["apple", "banana"];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_rest(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam foo -f bar baz ", 20);
|
||||
let expected: Vec<_> = vec!["cat", "dog", "eel"];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
let suggestions = extern_completer.complete("spam foo -f bar baz qux ", 24);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
@ -2289,6 +2310,17 @@ fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
/// When we're completing the flag name itself, not its value,
|
||||
/// custom completions should not be used
|
||||
#[rstest]
|
||||
fn custom_completion_flag_name_not_value(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --f", 8);
|
||||
match_suggestions(&vec!["--foo"], &suggestions);
|
||||
// Also test with partial short flag
|
||||
let suggestions = extern_completer.complete("spam -f", 7);
|
||||
match_suggestions(&vec!["-f"], &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
|
@ -199,7 +199,7 @@ pub fn merge_input(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::Value(Value::nothing(Span::unknown()), None),
|
||||
PipelineData::value(Value::nothing(Span::unknown()), None),
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.106.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -12,7 +12,7 @@ use nu_protocol::{
|
||||
/// ```rust
|
||||
/// # use nu_engine::command_prelude::*;
|
||||
/// # use nu_cmd_base::WrapCall;
|
||||
/// # fn do_command_logic(call: WrapCall) -> Result<PipelineData, ShellError> { Ok(PipelineData::Empty) }
|
||||
/// # fn do_command_logic(call: WrapCall) -> Result<PipelineData, ShellError> { Ok(PipelineData::empty()) }
|
||||
///
|
||||
/// # struct Command {}
|
||||
/// # impl Command {
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,13 +16,13 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", default-features = false }
|
||||
nu-json = { version = "0.106.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.0" }
|
||||
nu-pretty-hex = { version = "0.106.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.0", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
|
||||
nu-json = { version = "0.106.2", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.2" }
|
||||
nu-pretty-hex = { version = "0.106.2", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
@ -37,6 +37,6 @@ itertools = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.106.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
|
@ -3,7 +3,12 @@ use nu_protocol::engine::Command;
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_examples(cmd: impl Command + 'static) {
|
||||
test_examples::test_examples(cmd);
|
||||
test_examples::test_examples(cmd, &[]);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_examples_with_commands(cmd: impl Command + 'static, commands: &[&dyn Command]) {
|
||||
test_examples::test_examples(cmd, commands);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -21,10 +26,10 @@ mod test_examples {
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn test_examples(cmd: impl Command + 'static) {
|
||||
pub fn test_examples(cmd: impl Command + 'static, commands: &[&dyn Command]) {
|
||||
let examples = cmd.examples();
|
||||
let signature = cmd.signature();
|
||||
let mut engine_state = make_engine_state(cmd.clone_box());
|
||||
let mut engine_state = make_engine_state(cmd.clone_box(), commands);
|
||||
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
|
||||
@ -38,7 +43,7 @@ mod test_examples {
|
||||
check_example_input_and_output_types_match_command_signature(
|
||||
&example,
|
||||
&cwd,
|
||||
&mut make_engine_state(cmd.clone_box()),
|
||||
&mut make_engine_state(cmd.clone_box(), commands),
|
||||
&signature.input_output_types,
|
||||
signature.operates_on_cell_paths(),
|
||||
),
|
||||
@ -57,7 +62,7 @@ mod test_examples {
|
||||
);
|
||||
}
|
||||
|
||||
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
||||
fn make_engine_state(cmd: Box<dyn Command>, commands: &[&dyn Command]) -> Box<EngineState> {
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
|
||||
let delta = {
|
||||
@ -69,6 +74,10 @@ mod test_examples {
|
||||
working_set.add_decl(Box::new(nu_cmd_lang::If));
|
||||
working_set.add_decl(Box::new(nu_command::MathRound));
|
||||
|
||||
for command in commands {
|
||||
working_set.add_decl(command.clone_box());
|
||||
}
|
||||
|
||||
// Adding the command that is being tested to the working set
|
||||
working_set.add_decl(cmd);
|
||||
working_set.render()
|
||||
|
@ -72,7 +72,7 @@ impl Command for EachWhile {
|
||||
|
||||
let metadata = input.metadata();
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream(..) => {
|
||||
@ -109,7 +109,7 @@ impl Command for EachWhile {
|
||||
.fuse()
|
||||
.into_pipeline_data(head, engine_state.signals().clone()))
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
// This match allows non-iterables to be accepted,
|
||||
|
@ -55,7 +55,7 @@ fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
|
||||
.map(|(k, v)| (k, Value::string(v, head)))
|
||||
.collect();
|
||||
|
||||
Ok(PipelineData::Value(Value::record(record, head), metadata))
|
||||
Ok(PipelineData::value(Value::record(record, head), metadata))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedInput {
|
||||
msg: "String not compatible with URL encoding".to_string(),
|
||||
|
@ -109,18 +109,26 @@ impl Command for ToHtml {
|
||||
"produce a color table of all available themes",
|
||||
Some('l'),
|
||||
)
|
||||
.switch("raw", "do not escape html tags", Some('r'))
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Outputs an HTML string representing the contents of this table",
|
||||
description: "Outputs an HTML string representing the contents of this table",
|
||||
example: "[[foo bar]; [1 2]] | to html",
|
||||
result: Some(Value::test_string(
|
||||
r#"<html><style>body { background-color:white;color:black; }</style><body><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table></body></html>"#,
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Outputs an HTML string using a record of xml data",
|
||||
example: r#"{tag: a attributes: { style: "color: red" } content: ["hello!"] } | to xml | to html --raw"#,
|
||||
result: Some(Value::test_string(
|
||||
r#"<html><style>body { background-color:white;color:black; }</style><body><a style="color: red">hello!</a></body></html>"#,
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Optionally, only output the html for the content itself",
|
||||
example: "[[foo bar]; [1 2]] | to html --partial",
|
||||
@ -254,6 +262,7 @@ fn to_html(
|
||||
let dark = call.has_flag(engine_state, stack, "dark")?;
|
||||
let partial = call.has_flag(engine_state, stack, "partial")?;
|
||||
let list = call.has_flag(engine_state, stack, "list")?;
|
||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let theme: Option<Spanned<String>> = call.get_flag(engine_state, stack, "theme")?;
|
||||
let config = &stack.get_config(engine_state);
|
||||
|
||||
@ -319,15 +328,15 @@ fn to_html(
|
||||
let inner_value = match vec_of_values.len() {
|
||||
0 => String::default(),
|
||||
1 => match headers {
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
Some(headers) => html_table(vec_of_values, headers, raw, config),
|
||||
None => {
|
||||
let value = &vec_of_values[0];
|
||||
html_value(value.clone(), config)
|
||||
html_value(value.clone(), raw, config)
|
||||
}
|
||||
},
|
||||
_ => match headers {
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
None => html_list(vec_of_values, config),
|
||||
Some(headers) => html_table(vec_of_values, headers, raw, config),
|
||||
None => html_list(vec_of_values, raw, config),
|
||||
},
|
||||
};
|
||||
|
||||
@ -395,19 +404,19 @@ fn theme_demo(span: Span) -> PipelineData {
|
||||
})
|
||||
}
|
||||
|
||||
fn html_list(list: Vec<Value>, config: &Config) -> String {
|
||||
fn html_list(list: Vec<Value>, raw: bool, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
output_string.push_str("<ol>");
|
||||
for value in list {
|
||||
output_string.push_str("<li>");
|
||||
output_string.push_str(&html_value(value, config));
|
||||
output_string.push_str(&html_value(value, raw, config));
|
||||
output_string.push_str("</li>");
|
||||
}
|
||||
output_string.push_str("</ol>");
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> String {
|
||||
fn html_table(table: Vec<Value>, headers: Vec<String>, raw: bool, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
|
||||
output_string.push_str("<table>");
|
||||
@ -430,7 +439,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> Strin
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::nothing(span));
|
||||
output_string.push_str("<td>");
|
||||
output_string.push_str(&html_value(data, config));
|
||||
output_string.push_str(&html_value(data, raw, config));
|
||||
output_string.push_str("</td>");
|
||||
}
|
||||
output_string.push_str("</tr>");
|
||||
@ -441,7 +450,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> Strin
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_value(value: Value, config: &Config) -> String {
|
||||
fn html_value(value: Value, raw: bool, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
match value {
|
||||
Value::Binary { val, .. } => {
|
||||
@ -450,11 +459,22 @@ fn html_value(value: Value, config: &Config) -> String {
|
||||
output_string.push_str(&output);
|
||||
output_string.push_str("</pre>");
|
||||
}
|
||||
other => output_string.push_str(
|
||||
&v_htmlescape::escape(&other.to_abbreviated_string(config))
|
||||
.to_string()
|
||||
.replace('\n', "<br>"),
|
||||
),
|
||||
other => {
|
||||
if raw {
|
||||
output_string.push_str(
|
||||
&other
|
||||
.to_abbreviated_string(config)
|
||||
.to_string()
|
||||
.replace('\n', "<br>"),
|
||||
)
|
||||
} else {
|
||||
output_string.push_str(
|
||||
&v_htmlescape::escape(&other.to_abbreviated_string(config))
|
||||
.to_string()
|
||||
.replace('\n', "<br>"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
output_string
|
||||
}
|
||||
@ -717,9 +737,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
use crate::test_examples_with_commands;
|
||||
use nu_command::ToXml;
|
||||
|
||||
test_examples(ToHtml {})
|
||||
test_examples_with_commands(ToHtml {}, &[&ToXml])
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -113,7 +113,7 @@ fn format_bits(
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
|
||||
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
byte_stream_to_bits(stream, head),
|
||||
metadata,
|
||||
))
|
||||
|
@ -191,7 +191,7 @@ fn format(
|
||||
// We can only handle a Record or a List of Records
|
||||
match data_as_value {
|
||||
Value::Record { .. } => match format_record(format_operations, &data_as_value, config) {
|
||||
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
|
||||
Ok(value) => Ok(PipelineData::value(Value::string(value, head_span), None)),
|
||||
Err(value) => Err(value),
|
||||
},
|
||||
|
||||
|
@ -4,4 +4,4 @@ pub mod extra;
|
||||
pub use extra::*;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use example_test::test_examples;
|
||||
pub use example_test::{test_examples, test_examples_with_commands};
|
||||
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -15,12 +15,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", default-features = false }
|
||||
nu-experimental = { path = "../nu-experimental", version = "0.106.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.0", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
|
||||
nu-experimental = { path = "../nu-experimental", version = "0.106.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "1.2", default-features = false }
|
||||
|
@ -157,12 +157,12 @@ impl Command for Do {
|
||||
if !stderr_msg.is_empty() {
|
||||
child.stderr = Some(ChildPipe::Tee(Box::new(Cursor::new(stderr_msg))));
|
||||
}
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::child(child, span),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
Err(stream) => Ok(PipelineData::ByteStream(stream, metadata)),
|
||||
Err(stream) => Ok(PipelineData::byte_stream(stream, metadata)),
|
||||
}
|
||||
}
|
||||
Ok(PipelineData::ByteStream(mut stream, metadata))
|
||||
@ -176,7 +176,7 @@ impl Command for Do {
|
||||
if let ByteStreamSource::Child(child) = stream.source_mut() {
|
||||
child.ignore_error(true);
|
||||
}
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
Ok(PipelineData::byte_stream(stream, metadata))
|
||||
}
|
||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_all_errors => {
|
||||
Ok(PipelineData::empty())
|
||||
@ -189,7 +189,7 @@ impl Command for Do {
|
||||
value
|
||||
}
|
||||
});
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
r => r,
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-plugin"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.106.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
|
@ -66,7 +66,7 @@ impl Command for PluginStop {
|
||||
}
|
||||
|
||||
if found {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: format!("Failed to stop the `{}` plugin", name.item),
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,12 +14,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.106.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.106.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.106.0"
|
||||
version = "0.106.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -17,21 +17,21 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-ansi-term = { workspace = true }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.106.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.0", default-features = false }
|
||||
nu-experimental = { path = "../nu-experimental", version = "0.106.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.106.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.106.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.106.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.0", default-features = false }
|
||||
nu-system = { path = "../nu-system", version = "0.106.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.106.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.106.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.0", default-features = false }
|
||||
nuon = { path = "../nuon", version = "0.106.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.106.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
|
||||
nu-experimental = { path = "../nu-experimental", version = "0.106.2" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.106.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.106.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.106.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.106.2" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.106.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
|
||||
nu-system = { path = "../nu-system", version = "0.106.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.106.2" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.106.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
|
||||
nuon = { path = "../nuon", version = "0.106.2" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
@ -54,12 +54,14 @@ devicons = { workspace = true }
|
||||
dialoguer = { workspace = true, default-features = false, features = [
|
||||
"fuzzy-select",
|
||||
] }
|
||||
fuzzy-matcher = { workspace = true }
|
||||
digest = { workspace = true, default-features = false }
|
||||
dtparse = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
fancy-regex = { workspace = true }
|
||||
filesize = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
http = {workspace = true}
|
||||
human-date-parser = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
@ -92,6 +94,7 @@ rusqlite = { workspace = true, features = [
|
||||
"bundled",
|
||||
"backup",
|
||||
"chrono",
|
||||
"column_decltype",
|
||||
], optional = true }
|
||||
rustls = { workspace = true, optional = true, features = ["ring"] }
|
||||
rustls-native-certs = { workspace = true, optional = true }
|
||||
@ -191,6 +194,7 @@ os = [
|
||||
"uu_uname",
|
||||
"uu_whoami",
|
||||
"which",
|
||||
"ureq/platform-verifier"
|
||||
]
|
||||
|
||||
# The dependencies listed below need 'getrandom'.
|
||||
@ -219,7 +223,7 @@ rustls-tls = [
|
||||
"dep:rustls-native-certs",
|
||||
"dep:webpki-roots",
|
||||
"update-informer/rustls-tls",
|
||||
"ureq/tls", # ureq 3 will has the feature rustls instead
|
||||
"ureq/rustls",
|
||||
]
|
||||
|
||||
plugin = ["nu-parser/plugin", "os"]
|
||||
@ -227,8 +231,8 @@ sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
|
||||
dirs = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -76,7 +76,7 @@ impl Command for BytesAt {
|
||||
|
||||
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||
let stream = stream.slice(call.head, call.arguments_span(), range)?;
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
Ok(PipelineData::byte_stream(stream, metadata))
|
||||
} else {
|
||||
operate(
|
||||
map_value,
|
||||
|
@ -67,7 +67,7 @@ impl Command for BytesCollect {
|
||||
ByteStreamType::Binary,
|
||||
);
|
||||
|
||||
Ok(PipelineData::ByteStream(output, metadata))
|
||||
Ok(PipelineData::byte_stream(output, metadata))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -129,7 +129,7 @@ fn into_binary(
|
||||
|
||||
if let PipelineData::ByteStream(stream, metadata) = input {
|
||||
// Just set the type - that should be good enough
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
stream.with_type(ByteStreamType::Binary),
|
||||
metadata,
|
||||
))
|
||||
|
@ -82,9 +82,6 @@ impl Command for IntoDatetime {
|
||||
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
|
||||
(Type::table(), Type::table()),
|
||||
(Type::Nothing, Type::table()),
|
||||
// FIXME: https://github.com/nushell/nushell/issues/15485
|
||||
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
|
||||
(Type::record(), Type::Any),
|
||||
(Type::record(), Type::record()),
|
||||
(Type::record(), Type::Date),
|
||||
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
|
||||
|
@ -53,9 +53,6 @@ impl Command for IntoDuration {
|
||||
(Type::Float, Type::Duration),
|
||||
(Type::String, Type::Duration),
|
||||
(Type::Duration, Type::Duration),
|
||||
// FIXME: https://github.com/nushell/nushell/issues/15485
|
||||
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
|
||||
(Type::record(), Type::Any),
|
||||
(Type::record(), Type::record()),
|
||||
(Type::record(), Type::Duration),
|
||||
(Type::table(), Type::table()),
|
||||
|
@ -170,7 +170,7 @@ fn string_helper(
|
||||
// within a string stream is actually valid UTF-8. But refuse to do it if it was already set
|
||||
// to binary
|
||||
if stream.type_().is_string_coercible() {
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
stream.with_type(ByteStreamType::String),
|
||||
metadata,
|
||||
))
|
||||
|
@ -1,9 +1,12 @@
|
||||
use crate::database::values::sqlite::{open_sqlite_db, values_to_sql};
|
||||
use crate::{
|
||||
MEMORY_DB,
|
||||
database::values::sqlite::{open_sqlite_db, values_to_sql},
|
||||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nu_protocol::Signals;
|
||||
use std::path::Path;
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
pub const DEFAULT_TABLE_NAME: &str = "main";
|
||||
|
||||
@ -76,6 +79,17 @@ impl Command for IntoSqliteDb {
|
||||
example: "{ foo: bar, baz: quux } | into sqlite filename.db",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Insert data that contains records, lists or tables, that will be stored as JSONB columns
|
||||
These columns will be automatically turned back into nu objects when read directly via cell-path",
|
||||
example: "{a_record: {foo: bar, baz: quux}, a_list: [1 2 3], a_table: [[a b]; [0 1] [2 3]]} | into sqlite filename.db -t my_table
|
||||
(open filename.db).my_table.0.a_list",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
Value::test_int(3)
|
||||
]))
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -89,15 +103,25 @@ impl Table {
|
||||
pub fn new(
|
||||
db_path: &Spanned<String>,
|
||||
table_name: Option<Spanned<String>>,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> Result<Self, nu_protocol::ShellError> {
|
||||
let table_name = if let Some(table_name) = table_name {
|
||||
table_name.item
|
||||
} else {
|
||||
DEFAULT_TABLE_NAME.to_string()
|
||||
let table_name = table_name
|
||||
.map(|table_name| table_name.item)
|
||||
.unwrap_or_else(|| DEFAULT_TABLE_NAME.to_string());
|
||||
|
||||
let span = db_path.span;
|
||||
let db_path: Cow<'_, Path> = match db_path.item.as_str() {
|
||||
MEMORY_DB => Cow::Borrowed(Path::new(&db_path.item)),
|
||||
item => engine_state
|
||||
.cwd(Some(stack))?
|
||||
.join(item)
|
||||
.to_std_path_buf()
|
||||
.into(),
|
||||
};
|
||||
|
||||
// create the sqlite database table
|
||||
let conn = open_sqlite_db(Path::new(&db_path.item), db_path.span)?;
|
||||
let conn = open_sqlite_db(&db_path, span)?;
|
||||
|
||||
Ok(Self { conn, table_name })
|
||||
}
|
||||
@ -182,11 +206,12 @@ fn operate(
|
||||
let span = call.head;
|
||||
let file_name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let table_name: Option<Spanned<String>> = call.get_flag(engine_state, stack, "table-name")?;
|
||||
let table = Table::new(&file_name, table_name)?;
|
||||
Ok(action(input, table, span, engine_state.signals())?.into_pipeline_data())
|
||||
let table = Table::new(&file_name, table_name, engine_state, stack)?;
|
||||
Ok(action(engine_state, input, table, span, engine_state.signals())?.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn action(
|
||||
engine_state: &EngineState,
|
||||
input: PipelineData,
|
||||
table: Table,
|
||||
span: Span,
|
||||
@ -194,17 +219,17 @@ fn action(
|
||||
) -> Result<Value, ShellError> {
|
||||
match input {
|
||||
PipelineData::ListStream(stream, _) => {
|
||||
insert_in_transaction(stream.into_iter(), span, table, signals)
|
||||
insert_in_transaction(engine_state, stream.into_iter(), span, table, signals)
|
||||
}
|
||||
PipelineData::Value(value @ Value::List { .. }, _) => {
|
||||
let span = value.span();
|
||||
let vals = value
|
||||
.into_list()
|
||||
.expect("Value matched as list above, but is not a list");
|
||||
insert_in_transaction(vals.into_iter(), span, table, signals)
|
||||
insert_in_transaction(engine_state, vals.into_iter(), span, table, signals)
|
||||
}
|
||||
PipelineData::Value(val, _) => {
|
||||
insert_in_transaction(std::iter::once(val), span, table, signals)
|
||||
insert_in_transaction(engine_state, std::iter::once(val), span, table, signals)
|
||||
}
|
||||
_ => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "list".into(),
|
||||
@ -216,6 +241,7 @@ fn action(
|
||||
}
|
||||
|
||||
fn insert_in_transaction(
|
||||
engine_state: &EngineState,
|
||||
stream: impl Iterator<Item = Value>,
|
||||
span: Span,
|
||||
mut table: Table,
|
||||
@ -272,7 +298,7 @@ fn insert_in_transaction(
|
||||
inner: Vec::new(),
|
||||
})?;
|
||||
|
||||
let result = insert_value(stream_value, &mut insert_statement);
|
||||
let result = insert_value(engine_state, stream_value, span, &mut insert_statement);
|
||||
|
||||
insert_statement
|
||||
.finalize()
|
||||
@ -299,13 +325,15 @@ fn insert_in_transaction(
|
||||
}
|
||||
|
||||
fn insert_value(
|
||||
engine_state: &EngineState,
|
||||
stream_value: Value,
|
||||
call_span: Span,
|
||||
insert_statement: &mut rusqlite::Statement<'_>,
|
||||
) -> Result<(), ShellError> {
|
||||
match stream_value {
|
||||
// map each column value into its SQL representation
|
||||
Value::Record { val, .. } => {
|
||||
let sql_vals = values_to_sql(val.values().cloned())?;
|
||||
let sql_vals = values_to_sql(engine_state, val.values().cloned(), call_span)?;
|
||||
|
||||
insert_statement
|
||||
.execute(rusqlite::params_from_iter(sql_vals))
|
||||
@ -345,6 +373,7 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
||||
Type::Date => Ok("DATETIME"),
|
||||
Type::Duration => Ok("BIGINT"),
|
||||
Type::Filesize => Ok("INTEGER"),
|
||||
Type::List(_) | Type::Record(_) | Type::Table(_) => Ok("JSONB"),
|
||||
|
||||
// [NOTE] On null values, we just assume TEXT. This could end up
|
||||
// creating a table where the column type is wrong in the table schema.
|
||||
@ -358,11 +387,8 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
||||
| Type::Closure
|
||||
| Type::Custom(_)
|
||||
| Type::Error
|
||||
| Type::List(_)
|
||||
| Type::Range
|
||||
| Type::Record(_)
|
||||
| Type::Glob
|
||||
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
||||
| Type::Glob => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "sql".into(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
dst_span: Span::unknown(),
|
||||
@ -388,17 +414,3 @@ fn get_columns_with_sqlite_types(
|
||||
|
||||
Ok(columns)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
// use super::{action, IntoSqliteDb};
|
||||
// use nu_protocol::Type::Error;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(IntoSqliteDb {})
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ impl Command for QueryDb {
|
||||
Example {
|
||||
description: "Execute a SQL statement with parameters",
|
||||
example: r#"stor create -t my_table -c { first: str, second: int }
|
||||
stor open | query db "INSERT INTO my_table VALUES (?, ?)" -p [hello 123]"#,
|
||||
stor open | query db "INSERT INTO my_table VALUES (?, ?)" -p [hello 123]"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -54,6 +54,36 @@ stor open | query db "SELECT * FROM my_table WHERE second = :search_second" -p {
|
||||
"second" => Value::test_int(123)
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
description: "Execute a SQL query, selecting a declared JSON(B) column that will automatically be parsed",
|
||||
example: r#"stor create -t my_table -c {data: jsonb}
|
||||
[{data: {name: Albert, age: 40}} {data: {name: Barnaby, age: 54}}] | stor insert -t my_table
|
||||
stor open | query db "SELECT data FROM my_table WHERE data->>'age' < 45""#,
|
||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||
"data" => Value::test_record(
|
||||
record! {
|
||||
"name" => Value::test_string("Albert"),
|
||||
"age" => Value::test_int(40),
|
||||
}
|
||||
)})])),
|
||||
},
|
||||
Example {
|
||||
description: "Execute a SQL query selecting a sub-field of a JSON(B) column.
|
||||
In this case, results must be parsed afterwards because SQLite does not
|
||||
return declaration types when a JSON(B) column is not directly selected",
|
||||
example: r#"stor create -t my_table -c {data: jsonb}
|
||||
stor insert -t my_table -d {data: {foo: foo, bar: 12, baz: [0 1 2]}}
|
||||
stor open | query db "SELECT data->'baz' AS baz FROM my_table" | update baz {from json}"#,
|
||||
result: Some(Value::test_list(vec![Value::test_record(
|
||||
record! { "baz" =>
|
||||
Value::test_list(vec![
|
||||
Value::test_int(0),
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
])
|
||||
},
|
||||
)])),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -73,7 +103,7 @@ stor open | query db "SELECT * FROM my_table WHERE second = :search_second" -p {
|
||||
.get_flag(engine_state, stack, "params")?
|
||||
.unwrap_or_else(|| Value::nothing(Span::unknown()));
|
||||
|
||||
let params = nu_value_to_params(params_value)?;
|
||||
let params = nu_value_to_params(engine_state, params_value, call.head)?;
|
||||
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
db.query(&sql, params, call.head)
|
||||
|
@ -79,7 +79,7 @@ impl Command for SchemaDb {
|
||||
|
||||
// TODO: add views and triggers
|
||||
|
||||
Ok(PipelineData::Value(Value::record(record, span), None))
|
||||
Ok(PipelineData::value(Value::record(record, span), None))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ use super::definitions::{
|
||||
};
|
||||
use nu_protocol::{
|
||||
CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value,
|
||||
shell_error::io::IoError,
|
||||
engine::EngineState, shell_error::io::IoError,
|
||||
};
|
||||
use rusqlite::{
|
||||
Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement, ToSql,
|
||||
@ -431,35 +431,44 @@ fn run_sql_query(
|
||||
}
|
||||
|
||||
// This is taken from to text local_into_string but tweaks it a bit so that certain formatting does not happen
|
||||
pub fn value_to_sql(value: Value) -> Result<Box<dyn rusqlite::ToSql>, ShellError> {
|
||||
Ok(match value {
|
||||
Value::Bool { val, .. } => Box::new(val),
|
||||
Value::Int { val, .. } => Box::new(val),
|
||||
Value::Float { val, .. } => Box::new(val),
|
||||
Value::Filesize { val, .. } => Box::new(val.get()),
|
||||
Value::Duration { val, .. } => Box::new(val),
|
||||
Value::Date { val, .. } => Box::new(val),
|
||||
Value::String { val, .. } => Box::new(val),
|
||||
Value::Binary { val, .. } => Box::new(val),
|
||||
Value::Nothing { .. } => Box::new(rusqlite::types::Null),
|
||||
pub fn value_to_sql(
|
||||
engine_state: &EngineState,
|
||||
value: Value,
|
||||
call_span: Span,
|
||||
) -> Result<Box<dyn rusqlite::ToSql>, ShellError> {
|
||||
match value {
|
||||
Value::Bool { val, .. } => Ok(Box::new(val)),
|
||||
Value::Int { val, .. } => Ok(Box::new(val)),
|
||||
Value::Float { val, .. } => Ok(Box::new(val)),
|
||||
Value::Filesize { val, .. } => Ok(Box::new(val.get())),
|
||||
Value::Duration { val, .. } => Ok(Box::new(val)),
|
||||
Value::Date { val, .. } => Ok(Box::new(val)),
|
||||
Value::String { val, .. } => Ok(Box::new(val)),
|
||||
Value::Binary { val, .. } => Ok(Box::new(val)),
|
||||
Value::Nothing { .. } => Ok(Box::new(rusqlite::types::Null)),
|
||||
val => {
|
||||
return Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type:
|
||||
"bool, int, float, filesize, duration, date, string, nothing, binary".into(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
dst_span: Span::unknown(),
|
||||
src_span: val.span(),
|
||||
});
|
||||
let json_value = crate::value_to_json_value(engine_state, &val, call_span, false)?;
|
||||
match nu_json::to_string_raw(&json_value) {
|
||||
Ok(s) => Ok(Box::new(s)),
|
||||
Err(err) => Err(ShellError::CantConvert {
|
||||
to_type: "JSON".into(),
|
||||
from_type: val.get_type().to_string(),
|
||||
span: val.span(),
|
||||
help: Some(err.to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values_to_sql(
|
||||
engine_state: &EngineState,
|
||||
values: impl IntoIterator<Item = Value>,
|
||||
call_span: Span,
|
||||
) -> Result<Vec<Box<dyn rusqlite::ToSql>>, ShellError> {
|
||||
values
|
||||
.into_iter()
|
||||
.map(value_to_sql)
|
||||
.map(|v| value_to_sql(engine_state, v, call_span))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
@ -474,13 +483,17 @@ impl Default for NuSqlParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nu_value_to_params(value: Value) -> Result<NuSqlParams, ShellError> {
|
||||
pub fn nu_value_to_params(
|
||||
engine_state: &EngineState,
|
||||
value: Value,
|
||||
call_span: Span,
|
||||
) -> Result<NuSqlParams, ShellError> {
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
let mut params = Vec::with_capacity(val.len());
|
||||
|
||||
for (mut column, value) in val.into_owned().into_iter() {
|
||||
let sql_type_erased = value_to_sql(value)?;
|
||||
let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
|
||||
|
||||
if !column.starts_with([':', '@', '$']) {
|
||||
column.insert(0, ':');
|
||||
@ -495,7 +508,7 @@ pub fn nu_value_to_params(value: Value) -> Result<NuSqlParams, ShellError> {
|
||||
let mut params = Vec::with_capacity(vals.len());
|
||||
|
||||
for value in vals.into_iter() {
|
||||
let sql_type_erased = value_to_sql(value)?;
|
||||
let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
|
||||
|
||||
params.push(sql_type_erased);
|
||||
}
|
||||
@ -557,17 +570,49 @@ fn read_single_table(
|
||||
prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, signals)
|
||||
}
|
||||
|
||||
/// The SQLite type behind a query column returned as some raw type (e.g. 'text')
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DeclType {
|
||||
Json,
|
||||
Jsonb,
|
||||
}
|
||||
|
||||
impl DeclType {
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s.to_uppercase().as_str() {
|
||||
"JSON" => Some(DeclType::Json),
|
||||
"JSONB" => Some(DeclType::Jsonb),
|
||||
_ => None, // We are only special-casing JSON(B) columns for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A column out of an SQLite query, together with its type
|
||||
pub struct TypedColumn {
|
||||
pub name: String,
|
||||
pub decl_type: Option<DeclType>,
|
||||
}
|
||||
|
||||
impl TypedColumn {
|
||||
pub fn from_rusqlite_column(c: &rusqlite::Column) -> Self {
|
||||
Self {
|
||||
name: c.name().to_owned(),
|
||||
decl_type: c.decl_type().and_then(DeclType::from_str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepared_statement_to_nu_list(
|
||||
mut stmt: Statement,
|
||||
params: NuSqlParams,
|
||||
call_span: Span,
|
||||
signals: &Signals,
|
||||
) -> Result<Value, SqliteOrShellError> {
|
||||
let column_names = stmt
|
||||
.column_names()
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>();
|
||||
let columns: Vec<TypedColumn> = stmt
|
||||
.columns()
|
||||
.iter()
|
||||
.map(TypedColumn::from_rusqlite_column)
|
||||
.collect();
|
||||
|
||||
// I'm very sorry for this repetition
|
||||
// I tried scoping the match arms to the query_map alone, but lifetime and closure reference escapes
|
||||
@ -577,11 +622,7 @@ fn prepared_statement_to_nu_list(
|
||||
let refs: Vec<&dyn ToSql> = params.iter().map(|value| (&**value)).collect();
|
||||
|
||||
let row_results = stmt.query_map(refs.as_slice(), |row| {
|
||||
Ok(convert_sqlite_row_to_nu_value(
|
||||
row,
|
||||
call_span,
|
||||
&column_names,
|
||||
))
|
||||
Ok(convert_sqlite_row_to_nu_value(row, call_span, &columns))
|
||||
})?;
|
||||
|
||||
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
|
||||
@ -603,11 +644,7 @@ fn prepared_statement_to_nu_list(
|
||||
.collect();
|
||||
|
||||
let row_results = stmt.query_map(refs.as_slice(), |row| {
|
||||
Ok(convert_sqlite_row_to_nu_value(
|
||||
row,
|
||||
call_span,
|
||||
&column_names,
|
||||
))
|
||||
Ok(convert_sqlite_row_to_nu_value(row, call_span, &columns))
|
||||
})?;
|
||||
|
||||
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
|
||||
@ -650,14 +687,14 @@ fn read_entire_sqlite_db(
|
||||
Ok(Value::record(tables, call_span))
|
||||
}
|
||||
|
||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[String]) -> Value {
|
||||
let record = column_names
|
||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, columns: &[TypedColumn]) -> Value {
|
||||
let record = columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, col)| {
|
||||
(
|
||||
col.clone(),
|
||||
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span),
|
||||
col.name.clone(),
|
||||
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), col.decl_type, span),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@ -665,18 +702,25 @@ pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[Str
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
||||
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
||||
pub fn convert_sqlite_value_to_nu_value(
|
||||
value: ValueRef,
|
||||
decl_type: Option<DeclType>,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
match value {
|
||||
ValueRef::Null => Value::nothing(span),
|
||||
ValueRef::Integer(i) => Value::int(i, span),
|
||||
ValueRef::Real(f) => Value::float(f, span),
|
||||
ValueRef::Text(buf) => {
|
||||
let s = match std::str::from_utf8(buf) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Value::error(ShellError::NonUtf8 { span }, span),
|
||||
};
|
||||
Value::string(s.to_string(), span)
|
||||
}
|
||||
ValueRef::Text(buf) => match (std::str::from_utf8(buf), decl_type) {
|
||||
(Ok(txt), Some(DeclType::Json | DeclType::Jsonb)) => {
|
||||
match crate::convert_json_string_to_value(txt, span) {
|
||||
Ok(val) => val,
|
||||
Err(err) => Value::error(err, span),
|
||||
}
|
||||
}
|
||||
(Ok(txt), _) => Value::string(txt.to_string(), span),
|
||||
(Err(_), _) => Value::error(ShellError::NonUtf8 { span }, span),
|
||||
},
|
||||
ValueRef::Blob(u) => Value::binary(u.to_vec(), span),
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ impl Command for Debug {
|
||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let raw_value = call.has_flag(engine_state, stack, "raw-value")?;
|
||||
|
||||
// Should PipelineData::Empty result in an error here?
|
||||
// Should PipelineData::empty() result in an error here?
|
||||
|
||||
input.map(
|
||||
move |x| {
|
||||
|
@ -25,7 +25,7 @@ impl Command for DebugEnv {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
env_to_strings(engine_state, stack)?.into_value(call.head),
|
||||
None,
|
||||
))
|
||||
|
@ -35,7 +35,7 @@ impl Command for DebugExperimentalOptions {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
Value::list(
|
||||
nu_experimental::ALL
|
||||
.iter()
|
||||
|
4
crates/nu-command/src/env/config/config_.rs
vendored
4
crates/nu-command/src/env/config/config_.rs
vendored
@ -124,7 +124,7 @@ pub(super) fn start_editor(
|
||||
|
||||
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
// Wrap the output into a `PipelineData::byte_stream`.
|
||||
let child = nu_protocol::process::ChildProcess::new(
|
||||
child,
|
||||
None,
|
||||
@ -133,7 +133,7 @@ pub(super) fn start_editor(
|
||||
Some(post_wait_callback),
|
||||
)?;
|
||||
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::child(child, call.head),
|
||||
None,
|
||||
))
|
||||
|
@ -33,7 +33,7 @@ impl Command for ConfigUseColors {
|
||||
.get_config()
|
||||
.use_ansi_coloring
|
||||
.get(engine_state);
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
Value::bool(use_ansi_coloring, call.head),
|
||||
None,
|
||||
))
|
||||
|
@ -32,7 +32,7 @@ Messages may have numeric flags attached to them. This commands supports filteri
|
||||
If no tag is specified, this command will accept any message.
|
||||
|
||||
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
|
||||
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
|
||||
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
|
||||
If a timeout duration of zero is specified, it will succeed only if there already is a message in the mailbox.
|
||||
|
||||
Note: When using par-each, only one thread at a time can utilize this command.
|
||||
@ -78,9 +78,7 @@ in no particular order, regardless of the specified timeout parameter.
|
||||
|
||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
||||
|
||||
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
|
||||
|
||||
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
|
||||
let timeout: Option<Duration> = call.get_flag(engine_state, stack, "timeout")?;
|
||||
|
||||
let mut mailbox = engine_state
|
||||
.current_job
|
||||
@ -116,6 +114,11 @@ in no particular order, regardless of the specified timeout parameter.
|
||||
description: "Get a message or fail if no message is available immediately",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "job spawn { sleep 1sec; 'hi' | job send 0 }; job recv",
|
||||
description: "Receive a message from a newly-spawned job",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ impl Command for JobSend {
|
||||
r#"
|
||||
This command sends a message to a background job, which can then read sent messages
|
||||
in a first-in-first-out fashion with `job recv`. When it does so, it may additionally specify a numeric filter tag,
|
||||
in which case it will only read messages sent with the exact same filter tag.
|
||||
in which case it will only read messages sent with the exact same filter tag.
|
||||
In particular, the id 0 refers to the main/initial nushell thread.
|
||||
|
||||
A message can be any nushell value, and streams are always collected before being sent.
|
||||
@ -101,10 +101,17 @@ This command never blocks.
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
|
||||
description: "Send a message to a newly spawned job",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
|
||||
description: "Send a message from the main thread to a newly-spawned job",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "job spawn { sleep 1sec; 'hi' | job send 0 }; job recv",
|
||||
description: "Send a message from a newly-spawned job to the main thread (which always has an ID of 0)",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,6 @@ impl Command for Mktemp {
|
||||
});
|
||||
}
|
||||
};
|
||||
Ok(PipelineData::Value(Value::string(res, span), None))
|
||||
Ok(PipelineData::value(Value::string(res, span), None))
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ impl Command for Open {
|
||||
.map_err(|err| IoError::new(err, arg_span, PathBuf::from(path)))?;
|
||||
|
||||
// No content_type by default - Is added later if no converter is found
|
||||
let stream = PipelineData::ByteStream(
|
||||
let stream = PipelineData::byte_stream(
|
||||
ByteStream::file(file, call_span, engine_state.signals().clone()),
|
||||
Some(PipelineMetadata {
|
||||
data_source: DataSource::FilePath(path.to_path_buf()),
|
||||
@ -246,7 +246,7 @@ impl Command for Open {
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
} else if output.len() == 1 {
|
||||
Ok(output.remove(0))
|
||||
} else {
|
||||
|
@ -339,7 +339,7 @@ fn rm(
|
||||
inner: vec![],
|
||||
});
|
||||
} else if !confirmed {
|
||||
return Ok(PipelineData::Empty);
|
||||
return Ok(PipelineData::empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ impl Command for Save {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PipelineData::ListStream(ls, pipeline_metadata)
|
||||
if raw || prepare_path(&path, append, force)?.0.extension().is_none() =>
|
||||
|
@ -45,7 +45,7 @@ impl Command for Start {
|
||||
// Attempt to parse the input as a URL
|
||||
if let Ok(url) = url::Url::parse(path_no_whitespace) {
|
||||
open_path(url.as_str(), engine_state, stack, path.span)?;
|
||||
return Ok(PipelineData::Empty);
|
||||
return Ok(PipelineData::empty());
|
||||
}
|
||||
// If it's not a URL, treat it as a file path
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
@ -54,7 +54,7 @@ impl Command for Start {
|
||||
// Check if the path exists or if it's a valid file/directory
|
||||
if full_path.exists() {
|
||||
open_path(full_path, engine_state, stack, path.span)?;
|
||||
return Ok(PipelineData::Empty);
|
||||
return Ok(PipelineData::empty());
|
||||
}
|
||||
// If neither file nor URL, return an error
|
||||
Err(ShellError::GenericError {
|
||||
|
@ -6,7 +6,11 @@ use notify_debouncer_full::{
|
||||
},
|
||||
};
|
||||
use nu_engine::{ClosureEval, command_prelude::*};
|
||||
use nu_protocol::{engine::Closure, report_shell_error, shell_error::io::IoError};
|
||||
use nu_protocol::{
|
||||
DeprecationEntry, DeprecationType, ReportMode, engine::Closure, report_shell_error,
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc::{RecvTimeoutError, channel},
|
||||
@ -33,9 +37,20 @@ impl Command for Watch {
|
||||
vec!["watcher", "reload", "filesystem"]
|
||||
}
|
||||
|
||||
fn deprecation_info(&self) -> Vec<DeprecationEntry> {
|
||||
vec![DeprecationEntry {
|
||||
ty: DeprecationType::Flag("--debounce-ms".into()),
|
||||
report_mode: ReportMode::FirstUse,
|
||||
since: Some("0.107.0".into()),
|
||||
expected_removal: Some("0.109.0".into()),
|
||||
help: Some("`--debounce-ms` will be removed in favour of `--debounce`".into()),
|
||||
}]
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("watch")
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
// actually `watch` never returns normally, but we don't have `noreturn` / `never` type yet
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("path", SyntaxShape::Filepath, "The path to watch. Can be a file or directory.")
|
||||
.required("closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::String, SyntaxShape::String, SyntaxShape::String])),
|
||||
@ -43,7 +58,13 @@ impl Command for Watch {
|
||||
.named(
|
||||
"debounce-ms",
|
||||
SyntaxShape::Int,
|
||||
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events",
|
||||
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events (deprecated)",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"debounce",
|
||||
SyntaxShape::Duration,
|
||||
"Debounce changes for this duration (default: 100ms). Adjust if you find that single writes are reported as multiple events",
|
||||
Some('d'),
|
||||
)
|
||||
.named(
|
||||
@ -95,20 +116,14 @@ impl Command for Watch {
|
||||
|
||||
let quiet = call.has_flag(engine_state, stack, "quiet")?;
|
||||
|
||||
let debounce_duration_flag: Option<Spanned<i64>> =
|
||||
let debounce_duration_flag_ms: Option<Spanned<i64>> =
|
||||
call.get_flag(engine_state, stack, "debounce-ms")?;
|
||||
let debounce_duration = match debounce_duration_flag {
|
||||
Some(val) => match u64::try_from(val.item) {
|
||||
Ok(val) => Duration::from_millis(val),
|
||||
Err(_) => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "Debounce duration is invalid".to_string(),
|
||||
span: val.span,
|
||||
});
|
||||
}
|
||||
},
|
||||
None => DEFAULT_WATCH_DEBOUNCE_DURATION,
|
||||
};
|
||||
|
||||
let debounce_duration_flag: Option<Spanned<Duration>> =
|
||||
call.get_flag(engine_state, stack, "debounce")?;
|
||||
|
||||
let debounce_duration: Duration =
|
||||
resolve_duration_arguments(debounce_duration_flag_ms, debounce_duration_flag)?;
|
||||
|
||||
let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
|
||||
let glob_pattern = match glob_flag {
|
||||
@ -196,7 +211,7 @@ impl Command for Watch {
|
||||
new_path.unwrap_or_else(|| "".into()).to_string_lossy(),
|
||||
head,
|
||||
))
|
||||
.run_with_input(PipelineData::Empty);
|
||||
.run_with_input(PipelineData::empty());
|
||||
|
||||
match result {
|
||||
Ok(val) => val.print_table(engine_state, stack, false, false)?,
|
||||
@ -294,6 +309,11 @@ impl Command for Watch {
|
||||
example: r#"watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print file changes with a debounce time of 5 minutes",
|
||||
example: r#"watch /foo/bar --debounce 5min { |op, path| $"Registered ($op) on ($path)" | print }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Note: if you are looking to run a command every N units of time, this can be accomplished with a loop and sleep",
|
||||
example: r#"loop { command; sleep duration }"#,
|
||||
@ -302,3 +322,26 @@ impl Command for Watch {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_duration_arguments(
|
||||
debounce_duration_flag_ms: Option<Spanned<i64>>,
|
||||
debounce_duration_flag: Option<Spanned<Duration>>,
|
||||
) -> Result<Duration, ShellError> {
|
||||
match (debounce_duration_flag, debounce_duration_flag_ms) {
|
||||
(None, None) => Ok(DEFAULT_WATCH_DEBOUNCE_DURATION),
|
||||
(Some(l), Some(r)) => Err(ShellError::IncompatibleParameters {
|
||||
left_message: "Here".to_string(),
|
||||
left_span: l.span,
|
||||
right_message: "and here".to_string(),
|
||||
right_span: r.span,
|
||||
}),
|
||||
(None, Some(val)) => match u64::try_from(val.item) {
|
||||
Ok(v) => Ok(Duration::from_millis(v)),
|
||||
Err(_) => Err(ShellError::TypeMismatch {
|
||||
err_message: "Debounce duration is invalid".to_string(),
|
||||
span: val.span,
|
||||
}),
|
||||
},
|
||||
(Some(v), None) => Ok(v.item),
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ pub fn chunk_by(
|
||||
let metadata = input.metadata();
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream(..) => {
|
||||
|
@ -124,11 +124,11 @@ pub fn chunks(
|
||||
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||
let chunks = ChunksIter::new(vals, chunk_size, span);
|
||||
let stream = ListStream::new(chunks, span, engine_state.signals().clone());
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let stream = stream.modify(|iter| ChunksIter::new(iter, chunk_size, span));
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::Value(Value::Binary { val, .. }, metadata) => {
|
||||
let chunk_read = ChunkRead {
|
||||
@ -148,7 +148,7 @@ pub fn chunks(
|
||||
}
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
let pipeline_data = match stream.reader() {
|
||||
None => PipelineData::Empty,
|
||||
None => PipelineData::empty(),
|
||||
Some(reader) => {
|
||||
let chunk_read = ChunkRead {
|
||||
reader,
|
||||
|
@ -74,7 +74,7 @@ impl Command for Columns {
|
||||
fn getcol(head: Span, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata();
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(v, ..) => {
|
||||
let span = v.span();
|
||||
let cols = match v {
|
||||
|
@ -227,7 +227,7 @@ fn default(
|
||||
// stream's internal state already preserves the original signals config, so if this
|
||||
// Signals::empty list stream gets interrupted it will be caught by the underlying iterator
|
||||
let ls = ListStream::new(stream, span, Signals::empty());
|
||||
Ok(PipelineData::ListStream(ls, metadata))
|
||||
Ok(PipelineData::list_stream(ls, metadata))
|
||||
// Otherwise, return the input as is
|
||||
} else {
|
||||
Ok(input)
|
||||
@ -236,7 +236,7 @@ fn default(
|
||||
|
||||
/// A wrapper around the default value to handle closures and caching values
|
||||
enum DefaultValue {
|
||||
Uncalculated(Spanned<ClosureEval>),
|
||||
Uncalculated(Box<Spanned<ClosureEval>>),
|
||||
Calculated(Value),
|
||||
}
|
||||
|
||||
@ -258,7 +258,7 @@ impl DefaultValue {
|
||||
match value {
|
||||
Value::Closure { val, .. } => {
|
||||
let closure_eval = ClosureEval::new(engine_state, stack, *val);
|
||||
DefaultValue::Uncalculated(closure_eval.into_spanned(span))
|
||||
DefaultValue::Uncalculated(Box::new(closure_eval.into_spanned(span)))
|
||||
}
|
||||
_ => DefaultValue::Calculated(value),
|
||||
}
|
||||
@ -269,7 +269,7 @@ impl DefaultValue {
|
||||
DefaultValue::Uncalculated(closure) => {
|
||||
let value = closure
|
||||
.item
|
||||
.run_with_input(PipelineData::Empty)?
|
||||
.run_with_input(PipelineData::empty())?
|
||||
.into_value(closure.span)?;
|
||||
*self = DefaultValue::Calculated(value.clone());
|
||||
Ok(value)
|
||||
@ -282,7 +282,7 @@ impl DefaultValue {
|
||||
fn single_run_pipeline_data(self) -> Result<PipelineData, ShellError> {
|
||||
match self {
|
||||
DefaultValue::Uncalculated(mut closure) => {
|
||||
closure.item.run_with_input(PipelineData::Empty)
|
||||
closure.item.run_with_input(PipelineData::empty())
|
||||
}
|
||||
DefaultValue::Calculated(val) => Ok(val.into_pipeline_data()),
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ fn drop_cols(
|
||||
metadata,
|
||||
))
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
PipelineData::Value(mut v, ..) => {
|
||||
@ -136,7 +136,7 @@ fn drop_cols(
|
||||
val => Err(unsupported_value_error(&val, head)),
|
||||
}
|
||||
}
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::ByteStream(stream, ..) => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "table or record".into(),
|
||||
wrong_type: stream.type_().describe().into(),
|
||||
|
@ -90,6 +90,11 @@ with 'transpose' first."#
|
||||
Value::nothing(Span::test_data()),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
example: r#"$env.name? | each { $"hello ($in)" } | default "bye""#,
|
||||
description: "Update value if not null, otherwise do nothing",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -106,7 +111,8 @@ with 'transpose' first."#
|
||||
|
||||
let metadata = input.metadata();
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(Value::Nothing { .. }, ..) => Ok(input),
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream(..) => {
|
||||
@ -164,7 +170,7 @@ with 'transpose' first."#
|
||||
})
|
||||
.into_pipeline_data(head, engine_state.signals().clone()))
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
// This match allows non-iterables to be accepted,
|
||||
|
@ -28,7 +28,7 @@ pub fn empty(
|
||||
}
|
||||
} else {
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::ByteStream(stream, ..) => {
|
||||
let span = stream.span();
|
||||
match stream.reader() {
|
||||
|
@ -31,12 +31,12 @@ impl Command for Find {
|
||||
)
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"case-insensitive regex mode; equivalent to (?i)",
|
||||
"case-insensitive; when in regex mode, this is equivalent to (?i)",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"multiline",
|
||||
"multi-line regex mode: ^ and $ match begin/end of line; equivalent to (?m)",
|
||||
"don't split multi-line strings into lists of lines. you should use this option when using the (?m) or (?s) flags in regex mode",
|
||||
Some('m'),
|
||||
)
|
||||
.switch(
|
||||
@ -72,8 +72,8 @@ impl Command for Find {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Search and highlight text for a term in a string. Note that regular search is case insensitive",
|
||||
example: r#"'Cargo.toml' | find cargo"#,
|
||||
description: "Search and highlight text for a term in a string.",
|
||||
example: r#"'Cargo.toml' | find Cargo"#,
|
||||
result: Some(Value::test_string(
|
||||
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mCargo\u{1b}[0m\u{1b}[37m.toml\u{1b}[0m"
|
||||
.to_owned(),
|
||||
@ -81,7 +81,7 @@ impl Command for Find {
|
||||
},
|
||||
Example {
|
||||
description: "Search a number or a file size in a list of numbers",
|
||||
example: r#"[1 5 3kb 4 3Mb] | find 5 3kb"#,
|
||||
example: r#"[1 5 3kb 4 35 3Mb] | find 5 3kb"#,
|
||||
result: Some(Value::list(
|
||||
vec![Value::test_int(5), Value::test_filesize(3000)],
|
||||
Span::test_data(),
|
||||
@ -103,16 +103,16 @@ impl Command for Find {
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Find using regex",
|
||||
example: r#"[abc bde arc abf] | find --regex "ab""#,
|
||||
description: "Search using regex",
|
||||
example: r#"[abc odb arc abf] | find --regex "b.""#,
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string(
|
||||
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mab\u{1b}[0m\u{1b}[37mc\u{1b}[0m"
|
||||
"\u{1b}[37ma\u{1b}[0m\u{1b}[41;37mbc\u{1b}[0m\u{1b}[37m\u{1b}[0m"
|
||||
.to_string(),
|
||||
),
|
||||
Value::test_string(
|
||||
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mab\u{1b}[0m\u{1b}[37mf\u{1b}[0m"
|
||||
"\u{1b}[37ma\u{1b}[0m\u{1b}[41;37mbf\u{1b}[0m\u{1b}[37m\u{1b}[0m"
|
||||
.to_string(),
|
||||
),
|
||||
],
|
||||
@ -120,8 +120,8 @@ impl Command for Find {
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Find using regex case insensitive",
|
||||
example: r#"[aBc bde Arc abf] | find --regex "ab" -i"#,
|
||||
description: "Case insensitive search",
|
||||
example: r#"[aBc bde Arc abf] | find "ab" -i"#,
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string(
|
||||
@ -211,11 +211,33 @@ impl Command for Find {
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Find in a multi-line string",
|
||||
example: r#""Violets are red\nAnd roses are blue\nWhen metamaterials\nAlter their hue" | find "ue""#,
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string(
|
||||
"\u{1b}[37mAnd roses are bl\u{1b}[0m\u{1b}[41;37mue\u{1b}[0m\u{1b}[37m\u{1b}[0m",
|
||||
),
|
||||
Value::test_string(
|
||||
"\u{1b}[37mAlter their h\u{1b}[0m\u{1b}[41;37mue\u{1b}[0m\u{1b}[37m\u{1b}[0m",
|
||||
),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Find in a multi-line string without splitting the input into a list of lines",
|
||||
example: r#""Violets are red\nAnd roses are blue\nWhen metamaterials\nAlter their hue" | find --multiline "ue""#,
|
||||
result: Some(Value::test_string(
|
||||
"\u{1b}[37mViolets are red\nAnd roses are bl\u{1b}[0m\u{1b}[41;37mue\u{1b}[0m\u{1b}[37m\nWhen metamaterials\nAlter their h\u{1b}[0m\u{1b}[41;37mue\u{1b}[0m\u{1b}[37m\u{1b}[0m",
|
||||
)),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["filter", "regex", "search", "condition"]
|
||||
vec!["filter", "regex", "search", "condition", "grep"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -227,11 +249,25 @@ impl Command for Find {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern = get_match_pattern_from_arguments(engine_state, stack, call)?;
|
||||
|
||||
let multiline = call.has_flag(engine_state, stack, "multiline")?;
|
||||
|
||||
let columns_to_search: Vec<_> = call
|
||||
.get_flag(engine_state, stack, "columns")?
|
||||
.unwrap_or_default();
|
||||
|
||||
let input = split_string_if_multiline(input, call.head);
|
||||
let input = if multiline {
|
||||
if let PipelineData::ByteStream(..) = input {
|
||||
// ByteStream inputs are processed by iterating over the lines, which necessarily
|
||||
// breaks the multi-line text being streamed into a list of lines.
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: "Flag `--multiline` currently doesn't work for byte stream inputs. Consider using `collect`".into(),
|
||||
span: call.get_flag_span(stack, "multiline").expect("has flag"),
|
||||
});
|
||||
};
|
||||
input
|
||||
} else {
|
||||
split_string_if_multiline(input, call.head)
|
||||
};
|
||||
|
||||
find_in_pipelinedata(pattern, columns_to_search, engine_state, stack, input)
|
||||
}
|
||||
@ -242,8 +278,11 @@ struct MatchPattern {
|
||||
/// the regex to be used for matching in text
|
||||
regex: Regex,
|
||||
|
||||
/// the list of match terms converted to lowercase strings, or empty if a regex was provided
|
||||
lower_terms: Vec<String>,
|
||||
/// the list of match terms (converted to lowercase if needed), or empty if a regex was provided
|
||||
search_terms: Vec<String>,
|
||||
|
||||
/// case-insensitive match
|
||||
ignore_case: bool,
|
||||
|
||||
/// return a modified version of the value where matching parts are highlighted
|
||||
highlight: bool,
|
||||
@ -272,6 +311,10 @@ fn get_match_pattern_from_arguments(
|
||||
let invert = call.has_flag(engine_state, stack, "invert")?;
|
||||
let highlight = !call.has_flag(engine_state, stack, "no-highlight")?;
|
||||
|
||||
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
|
||||
let dotall = call.has_flag(engine_state, stack, "dotall")?;
|
||||
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
@ -280,7 +323,7 @@ fn get_match_pattern_from_arguments(
|
||||
let highlight_style =
|
||||
style_computer.compute("search_result", &Value::string("search result", span));
|
||||
|
||||
let (regex_str, lower_terms) = if let Some(regex) = regex {
|
||||
let (regex_str, search_terms) = if let Some(regex) = regex {
|
||||
if !terms.is_empty() {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: "Cannot use a `--regex` parameter with additional search terms".into(),
|
||||
@ -288,47 +331,54 @@ fn get_match_pattern_from_arguments(
|
||||
});
|
||||
}
|
||||
|
||||
let insensitive = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
let multiline = call.has_flag(engine_state, stack, "multiline")?;
|
||||
let dotall = call.has_flag(engine_state, stack, "dotall")?;
|
||||
|
||||
let flags = match (insensitive, multiline, dotall) {
|
||||
(false, false, false) => "",
|
||||
(true, false, false) => "(?i)", // case insensitive
|
||||
(false, true, false) => "(?m)", // multi-line mode
|
||||
(false, false, true) => "(?s)", // allow . to match \n
|
||||
(true, true, false) => "(?im)", // case insensitive and multi-line mode
|
||||
(true, false, true) => "(?is)", // case insensitive and allow . to match \n
|
||||
(false, true, true) => "(?ms)", // multi-line mode and allow . to match \n
|
||||
(true, true, true) => "(?ims)", // case insensitive, multi-line mode and allow . to match \n
|
||||
let flags = match (ignore_case, dotall) {
|
||||
(false, false) => "",
|
||||
(true, false) => "(?i)", // case insensitive
|
||||
(false, true) => "(?s)", // allow . to match \n
|
||||
(true, true) => "(?is)", // case insensitive and allow . to match \n
|
||||
};
|
||||
|
||||
(flags.to_string() + regex.as_str(), Vec::new())
|
||||
} else {
|
||||
if dotall {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: "Flag --dotall only works for regex search".into(),
|
||||
span: call.get_flag_span(stack, "dotall").expect("has flag"),
|
||||
});
|
||||
}
|
||||
|
||||
let mut regex = String::new();
|
||||
|
||||
regex += "(?i)";
|
||||
if ignore_case {
|
||||
regex += "(?i)";
|
||||
}
|
||||
|
||||
let lower_terms = terms
|
||||
let search_terms = terms
|
||||
.iter()
|
||||
.map(|v| escape(&v.to_expanded_string("", &config).to_lowercase()).into())
|
||||
.map(|v| {
|
||||
if ignore_case {
|
||||
v.to_expanded_string("", &config).to_lowercase()
|
||||
} else {
|
||||
v.to_expanded_string("", &config)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if let Some(term) = lower_terms.first() {
|
||||
let escaped_terms = search_terms
|
||||
.iter()
|
||||
.map(|v| escape(v).into())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if let Some(term) = escaped_terms.first() {
|
||||
regex += term;
|
||||
}
|
||||
|
||||
for term in lower_terms.iter().skip(1) {
|
||||
for term in escaped_terms.iter().skip(1) {
|
||||
regex += "|";
|
||||
regex += term;
|
||||
}
|
||||
|
||||
let lower_terms = terms
|
||||
.iter()
|
||||
.map(|v| v.to_expanded_string("", &config).to_lowercase())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
(regex, lower_terms)
|
||||
(regex, search_terms)
|
||||
};
|
||||
|
||||
let regex = Regex::new(regex_str.as_str()).map_err(|e| ShellError::TypeMismatch {
|
||||
@ -338,7 +388,8 @@ fn get_match_pattern_from_arguments(
|
||||
|
||||
Ok(MatchPattern {
|
||||
regex,
|
||||
lower_terms,
|
||||
search_terms,
|
||||
ignore_case,
|
||||
invert,
|
||||
highlight,
|
||||
string_style,
|
||||
@ -448,7 +499,7 @@ fn find_in_pipelinedata(
|
||||
let map_columns_to_search = columns_to_search.clone();
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(_, _) => input
|
||||
.filter(
|
||||
move |value| {
|
||||
@ -470,7 +521,7 @@ fn find_in_pipelinedata(
|
||||
.map(move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search))
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::ByteStream(stream, ..) => {
|
||||
let span = stream.span();
|
||||
@ -489,7 +540,7 @@ fn find_in_pipelinedata(
|
||||
}
|
||||
Ok(Value::list(output, span).into_pipeline_data())
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -507,7 +558,11 @@ fn value_should_be_printed(
|
||||
columns_to_search: &[String],
|
||||
config: &Config,
|
||||
) -> bool {
|
||||
let lower_value = value.to_expanded_string("", config).to_lowercase();
|
||||
let value_as_string = if pattern.ignore_case {
|
||||
value.to_expanded_string("", config).to_lowercase()
|
||||
} else {
|
||||
value.to_expanded_string("", config)
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::Bool { .. }
|
||||
@ -519,18 +574,18 @@ fn value_should_be_printed(
|
||||
| Value::Float { .. }
|
||||
| Value::Closure { .. }
|
||||
| Value::Nothing { .. } => {
|
||||
if !pattern.lower_terms.is_empty() {
|
||||
if !pattern.search_terms.is_empty() {
|
||||
// look for exact match when searching with terms
|
||||
pattern
|
||||
.lower_terms
|
||||
.search_terms
|
||||
.iter()
|
||||
.any(|term: &String| term == &lower_value)
|
||||
.any(|term: &String| term == &value_as_string)
|
||||
} else {
|
||||
string_should_be_printed(pattern, &lower_value)
|
||||
string_should_be_printed(pattern, &value_as_string)
|
||||
}
|
||||
}
|
||||
Value::Glob { .. } | Value::CellPath { .. } | Value::Custom { .. } => {
|
||||
string_should_be_printed(pattern, &lower_value)
|
||||
string_should_be_printed(pattern, &value_as_string)
|
||||
}
|
||||
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
||||
Value::List { vals, .. } => vals
|
||||
@ -597,7 +652,8 @@ pub fn find_internal(
|
||||
|
||||
let pattern = MatchPattern {
|
||||
regex,
|
||||
lower_terms: vec![search_term.to_lowercase()],
|
||||
search_terms: vec![search_term.to_lowercase()],
|
||||
ignore_case: true,
|
||||
highlight,
|
||||
invert: false,
|
||||
string_style,
|
||||
|
@ -167,7 +167,7 @@ fn first_helper(
|
||||
Err(ShellError::AccessEmptyContent { span: head })
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::ListStream(
|
||||
Ok(PipelineData::list_stream(
|
||||
stream.modify(|iter| iter.take(rows)),
|
||||
metadata,
|
||||
))
|
||||
@ -191,7 +191,7 @@ fn first_helper(
|
||||
}
|
||||
} else {
|
||||
// Just take 'rows' bytes off the stream, mimicking the binary behavior
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::read(
|
||||
reader.take(rows as u64),
|
||||
head,
|
||||
@ -202,7 +202,7 @@ fn first_helper(
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::OnlySupportsThisInputType {
|
||||
|
@ -45,6 +45,11 @@ If multiple cell paths are given, this will produce a list of values."#
|
||||
"make all cell path members optional (returns `null` for missing values)",
|
||||
Some('o'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"make all cell path members case insensitive",
|
||||
None,
|
||||
)
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"ignore missing data (make all cell path members optional) (deprecated)",
|
||||
@ -74,6 +79,30 @@ If multiple cell paths are given, this will produce a list of values."#
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Get a column from a table where some rows don't have that column, using optional cell-path syntax",
|
||||
example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get A?",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("A0"),
|
||||
Value::test_nothing(),
|
||||
Value::test_string("A2"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Get a column from a table where some rows don't have that column, using the optional flag",
|
||||
example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get -o A",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
Value::test_string("A0"),
|
||||
Value::test_nothing(),
|
||||
Value::test_string("A2"),
|
||||
],
|
||||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Get a cell from a table",
|
||||
example: "[{A: A0}] | get 0.A",
|
||||
@ -90,8 +119,13 @@ If multiple cell paths are given, this will produce a list of values."#
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Getting Path/PATH in a case insensitive way",
|
||||
example: "$env | get paTH!",
|
||||
description: "Getting environment variables in a case insensitive way, using case insensitive cell-path syntax",
|
||||
example: "$env | get home! path!",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Getting environment variables in a case insensitive way, using the '--ignore-case' flag",
|
||||
example: "$env | get --ignore-case home path",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -114,13 +148,16 @@ If multiple cell paths are given, this will produce a list of values."#
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_path: CellPath = call.req_const(working_set, 0)?;
|
||||
let rest: Vec<CellPath> = call.rest_const(working_set, 1)?;
|
||||
let ignore_errors = call.has_flag_const(working_set, "ignore-errors")?;
|
||||
let optional = call.has_flag_const(working_set, "optional")?
|
||||
|| call.has_flag_const(working_set, "ignore-errors")?;
|
||||
let ignore_case = call.has_flag_const(working_set, "ignore-case")?;
|
||||
let metadata = input.metadata();
|
||||
action(
|
||||
input,
|
||||
cell_path,
|
||||
rest,
|
||||
ignore_errors,
|
||||
optional,
|
||||
ignore_case,
|
||||
working_set.permanent().signals().clone(),
|
||||
call.head,
|
||||
)
|
||||
@ -138,12 +175,14 @@ If multiple cell paths are given, this will produce a list of values."#
|
||||
let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let optional = call.has_flag(engine_state, stack, "optional")?
|
||||
|| call.has_flag(engine_state, stack, "ignore-errors")?;
|
||||
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
let metadata = input.metadata();
|
||||
action(
|
||||
input,
|
||||
cell_path,
|
||||
rest,
|
||||
optional,
|
||||
ignore_case,
|
||||
engine_state.signals().clone(),
|
||||
call.head,
|
||||
)
|
||||
@ -175,6 +214,7 @@ fn action(
|
||||
mut cell_path: CellPath,
|
||||
mut rest: Vec<CellPath>,
|
||||
optional: bool,
|
||||
ignore_case: bool,
|
||||
signals: Signals,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
@ -185,6 +225,13 @@ fn action(
|
||||
}
|
||||
}
|
||||
|
||||
if ignore_case {
|
||||
cell_path.make_insensitive();
|
||||
for path in &mut rest {
|
||||
path.make_insensitive();
|
||||
}
|
||||
}
|
||||
|
||||
if let PipelineData::Empty = input {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: span });
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ fn insert(
|
||||
value
|
||||
}
|
||||
});
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
} else {
|
||||
let stream = stream.map(move |mut value| {
|
||||
if let Err(e) = value.insert_data_at_cell_path(
|
||||
@ -278,7 +278,7 @@ fn insert(
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
}
|
||||
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
||||
|
@ -120,7 +120,7 @@ interleave
|
||||
.into_iter()
|
||||
.chain(closures.into_iter().map(|closure| {
|
||||
ClosureEvalOnce::new(engine_state, stack, closure)
|
||||
.run_with_input(PipelineData::Empty)
|
||||
.run_with_input(PipelineData::empty())
|
||||
}))
|
||||
.try_for_each(|stream| {
|
||||
stream.and_then(|stream| {
|
||||
|
@ -42,7 +42,7 @@ impl Command for Items {
|
||||
|
||||
let metadata = input.metadata();
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
@ -55,7 +55,7 @@ impl Command for Items {
|
||||
let result = closure
|
||||
.add_arg(Value::string(col, span))
|
||||
.add_arg(val)
|
||||
.run_with_input(PipelineData::Empty)
|
||||
.run_with_input(PipelineData::empty())
|
||||
.and_then(|data| data.into_value(head));
|
||||
|
||||
match result {
|
||||
|
@ -85,7 +85,7 @@ impl Command for Join {
|
||||
Value::String { val: r_on, .. },
|
||||
) => {
|
||||
let result = join(rows_1, rows_2, l_on, r_on, join_type, span);
|
||||
Ok(PipelineData::Value(result, metadata))
|
||||
Ok(PipelineData::value(result, metadata))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedInput {
|
||||
msg: "(PipelineData<table>, table, string, string)".into(),
|
||||
|
@ -186,7 +186,7 @@ impl Command for Last {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::OnlySupportsThisInputType {
|
||||
|
@ -57,7 +57,7 @@ impl Command for Lines {
|
||||
src_span: value.span(),
|
||||
}),
|
||||
},
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let stream = stream.modify(|iter| {
|
||||
iter.filter_map(move |value| {
|
||||
@ -81,7 +81,7 @@ impl Command for Lines {
|
||||
.flatten()
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::ByteStream(stream, ..) => {
|
||||
if let Some(lines) = stream.lines() {
|
||||
|
@ -130,7 +130,7 @@ impl Command for ParEach {
|
||||
};
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
|
@ -123,7 +123,7 @@ impl Command for Reduce {
|
||||
acc = closure
|
||||
.add_arg(value)
|
||||
.add_arg(acc.clone())
|
||||
.run_with_input(PipelineData::Value(acc, None))?
|
||||
.run_with_input(PipelineData::value(acc, None))?
|
||||
.into_value(head)?;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,11 @@ impl Command for Reject {
|
||||
(Type::list(Type::Any), Type::list(Type::Any)),
|
||||
])
|
||||
.switch("optional", "make all cell path members optional", Some('o'))
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"make all cell path members case insensitive",
|
||||
None,
|
||||
)
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"ignore missing data (make all cell path members optional) (deprecated)",
|
||||
@ -93,12 +98,20 @@ impl Command for Reject {
|
||||
|
||||
let optional = call.has_flag(engine_state, stack, "optional")?
|
||||
|| call.has_flag(engine_state, stack, "ignore-errors")?;
|
||||
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
|
||||
if optional {
|
||||
for cell_path in &mut new_columns {
|
||||
cell_path.make_optional();
|
||||
}
|
||||
}
|
||||
|
||||
if ignore_case {
|
||||
for cell_path in &mut new_columns {
|
||||
cell_path.make_insensitive();
|
||||
}
|
||||
}
|
||||
|
||||
reject(engine_state, span, input, new_columns)
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,11 @@ impl Command for Select {
|
||||
"make all cell path members optional (returns `null` for missing values)",
|
||||
Some('o'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-case",
|
||||
"make all cell path members case insensitive",
|
||||
None,
|
||||
)
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"ignore missing data (make all cell path members optional) (deprecated)",
|
||||
@ -110,6 +115,7 @@ produce a table, a list will produce a list, and a record will produce a record.
|
||||
}
|
||||
let optional = call.has_flag(engine_state, stack, "optional")?
|
||||
|| call.has_flag(engine_state, stack, "ignore-errors")?;
|
||||
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
|
||||
let span = call.head;
|
||||
|
||||
if optional {
|
||||
@ -118,6 +124,12 @@ produce a table, a list will produce a list, and a record will produce a record.
|
||||
}
|
||||
}
|
||||
|
||||
if ignore_case {
|
||||
for cell_path in &mut new_columns {
|
||||
cell_path.make_insensitive();
|
||||
}
|
||||
}
|
||||
|
||||
select(engine_state, span, new_columns, input)
|
||||
}
|
||||
|
||||
@ -143,6 +155,18 @@ produce a table, a list will produce a list, and a record will produce a record.
|
||||
"a" => Value::test_string("a")
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
description: "Select a column even if some rows are missing that column",
|
||||
example: "[{a: a0 b: b0} {b: b1}] | select -o a",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_string("a0")
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_nothing()
|
||||
}),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Select a field in a record",
|
||||
example: "{a: a b: b} | select a",
|
||||
|
@ -94,7 +94,7 @@ impl Command for Skip {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
let span = stream.span();
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
stream.skip(span, n as u64)?,
|
||||
metadata,
|
||||
))
|
||||
|
@ -81,7 +81,7 @@ impl Command for Slice {
|
||||
};
|
||||
|
||||
if count == 0 {
|
||||
Ok(PipelineData::Value(Value::list(vec![], head), None))
|
||||
Ok(PipelineData::value(Value::list(vec![], head), None))
|
||||
} else {
|
||||
let iter = v.into_iter().skip(from).take(count);
|
||||
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
|
||||
@ -102,7 +102,7 @@ impl Command for Slice {
|
||||
};
|
||||
|
||||
if count == 0 {
|
||||
Ok(PipelineData::Value(Value::list(vec![], head), None))
|
||||
Ok(PipelineData::value(Value::list(vec![], head), None))
|
||||
} else {
|
||||
let iter = input.into_iter().skip(from).take(count);
|
||||
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
|
||||
|
@ -62,7 +62,7 @@ impl Command for Take {
|
||||
)),
|
||||
Value::Binary { val, .. } => {
|
||||
let slice: Vec<u8> = val.into_iter().take(rows_desired).collect();
|
||||
Ok(PipelineData::Value(Value::binary(slice, span), metadata))
|
||||
Ok(PipelineData::value(Value::binary(slice, span), metadata))
|
||||
}
|
||||
Value::Range { val, .. } => Ok(val
|
||||
.into_range_iter(span, Signals::empty())
|
||||
@ -82,14 +82,14 @@ impl Command for Take {
|
||||
}),
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata) => Ok(PipelineData::ListStream(
|
||||
PipelineData::ListStream(stream, metadata) => Ok(PipelineData::list_stream(
|
||||
stream.modify(|iter| iter.take(rows_desired)),
|
||||
metadata,
|
||||
)),
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
if stream.type_().is_binary_coercible() {
|
||||
let span = stream.span();
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
stream.take(span, rows_desired as u64)?,
|
||||
metadata,
|
||||
))
|
||||
|
@ -138,7 +138,7 @@ use it in your pipeline."#
|
||||
let tee_thread = spawn_tee(info, eval_block)?;
|
||||
let tee = IoTee::new(read, tee_thread);
|
||||
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::read(tee, span, engine_state.signals().clone(), type_),
|
||||
metadata,
|
||||
))
|
||||
@ -151,7 +151,7 @@ use it in your pipeline."#
|
||||
let tee_thread = spawn_tee(info, eval_block)?;
|
||||
let tee = IoTee::new(file, tee_thread);
|
||||
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::read(tee, span, engine_state.signals().clone(), type_),
|
||||
metadata,
|
||||
))
|
||||
@ -234,7 +234,7 @@ use it in your pipeline."#
|
||||
};
|
||||
|
||||
if child.stdout.is_some() || child.stderr.is_some() {
|
||||
Ok(PipelineData::ByteStream(
|
||||
Ok(PipelineData::byte_stream(
|
||||
ByteStream::child(*child, span),
|
||||
metadata,
|
||||
))
|
||||
@ -243,7 +243,7 @@ use it in your pipeline."#
|
||||
thread.join().unwrap_or_else(|_| Err(panic_error()))?;
|
||||
}
|
||||
child.wait()?;
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -439,7 +439,7 @@ fn spawn_tee(
|
||||
Signals::empty(),
|
||||
info.type_,
|
||||
);
|
||||
eval_block(PipelineData::ByteStream(stream, info.metadata))
|
||||
eval_block(PipelineData::byte_stream(stream, info.metadata))
|
||||
})
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(err, info.span, None, "Could not spawn tee")
|
||||
|
@ -293,7 +293,7 @@ pub fn transpose(
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
if result_data.len() == 1 && args.as_record {
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
result_data
|
||||
.pop()
|
||||
.expect("already check result only contains one item"),
|
||||
|
@ -210,7 +210,7 @@ fn update(
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
} else {
|
||||
let stream = stream.map(move |mut value| {
|
||||
if let Err(e) =
|
||||
@ -222,7 +222,7 @@ fn update(
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
}
|
||||
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
||||
|
@ -288,7 +288,7 @@ fn upsert(
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
} else {
|
||||
let stream = stream.map(move |mut value| {
|
||||
if let Err(e) =
|
||||
@ -300,7 +300,7 @@ fn upsert(
|
||||
}
|
||||
});
|
||||
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
}
|
||||
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
||||
@ -335,7 +335,7 @@ fn upsert_value_by_closure(
|
||||
let input = value_at_path
|
||||
.map(Cow::into_owned)
|
||||
.map(IntoPipelineData::into_pipeline_data)
|
||||
.unwrap_or(PipelineData::Empty);
|
||||
.unwrap_or(PipelineData::empty());
|
||||
|
||||
let new_value = closure
|
||||
.add_arg(arg)
|
||||
@ -366,7 +366,7 @@ fn upsert_single_value_by_closure(
|
||||
let input = value_at_path
|
||||
.map(Cow::into_owned)
|
||||
.map(IntoPipelineData::into_pipeline_data)
|
||||
.unwrap_or(PipelineData::Empty);
|
||||
.unwrap_or(PipelineData::empty());
|
||||
|
||||
let new_value = closure
|
||||
.add_arg(arg)
|
||||
|
@ -137,7 +137,7 @@ fn values(
|
||||
let signals = engine_state.signals().clone();
|
||||
let metadata = input.metadata();
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(v, ..) => {
|
||||
let span = v.span();
|
||||
match v {
|
||||
|
@ -120,12 +120,12 @@ impl Command for Window {
|
||||
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||
let chunks = WindowGapIter::new(vals, size, stride, remainder, head);
|
||||
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let stream = stream
|
||||
.modify(|iter| WindowGapIter::new(iter, size, stride, remainder, head));
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
input => Err(input.unsupported_input_error("list", head)),
|
||||
}
|
||||
@ -134,12 +134,12 @@ impl Command for Window {
|
||||
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||
let chunks = WindowOverlapIter::new(vals, size, stride, remainder, head);
|
||||
let stream = ListStream::new(chunks, head, engine_state.signals().clone());
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let stream = stream
|
||||
.modify(|iter| WindowOverlapIter::new(iter, size, stride, remainder, head));
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
Ok(PipelineData::list_stream(stream, metadata))
|
||||
}
|
||||
input => Err(input.unsupported_input_error("list", head)),
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ impl Command for Wrap {
|
||||
let metadata = input.metadata();
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. } => Ok(input
|
||||
|
@ -103,7 +103,7 @@ impl Command for Zip {
|
||||
let metadata = input.metadata();
|
||||
let other = if let Value::Closure { val, .. } = other {
|
||||
// If a closure was provided, evaluate it and consume its stream output
|
||||
ClosureEvalOnce::new(engine_state, stack, *val).run_with_input(PipelineData::Empty)?
|
||||
ClosureEvalOnce::new(engine_state, stack, *val).run_with_input(PipelineData::empty())?
|
||||
} else {
|
||||
other.into_pipeline_data()
|
||||
};
|
||||
|
@ -95,11 +95,11 @@ pub(super) fn from_delimited_data(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata().map(|md| md.with_content_type(None));
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Empty => Ok(PipelineData::empty()),
|
||||
PipelineData::Value(value, ..) => {
|
||||
let string = value.into_string()?;
|
||||
let byte_stream = ByteStream::read_string(string, name, Signals::empty());
|
||||
Ok(PipelineData::ListStream(
|
||||
Ok(PipelineData::list_stream(
|
||||
from_delimited_stream(config, byte_stream, name)?,
|
||||
metadata,
|
||||
))
|
||||
@ -110,7 +110,7 @@ pub(super) fn from_delimited_data(
|
||||
dst_span: name,
|
||||
src_span: list_stream.span(),
|
||||
}),
|
||||
PipelineData::ByteStream(byte_stream, ..) => Ok(PipelineData::ListStream(
|
||||
PipelineData::ByteStream(byte_stream, ..) => Ok(PipelineData::list_stream(
|
||||
from_delimited_stream(config, byte_stream, name)?,
|
||||
metadata,
|
||||
)),
|
||||
|
@ -76,25 +76,27 @@ impl Command for FromJson {
|
||||
if call.has_flag(engine_state, stack, "objects")? {
|
||||
// Return a stream of JSON values, one for each non-empty line
|
||||
match input {
|
||||
PipelineData::Value(Value::String { val, .. }, ..) => Ok(PipelineData::ListStream(
|
||||
read_json_lines(
|
||||
Cursor::new(val),
|
||||
span,
|
||||
strict,
|
||||
engine_state.signals().clone(),
|
||||
),
|
||||
metadata,
|
||||
)),
|
||||
PipelineData::Value(Value::String { val, .. }, ..) => {
|
||||
Ok(PipelineData::list_stream(
|
||||
read_json_lines(
|
||||
Cursor::new(val),
|
||||
span,
|
||||
strict,
|
||||
engine_state.signals().clone(),
|
||||
),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
PipelineData::ByteStream(stream, ..)
|
||||
if stream.type_() != ByteStreamType::Binary =>
|
||||
{
|
||||
if let Some(reader) = stream.reader() {
|
||||
Ok(PipelineData::ListStream(
|
||||
Ok(PipelineData::list_stream(
|
||||
read_json_lines(reader, span, strict, Signals::empty()),
|
||||
metadata,
|
||||
))
|
||||
} else {
|
||||
Ok(PipelineData::Empty)
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::OnlySupportsThisInputType {
|
||||
@ -184,7 +186,7 @@ fn convert_nujson_to_value(value: nu_json::Value, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_string_to_value(string_input: &str, span: Span) -> Result<Value, ShellError> {
|
||||
pub(crate) fn convert_string_to_value(string_input: &str, span: Span) -> Result<Value, ShellError> {
|
||||
match nu_json::from_str(string_input) {
|
||||
Ok(value) => Ok(convert_nujson_to_value(value, span)),
|
||||
|
||||
|
@ -27,3 +27,6 @@ pub use xlsx::FromXlsx;
|
||||
pub use xml::FromXml;
|
||||
pub use yaml::FromYaml;
|
||||
pub use yaml::FromYml;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub(crate) use json::convert_string_to_value as convert_json_string_to_value;
|
||||
|
@ -168,7 +168,7 @@ fn from_ods(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
Value::record(dict.into_iter().collect(), head),
|
||||
None,
|
||||
))
|
||||
|
@ -181,7 +181,7 @@ fn from_xlsx(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Ok(PipelineData::value(
|
||||
Value::record(dict.into_iter().collect(), head),
|
||||
None,
|
||||
))
|
||||
|
@ -132,7 +132,7 @@ pub fn to_delimited_data(
|
||||
Value::Record { val, .. } => val.columns().cloned().collect(),
|
||||
_ => return Err(make_unsupported_input_error(value.get_type(), head, span)),
|
||||
};
|
||||
input = PipelineData::Value(value, metadata.clone());
|
||||
input = PipelineData::value(value, metadata.clone());
|
||||
columns
|
||||
}
|
||||
};
|
||||
@ -181,5 +181,5 @@ pub fn to_delimited_data(
|
||||
},
|
||||
);
|
||||
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
Ok(PipelineData::byte_stream(stream, metadata))
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl Command for ToJson {
|
||||
data_source: nu_protocol::DataSource::None,
|
||||
content_type: Some(mime::APPLICATION_JSON.to_string()),
|
||||
};
|
||||
Ok(PipelineData::Value(res, Some(metadata)))
|
||||
Ok(PipelineData::value(res, Some(metadata)))
|
||||
}
|
||||
_ => Err(ShellError::CantConvert {
|
||||
to_type: "JSON".into(),
|
||||
|
@ -121,10 +121,10 @@ impl Command for ToText {
|
||||
)
|
||||
};
|
||||
|
||||
Ok(PipelineData::ByteStream(stream, update_metadata(meta)))
|
||||
Ok(PipelineData::byte_stream(stream, update_metadata(meta)))
|
||||
}
|
||||
PipelineData::ByteStream(stream, meta) => {
|
||||
Ok(PipelineData::ByteStream(stream, update_metadata(meta)))
|
||||
Ok(PipelineData::byte_stream(stream, update_metadata(meta)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ In this case, generation also stops when the input stream stops."#
|
||||
|
||||
let closure_result = closure
|
||||
.add_arg(state_arg)
|
||||
.run_with_input(PipelineData::Empty);
|
||||
.run_with_input(PipelineData::empty());
|
||||
let (output, next_input) = parse_closure_result(closure_result, head);
|
||||
|
||||
// We use `state` to control when to stop, not `output`. By wrapping
|
||||
@ -135,7 +135,7 @@ In this case, generation also stops when the input stream stops."#
|
||||
let closure_result = closure
|
||||
.add_arg(item)
|
||||
.add_arg(state_arg)
|
||||
.run_with_input(PipelineData::Empty);
|
||||
.run_with_input(PipelineData::empty());
|
||||
let (output, next_input) = parse_closure_result(closure_result, head);
|
||||
state = next_input;
|
||||
Some(output)
|
||||
|
@ -76,7 +76,7 @@ pub fn calculate(
|
||||
PipelineData::Value(Value::List { ref vals, .. }, ..) => match &vals[..] {
|
||||
[Value::Record { .. }, _end @ ..] => helper_for_tables(
|
||||
vals,
|
||||
values.span().expect("PipelineData::Value had no span"),
|
||||
values.span().expect("PipelineData::value had no span"),
|
||||
name,
|
||||
mf,
|
||||
),
|
||||
|
@ -1,26 +1,35 @@
|
||||
use crate::{formats::value_to_json_value, network::tls::tls};
|
||||
use crate::{
|
||||
formats::value_to_json_value,
|
||||
network::{http::timeout_extractor_reader::UreqTimeoutExtractorReader, tls::tls},
|
||||
};
|
||||
use base64::{
|
||||
Engine, alphabet,
|
||||
engine::{GeneralPurpose, general_purpose::PAD},
|
||||
};
|
||||
use http::StatusCode;
|
||||
use log::error;
|
||||
use multipart_rs::MultipartWriter;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ByteStream, LabeledError, Signals, shell_error::io::IoError};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error::Error as StdError,
|
||||
io::Cursor,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::mpsc::{self, RecvTimeoutError},
|
||||
time::Duration,
|
||||
};
|
||||
use ureq::{Error, ErrorKind, Request, Response};
|
||||
use ureq::{
|
||||
Body, Error, RequestBuilder, ResponseExt, SendBody,
|
||||
typestate::{WithBody, WithoutBody},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
const HTTP_DOCS: &str = "https://www.nushell.sh/cookbook/http.html";
|
||||
|
||||
type Response = http::Response<Body>;
|
||||
|
||||
type ContentType = String;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@ -43,6 +52,20 @@ impl From<Option<ContentType>> for BodyType {
|
||||
}
|
||||
}
|
||||
|
||||
trait GetHeader {
|
||||
fn header(&self, key: &str) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl GetHeader for Response {
|
||||
fn header(&self, key: &str) -> Option<&str> {
|
||||
self.headers().get(key).and_then(|v| {
|
||||
v.to_str()
|
||||
.map_err(|e| log::error!("Invalid header {e:?}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RedirectMode {
|
||||
Follow,
|
||||
@ -56,21 +79,24 @@ pub fn http_client(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<ureq::Agent, ShellError> {
|
||||
let mut agent_builder = ureq::builder()
|
||||
let mut config_builder = ureq::config::Config::builder()
|
||||
.user_agent("nushell")
|
||||
.tls_connector(std::sync::Arc::new(tls(allow_insecure)?));
|
||||
.save_redirect_history(true)
|
||||
.http_status_as_error(false)
|
||||
.max_redirects_will_error(false);
|
||||
|
||||
if let RedirectMode::Manual | RedirectMode::Error = redirect_mode {
|
||||
agent_builder = agent_builder.redirects(0);
|
||||
config_builder = config_builder.max_redirects(0);
|
||||
}
|
||||
|
||||
if let Some(http_proxy) = retrieve_http_proxy_from_env(engine_state, stack) {
|
||||
if let Ok(proxy) = ureq::Proxy::new(http_proxy) {
|
||||
agent_builder = agent_builder.proxy(proxy);
|
||||
if let Ok(proxy) = ureq::Proxy::new(&http_proxy) {
|
||||
config_builder = config_builder.proxy(Some(proxy));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(agent_builder.build())
|
||||
config_builder = config_builder.tls_config(tls(allow_insecure)?);
|
||||
Ok(ureq::Agent::new_with_config(config_builder.build()))
|
||||
}
|
||||
|
||||
pub fn http_parse_url(
|
||||
@ -92,7 +118,7 @@ pub fn http_parse_url(
|
||||
msg: "Incomplete or incorrect URL. Expected a full URL, e.g., https://www.example.com".to_string(),
|
||||
input: format!("value: '{requested_url:?}'"),
|
||||
msg_span: call.head,
|
||||
input_span: span
|
||||
input_span: span,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -141,20 +167,22 @@ pub fn response_to_buffer(
|
||||
_ => ByteStreamType::Unknown,
|
||||
};
|
||||
|
||||
let reader = response.into_reader();
|
||||
let reader = UreqTimeoutExtractorReader {
|
||||
r: response.into_body().into_reader(),
|
||||
};
|
||||
|
||||
PipelineData::ByteStream(
|
||||
PipelineData::byte_stream(
|
||||
ByteStream::read(reader, span, engine_state.signals().clone(), response_type)
|
||||
.with_known_size(buffer_size),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_add_authorization_header(
|
||||
pub fn request_add_authorization_header<B>(
|
||||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
mut request: Request,
|
||||
) -> Request {
|
||||
mut request: RequestBuilder<B>,
|
||||
) -> RequestBuilder<B> {
|
||||
let base64_engine = GeneralPurpose::new(&alphabet::STANDARD, PAD);
|
||||
|
||||
let login = match (user, password) {
|
||||
@ -177,7 +205,7 @@ pub fn request_add_authorization_header(
|
||||
};
|
||||
|
||||
if let Some(login) = login {
|
||||
request = request.set("Authorization", &format!("Basic {login}"));
|
||||
request = request.header("Authorization", &format!("Basic {login}"));
|
||||
}
|
||||
|
||||
request
|
||||
@ -200,34 +228,45 @@ impl From<ShellError> for ShellErrorOrRequestError {
|
||||
pub enum HttpBody {
|
||||
Value(Value),
|
||||
ByteStream(ByteStream),
|
||||
None,
|
||||
}
|
||||
|
||||
pub fn send_request_no_body(
|
||||
request: RequestBuilder<WithoutBody>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
) -> (Result<Response, ShellError>, Headers) {
|
||||
let headers = extract_request_headers(&request);
|
||||
let request_url = request.uri_ref().cloned().unwrap_or_default().to_string();
|
||||
let result = send_cancellable_request(&request_url, Box::new(|| request.call()), span, signals)
|
||||
.map_err(|e| request_error_to_shell_error(span, e));
|
||||
|
||||
(result, headers.unwrap_or_default())
|
||||
}
|
||||
|
||||
// remove once all commands have been migrated
|
||||
pub fn send_request(
|
||||
engine_state: &EngineState,
|
||||
request: Request,
|
||||
http_body: HttpBody,
|
||||
request: RequestBuilder<WithBody>,
|
||||
body: HttpBody,
|
||||
content_type: Option<String>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
) -> Result<Response, ShellErrorOrRequestError> {
|
||||
let request_url = request.url().to_string();
|
||||
) -> (Result<Response, ShellError>, Headers) {
|
||||
let mut request_headers = Headers::new();
|
||||
let request_url = request.uri_ref().cloned().unwrap_or_default().to_string();
|
||||
// hard code serialze_types to false because closures probably shouldn't be
|
||||
// deserialized for send_request but it's required by send_json_request
|
||||
let serialze_types = false;
|
||||
|
||||
match http_body {
|
||||
HttpBody::None => {
|
||||
send_cancellable_request(&request_url, Box::new(|| request.call()), span, signals)
|
||||
}
|
||||
let response = match body {
|
||||
HttpBody::ByteStream(byte_stream) => {
|
||||
let req = if let Some(content_type) = content_type {
|
||||
request.set("Content-Type", &content_type)
|
||||
request.header("Content-Type", &content_type)
|
||||
} else {
|
||||
request
|
||||
};
|
||||
|
||||
if let Some(h) = extract_request_headers(&req) {
|
||||
request_headers = h;
|
||||
}
|
||||
send_cancellable_request_bytes(&request_url, req, byte_stream, span, signals)
|
||||
}
|
||||
HttpBody::Value(body) => {
|
||||
@ -236,11 +275,15 @@ pub fn send_request(
|
||||
// We should set the content_type if there is one available
|
||||
// when the content type is unknown
|
||||
let req = if let BodyType::Unknown(Some(content_type)) = &body_type {
|
||||
request.clone().set("Content-Type", content_type)
|
||||
request.header("Content-Type", content_type)
|
||||
} else {
|
||||
request
|
||||
};
|
||||
|
||||
if let Some(h) = extract_request_headers(&req) {
|
||||
request_headers = h;
|
||||
}
|
||||
|
||||
match body_type {
|
||||
BodyType::Json => send_json_request(
|
||||
engine_state,
|
||||
@ -260,14 +303,18 @@ pub fn send_request(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let response = response.map_err(|e| request_error_to_shell_error(span, e));
|
||||
|
||||
(response, request_headers)
|
||||
}
|
||||
|
||||
fn send_json_request(
|
||||
engine_state: &EngineState,
|
||||
request_url: &str,
|
||||
body: Value,
|
||||
req: Request,
|
||||
req: RequestBuilder<WithBody>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
serialize_types: bool,
|
||||
@ -311,7 +358,7 @@ fn send_json_request(
|
||||
fn send_form_request(
|
||||
request_url: &str,
|
||||
body: Value,
|
||||
req: Request,
|
||||
req: RequestBuilder<WithBody>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
) -> Result<Response, ShellErrorOrRequestError> {
|
||||
@ -321,7 +368,7 @@ fn send_form_request(
|
||||
.iter()
|
||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||
.collect::<Vec<(&str, &str)>>();
|
||||
req.send_form(&data)
|
||||
req.send_form(data)
|
||||
};
|
||||
|
||||
match body {
|
||||
@ -364,7 +411,7 @@ fn send_form_request(
|
||||
fn send_multipart_request(
|
||||
request_url: &str,
|
||||
body: Value,
|
||||
req: Request,
|
||||
req: RequestBuilder<WithBody>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
) -> Result<Response, ShellErrorOrRequestError> {
|
||||
@ -401,7 +448,7 @@ fn send_multipart_request(
|
||||
let (boundary, data) = (builder.boundary, builder.data);
|
||||
let content_type = format!("multipart/form-data; boundary={boundary}");
|
||||
|
||||
move || req.set("Content-Type", &content_type).send_bytes(&data)
|
||||
move || req.header("Content-Type", &content_type).send(&data)
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellErrorOrRequestError::ShellError(
|
||||
@ -418,23 +465,17 @@ fn send_multipart_request(
|
||||
fn send_default_request(
|
||||
request_url: &str,
|
||||
body: Value,
|
||||
req: Request,
|
||||
req: RequestBuilder<WithBody>,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
) -> Result<Response, ShellErrorOrRequestError> {
|
||||
match body {
|
||||
Value::Binary { val, .. } => send_cancellable_request(
|
||||
request_url,
|
||||
Box::new(move || req.send_bytes(&val)),
|
||||
span,
|
||||
signals,
|
||||
),
|
||||
Value::String { val, .. } => send_cancellable_request(
|
||||
request_url,
|
||||
Box::new(move || req.send_string(&val)),
|
||||
span,
|
||||
signals,
|
||||
),
|
||||
Value::Binary { val, .. } => {
|
||||
send_cancellable_request(request_url, Box::new(move || req.send(&val)), span, signals)
|
||||
}
|
||||
Value::String { val, .. } => {
|
||||
send_cancellable_request(request_url, Box::new(move || req.send(&val)), span, signals)
|
||||
}
|
||||
_ => Err(ShellErrorOrRequestError::ShellError(
|
||||
ShellError::TypeMismatch {
|
||||
err_message: format!("Accepted types: [binary, string]. Check: {HTTP_DOCS}"),
|
||||
@ -487,7 +528,7 @@ fn send_cancellable_request(
|
||||
// ureq functions can block for a long time (default 30s?) while attempting to make an HTTP connection
|
||||
fn send_cancellable_request_bytes(
|
||||
request_url: &str,
|
||||
request: Request,
|
||||
request: ureq::RequestBuilder<WithBody>,
|
||||
byte_stream: ByteStream,
|
||||
span: Span,
|
||||
signals: &Signals,
|
||||
@ -511,9 +552,11 @@ fn send_cancellable_request_bytes(
|
||||
})
|
||||
})
|
||||
.and_then(|reader| {
|
||||
request.send(reader).map_err(|e| {
|
||||
ShellErrorOrRequestError::RequestError(request_url_string, Box::new(e))
|
||||
})
|
||||
request
|
||||
.send(SendBody::from_owned_reader(reader))
|
||||
.map_err(|e| {
|
||||
ShellErrorOrRequestError::RequestError(request_url_string, Box::new(e))
|
||||
})
|
||||
});
|
||||
|
||||
// may fail if the user has cancelled the operation
|
||||
@ -537,10 +580,10 @@ fn send_cancellable_request_bytes(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_set_timeout(
|
||||
pub fn request_set_timeout<B>(
|
||||
timeout: Option<Value>,
|
||||
mut request: Request,
|
||||
) -> Result<Request, ShellError> {
|
||||
mut request: RequestBuilder<B>,
|
||||
) -> Result<RequestBuilder<B>, ShellError> {
|
||||
if let Some(timeout) = timeout {
|
||||
let val = timeout.as_duration()?;
|
||||
if val.is_negative() || val < 1 {
|
||||
@ -550,16 +593,19 @@ pub fn request_set_timeout(
|
||||
});
|
||||
}
|
||||
|
||||
request = request.timeout(Duration::from_nanos(val as u64));
|
||||
request = request
|
||||
.config()
|
||||
.timeout_global(Some(Duration::from_nanos(val as u64)))
|
||||
.build()
|
||||
}
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
pub fn request_add_custom_headers(
|
||||
pub fn request_add_custom_headers<B>(
|
||||
headers: Option<Value>,
|
||||
mut request: Request,
|
||||
) -> Result<Request, ShellError> {
|
||||
mut request: RequestBuilder<B>,
|
||||
) -> Result<RequestBuilder<B>, ShellError> {
|
||||
if let Some(headers) = headers {
|
||||
let mut custom_headers: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
@ -611,7 +657,7 @@ pub fn request_add_custom_headers(
|
||||
|
||||
for (k, v) in custom_headers {
|
||||
if let Ok(s) = v.coerce_into_string() {
|
||||
request = request.set(&k, &s);
|
||||
request = request.header(&k, &s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -619,63 +665,57 @@ pub fn request_add_custom_headers(
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
|
||||
match response_err {
|
||||
Error::Status(301, _) => ShellError::NetworkFailure {
|
||||
fn handle_status_error(span: Span, requested_url: &str, status: StatusCode) -> ShellError {
|
||||
match status {
|
||||
StatusCode::MOVED_PERMANENTLY => ShellError::NetworkFailure {
|
||||
msg: format!("Resource moved permanently (301): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(400, _) => ShellError::NetworkFailure {
|
||||
StatusCode::BAD_REQUEST => ShellError::NetworkFailure {
|
||||
msg: format!("Bad request (400) to {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(403, _) => ShellError::NetworkFailure {
|
||||
StatusCode::FORBIDDEN => ShellError::NetworkFailure {
|
||||
msg: format!("Access forbidden (403) to {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(404, _) => ShellError::NetworkFailure {
|
||||
StatusCode::NOT_FOUND => ShellError::NetworkFailure {
|
||||
msg: format!("Requested file not found (404): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(408, _) => ShellError::NetworkFailure {
|
||||
StatusCode::REQUEST_TIMEOUT => ShellError::NetworkFailure {
|
||||
msg: format!("Request timeout (408): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(_, _) => ShellError::NetworkFailure {
|
||||
c => ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Cannot make request to {:?}. Error is {:?}",
|
||||
requested_url,
|
||||
response_err.to_string()
|
||||
c.to_string()
|
||||
),
|
||||
span,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Error::Transport(t) => {
|
||||
let generic_network_failure = || ShellError::NetworkFailure {
|
||||
msg: t.to_string(),
|
||||
span,
|
||||
};
|
||||
match t.kind() {
|
||||
ErrorKind::ConnectionFailed => ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Cannot make request to {requested_url}, there was an error establishing a connection.",
|
||||
),
|
||||
span,
|
||||
},
|
||||
ErrorKind::Io => 'io: {
|
||||
let Some(source) = t.source() else {
|
||||
break 'io generic_network_failure();
|
||||
};
|
||||
|
||||
let Some(io_error) = source.downcast_ref::<std::io::Error>() else {
|
||||
break 'io generic_network_failure();
|
||||
};
|
||||
|
||||
ShellError::Io(IoError::new(io_error, span, None))
|
||||
}
|
||||
_ => generic_network_failure(),
|
||||
}
|
||||
}
|
||||
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
|
||||
match response_err {
|
||||
Error::ConnectionFailed => ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Cannot make request to {requested_url}, there was an error establishing a connection.",
|
||||
),
|
||||
span,
|
||||
},
|
||||
Error::Timeout(..) => ShellError::Io(IoError::new(
|
||||
ErrorKind::from_std(std::io::ErrorKind::TimedOut),
|
||||
span,
|
||||
None,
|
||||
)),
|
||||
Error::Io(error) => ShellError::Io(IoError::new(error, span, None)),
|
||||
e => ShellError::NetworkFailure {
|
||||
msg: e.to_string(),
|
||||
span,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -743,31 +783,60 @@ fn transform_response_using_content_type(
|
||||
pub fn check_response_redirection(
|
||||
redirect_mode: RedirectMode,
|
||||
span: Span,
|
||||
response: &Result<Response, ShellErrorOrRequestError>,
|
||||
resp: &Response,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Ok(resp) = response {
|
||||
if RedirectMode::Error == redirect_mode && (300..400).contains(&resp.status()) {
|
||||
return Err(ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Redirect encountered when redirect handling mode was 'error' ({} {})",
|
||||
resp.status(),
|
||||
resp.status_text()
|
||||
),
|
||||
span,
|
||||
});
|
||||
}
|
||||
if RedirectMode::Error == redirect_mode && (300..400).contains(&resp.status().as_u16()) {
|
||||
return Err(ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Redirect encountered when redirect handling mode was 'error' ({})",
|
||||
resp.status()
|
||||
),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_handle_response_content(
|
||||
pub(crate) fn handle_response_status(
|
||||
resp: &Response,
|
||||
redirect_mode: RedirectMode,
|
||||
requested_url: &str,
|
||||
span: Span,
|
||||
allow_errors: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
let manual_redirect = redirect_mode == RedirectMode::Manual;
|
||||
|
||||
let is_success = resp.status().is_success()
|
||||
|| allow_errors
|
||||
|| (resp.status().is_redirection() && manual_redirect);
|
||||
if is_success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(handle_status_error(span, requested_url, resp.status()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RequestMetadata<'a> {
|
||||
pub requested_url: &'a str,
|
||||
pub span: Span,
|
||||
pub headers: Headers,
|
||||
pub redirect_mode: RedirectMode,
|
||||
pub flags: RequestFlags,
|
||||
}
|
||||
|
||||
pub(crate) fn request_handle_response(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
requested_url: &str,
|
||||
flags: RequestFlags,
|
||||
RequestMetadata {
|
||||
requested_url,
|
||||
span,
|
||||
headers,
|
||||
redirect_mode,
|
||||
flags,
|
||||
}: RequestMetadata,
|
||||
|
||||
resp: Response,
|
||||
request: Request,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// #response_to_buffer moves "resp" making it impossible to read headers later.
|
||||
// Wrapping it into a closure to call when needed
|
||||
@ -787,11 +856,18 @@ fn request_handle_response_content(
|
||||
None => Ok(response_to_buffer(response, engine_state, span)),
|
||||
}
|
||||
};
|
||||
handle_response_status(
|
||||
&resp,
|
||||
redirect_mode,
|
||||
requested_url,
|
||||
span,
|
||||
flags.allow_errors,
|
||||
)?;
|
||||
|
||||
if flags.full {
|
||||
let response_status = resp.status();
|
||||
|
||||
let request_headers_value = headers_to_nu(&extract_request_headers(&request), span)
|
||||
let request_headers_value = headers_to_nu(&headers, span)
|
||||
.and_then(|data| data.into_value(span))
|
||||
.unwrap_or(Value::nothing(span));
|
||||
|
||||
@ -803,14 +879,23 @@ fn request_handle_response_content(
|
||||
"request" => request_headers_value,
|
||||
"response" => response_headers_value,
|
||||
};
|
||||
|
||||
let urls = Value::list(
|
||||
resp.get_redirect_history()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|v| Value::string(v.to_string(), span))
|
||||
.collect(),
|
||||
span,
|
||||
);
|
||||
let body = consume_response_body(resp)?.into_value(span)?;
|
||||
|
||||
let full_response = Value::record(
|
||||
record! {
|
||||
"urls" => urls,
|
||||
"headers" => Value::record(headers, span),
|
||||
"body" => body,
|
||||
"status" => Value::int(response_status as i64, span),
|
||||
"status" => Value::int(response_status.as_u16().into(), span),
|
||||
|
||||
},
|
||||
span,
|
||||
);
|
||||
@ -821,79 +906,58 @@ fn request_handle_response_content(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_handle_response(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
requested_url: &str,
|
||||
flags: RequestFlags,
|
||||
response: Result<Response, ShellErrorOrRequestError>,
|
||||
request: Request,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match response {
|
||||
Ok(resp) => request_handle_response_content(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
requested_url,
|
||||
flags,
|
||||
resp,
|
||||
request,
|
||||
),
|
||||
Err(e) => match e {
|
||||
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||
ShellErrorOrRequestError::RequestError(_, e) => {
|
||||
if flags.allow_errors {
|
||||
if let Error::Status(_, resp) = *e {
|
||||
Ok(request_handle_response_content(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
requested_url,
|
||||
flags,
|
||||
resp,
|
||||
request,
|
||||
)?)
|
||||
} else {
|
||||
Err(handle_response_error(span, requested_url, *e))
|
||||
}
|
||||
} else {
|
||||
Err(handle_response_error(span, requested_url, *e))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Headers = HashMap<String, Vec<String>>;
|
||||
|
||||
fn extract_request_headers(request: &Request) -> Headers {
|
||||
request
|
||||
.header_names()
|
||||
.iter()
|
||||
fn extract_request_headers<B>(request: &RequestBuilder<B>) -> Option<Headers> {
|
||||
let headers = request.headers_ref()?;
|
||||
let headers_str = headers
|
||||
.keys()
|
||||
.map(|name| {
|
||||
(
|
||||
name.clone(),
|
||||
request.all(name).iter().map(|e| e.to_string()).collect(),
|
||||
name.to_string().clone(),
|
||||
headers
|
||||
.get_all(name)
|
||||
.iter()
|
||||
.filter_map(|v| {
|
||||
v.to_str()
|
||||
.map_err(|e| {
|
||||
error!("Invalid header {name:?}: {e:?}");
|
||||
})
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Some(headers_str)
|
||||
}
|
||||
|
||||
pub(crate) fn extract_response_headers(response: &Response) -> Headers {
|
||||
let header_map = response.headers();
|
||||
header_map
|
||||
.keys()
|
||||
.map(|name| {
|
||||
(
|
||||
name.to_string().clone(),
|
||||
header_map
|
||||
.get_all(name)
|
||||
.iter()
|
||||
.filter_map(|v| {
|
||||
v.to_str()
|
||||
.map_err(|e| {
|
||||
error!("Invalid header {name:?}: {e:?}");
|
||||
})
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn extract_response_headers(response: &Response) -> Headers {
|
||||
response
|
||||
.headers_names()
|
||||
.iter()
|
||||
.map(|name| {
|
||||
(
|
||||
name.clone(),
|
||||
response.all(name).iter().map(|e| e.to_string()).collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn headers_to_nu(headers: &Headers, span: Span) -> Result<PipelineData, ShellError> {
|
||||
pub(crate) fn headers_to_nu(headers: &Headers, span: Span) -> Result<PipelineData, ShellError> {
|
||||
let mut vals = Vec::with_capacity(headers.len());
|
||||
|
||||
for (name, values) in headers {
|
||||
@ -927,18 +991,12 @@ fn headers_to_nu(headers: &Headers, span: Span) -> Result<PipelineData, ShellErr
|
||||
Ok(Value::list(vals, span).into_pipeline_data())
|
||||
}
|
||||
|
||||
pub fn request_handle_response_headers(
|
||||
span: Span,
|
||||
response: Result<Response, ShellErrorOrRequestError>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match response {
|
||||
Ok(resp) => headers_to_nu(&extract_response_headers(&resp), span),
|
||||
Err(e) => match e {
|
||||
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||
ShellErrorOrRequestError::RequestError(requested_url, e) => {
|
||||
Err(handle_response_error(span, &requested_url, *e))
|
||||
}
|
||||
},
|
||||
pub(crate) fn request_error_to_shell_error(span: Span, e: ShellErrorOrRequestError) -> ShellError {
|
||||
match e {
|
||||
ShellErrorOrRequestError::ShellError(e) => e,
|
||||
ShellErrorOrRequestError::RequestError(requested_url, e) => {
|
||||
handle_response_error(span, &requested_url, *e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::network::http::client::{
|
||||
HttpBody, RequestFlags, check_response_redirection, http_client, http_parse_redirect_mode,
|
||||
http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
HttpBody, RequestFlags, RequestMetadata, check_response_redirection, http_client,
|
||||
http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
|
||||
request_add_custom_headers, request_handle_response, request_set_timeout, send_request,
|
||||
send_request_no_body,
|
||||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
@ -148,7 +149,7 @@ impl Command for HttpDelete {
|
||||
struct Arguments {
|
||||
url: Value,
|
||||
headers: Option<Value>,
|
||||
data: HttpBody,
|
||||
data: Option<HttpBody>,
|
||||
content_type: Option<String>,
|
||||
raw: bool,
|
||||
insecure: bool,
|
||||
@ -168,13 +169,13 @@ fn run_delete(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let (data, maybe_metadata) = call
|
||||
.get_flag::<Value>(engine_state, stack, "data")?
|
||||
.map(|v| (HttpBody::Value(v), None))
|
||||
.map(|v| (Some(HttpBody::Value(v)), None))
|
||||
.unwrap_or_else(|| match input {
|
||||
PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata),
|
||||
PipelineData::Value(v, metadata) => (Some(HttpBody::Value(v)), metadata),
|
||||
PipelineData::ByteStream(byte_stream, metadata) => {
|
||||
(HttpBody::ByteStream(byte_stream), metadata)
|
||||
(Some(HttpBody::ByteStream(byte_stream)), metadata)
|
||||
}
|
||||
_ => (HttpBody::None, None),
|
||||
_ => (None, None),
|
||||
});
|
||||
let content_type = call
|
||||
.get_flag(engine_state, stack, "content-type")?
|
||||
@ -216,31 +217,43 @@ fn helper(
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
let (response, request_headers) = match args.data {
|
||||
None => send_request_no_body(request, call.head, engine_state.signals()),
|
||||
|
||||
let response = send_request(
|
||||
engine_state,
|
||||
request.clone(),
|
||||
args.data,
|
||||
args.content_type,
|
||||
call.head,
|
||||
engine_state.signals(),
|
||||
);
|
||||
Some(body) => send_request(
|
||||
engine_state,
|
||||
// Nushell allows sending body via delete method, but not via get.
|
||||
// We should probably unify the behaviour here.
|
||||
//
|
||||
// Sending body with DELETE goes against the spec, but might be useful in some cases,
|
||||
// see [force_send_body] documentation.
|
||||
request.force_send_body(),
|
||||
body,
|
||||
args.content_type,
|
||||
span,
|
||||
engine_state.signals(),
|
||||
),
|
||||
};
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
let response = response?;
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
request_flags,
|
||||
RequestMetadata {
|
||||
requested_url: &requested_url,
|
||||
span,
|
||||
headers: request_headers,
|
||||
redirect_mode,
|
||||
flags: request_flags,
|
||||
},
|
||||
response,
|
||||
request,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
use crate::network::http::client::{
|
||||
RequestFlags, check_response_redirection, http_client, http_parse_redirect_mode,
|
||||
http_parse_url, request_add_authorization_header, request_add_custom_headers,
|
||||
request_handle_response, request_set_timeout, send_request,
|
||||
RequestFlags, RequestMetadata, check_response_redirection, http_client,
|
||||
http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
|
||||
request_add_custom_headers, request_handle_response, request_set_timeout, send_request_no_body,
|
||||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use super::client::HttpBody;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpGet;
|
||||
|
||||
@ -180,15 +178,8 @@ fn helper(
|
||||
request = request_set_timeout(args.timeout, request)?;
|
||||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(
|
||||
engine_state,
|
||||
request.clone(),
|
||||
HttpBody::None,
|
||||
None,
|
||||
call.head,
|
||||
engine_state.signals(),
|
||||
);
|
||||
let (response, request_headers) =
|
||||
send_request_no_body(request, call.head, engine_state.signals());
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
@ -196,15 +187,20 @@ fn helper(
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
let response = response?;
|
||||
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
request_flags,
|
||||
RequestMetadata {
|
||||
requested_url: &requested_url,
|
||||
span,
|
||||
headers: request_headers,
|
||||
redirect_mode,
|
||||
flags: request_flags,
|
||||
},
|
||||
response,
|
||||
request,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
use super::client::HttpBody;
|
||||
use crate::network::http::client::{
|
||||
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
|
||||
request_add_authorization_header, request_add_custom_headers, request_handle_response_headers,
|
||||
request_set_timeout, send_request,
|
||||
check_response_redirection, extract_response_headers, handle_response_status, headers_to_nu,
|
||||
http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
|
||||
request_add_custom_headers, request_set_timeout, send_request_no_body,
|
||||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Signals;
|
||||
@ -140,7 +139,6 @@ fn run_head(
|
||||
}
|
||||
|
||||
// Helper function that actually goes to retrieve the resource from the url given
|
||||
// The Option<String> return a possible file extension which can be used in AutoConvert commands
|
||||
fn helper(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -159,16 +157,11 @@ fn helper(
|
||||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(
|
||||
engine_state,
|
||||
request,
|
||||
HttpBody::None,
|
||||
None,
|
||||
call.head,
|
||||
signals,
|
||||
);
|
||||
let (response, _request_headers) = send_request_no_body(request, call.head, signals);
|
||||
let response = response?;
|
||||
check_response_redirection(redirect_mode, span, &response)?;
|
||||
request_handle_response_headers(span, response)
|
||||
handle_response_status(&response, redirect_mode, &requested_url, span, false)?;
|
||||
headers_to_nu(&extract_response_headers(&response), span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user