mirror of
https://github.com/nushell/nushell.git
synced 2025-08-15 00:42:30 +02:00
Compare commits
1 Commits
main
...
revert-159
Author | SHA1 | Date | |
---|---|---|---|
4f67310681 |
42
.github/pull_request_template.md
vendored
42
.github/pull_request_template.md
vendored
@ -1,16 +1,40 @@
|
||||
<!--
|
||||
Thank you for improving Nushell!
|
||||
Please, read our contributing guide: https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md
|
||||
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
|
||||
|
||||
Use the following space to include the motivation and any technical details behind this PR.
|
||||
you can also mention related issues, PRs or discussions!
|
||||
-->
|
||||
|
||||
## Release notes summary - What our users need to know
|
||||
# Description
|
||||
<!--
|
||||
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!
|
||||
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.
|
||||
-->
|
||||
|
||||
## 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)
|
||||
# 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. -->
|
||||
|
25
.github/workflows/friendly-config-reminder.yml
vendored
25
.github/workflows/friendly-config-reminder.yml
vendored
@ -1,25 +0,0 @@
|
||||
name: Comment on changes to the config
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'crates/nu-protocol/src/config/**'
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if there is already a bot comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Hey, just a bot checking in!
|
||||
- name: Create comment if there is not
|
||||
if: steps.fc.outputs.comment-id == ''
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Hey, just a bot checking in! You edited files related to the configuration.
|
||||
If you changed any of the default values or added a new config option, don't forget to update the [`doc_config.nu`](https://github.com/nushell/nushell/blob/main/crates/nu-utils/src/default_files/doc_config.nu) which documents the options for our users including the defaults provided by the Rust implementation.
|
||||
If you didn't make a change here, you can just ignore me.
|
27
.github/workflows/nightly-build.yml
vendored
27
.github/workflows/nightly-build.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.105.1
|
||||
version: 0.103.0
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
@ -127,7 +127,6 @@ jobs:
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
@ -153,8 +152,6 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
@ -182,22 +179,36 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
with:
|
||||
version: 0.105.1
|
||||
version: 0.103.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build Nu for Windows ARM64
|
||||
id: nu0
|
||||
shell: pwsh
|
||||
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||
run: |
|
||||
$env:OS = 'windows'
|
||||
$env:REF = '${{ github.ref }}'
|
||||
$env:TARGET = '${{ matrix.target }}'
|
||||
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||
./nu.exe -c 'version'
|
||||
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||
|
||||
- name: Create an Issue for Release Failure
|
||||
if: ${{ failure() }}
|
||||
uses: JasonEtco/create-an-issue@v2
|
||||
@ -217,7 +228,9 @@ jobs:
|
||||
prerelease: true
|
||||
files: |
|
||||
${{ steps.nu.outputs.msi }}
|
||||
${{ steps.nu0.outputs.msi }}
|
||||
${{ steps.nu.outputs.archive }}
|
||||
${{ steps.nu0.outputs.archive }}
|
||||
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
||||
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
|
||||
env:
|
||||
@ -263,7 +276,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.105.1
|
||||
version: 0.103.0
|
||||
|
||||
# Keep the last a few releases
|
||||
- name: Delete Older Releases
|
||||
|
44
.github/workflows/pre-release-checkup.yml
vendored
44
.github/workflows/pre-release-checkup.yml
vendored
@ -1,44 +0,0 @@
|
||||
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/release-msi.yml
vendored
2
.github/workflows/release-msi.yml
vendored
@ -58,7 +58,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.105.1
|
||||
version: nightly
|
||||
|
||||
- name: Release MSI Packages
|
||||
id: nu
|
||||
|
8
.github/workflows/release-pkg.nu
vendored
8
.github/workflows/release-pkg.nu
vendored
@ -99,14 +99,6 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
'loongarch64-unknown-linux-musl' => {
|
||||
print $"(ansi g)Downloading LoongArch64 musl cross-compilation toolchain...(ansi reset)"
|
||||
aria2c -q https://github.com/LoongsonLab/oscomp-toolchains-for-oskernel/releases/download/loongarch64-linux-musl-cross-gcc-13.2.0/loongarch64-linux-musl-cross.tgz
|
||||
tar -xf loongarch64-linux-musl-cross.tgz
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.PWD)/loongarch64-linux-musl-cross/bin')
|
||||
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_MUSL_LINKER = "loongarch64-linux-musl-gcc"
|
||||
cargo-build-nu
|
||||
}
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -35,7 +35,6 @@ jobs:
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
@ -61,8 +60,6 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
@ -93,17 +90,32 @@ jobs:
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
with:
|
||||
version: 0.105.1
|
||||
version: 0.103.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build Nu for Windows ARM64
|
||||
id: nu0
|
||||
shell: pwsh
|
||||
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||
run: |
|
||||
$env:OS = 'windows'
|
||||
$env:REF = '${{ github.ref }}'
|
||||
$env:TARGET = '${{ matrix.target }}'
|
||||
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||
./nu.exe -c 'version'
|
||||
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||
|
||||
# WARN: Don't upgrade this action due to the release per asset issue.
|
||||
# See: https://github.com/softprops/action-gh-release/issues/445
|
||||
- name: Publish Archive
|
||||
@ -113,7 +125,9 @@ jobs:
|
||||
draft: true
|
||||
files: |
|
||||
${{ steps.nu.outputs.msi }}
|
||||
${{ steps.nu0.outputs.msi }}
|
||||
${{ steps.nu.outputs.archive }}
|
||||
${{ steps.nu0.outputs.archive }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
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.35.4
|
||||
uses: crate-ci/typos@v1.33.1
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -32,17 +32,11 @@ unstable_cargo_features.txt
|
||||
# Helix configuration folder
|
||||
.helix/*
|
||||
.helix
|
||||
wix/bin/
|
||||
wix/obj/
|
||||
wix/nu/
|
||||
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
||||
# benchmarking
|
||||
/tango
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
*.rsproj
|
||||
|
@ -3,7 +3,6 @@
|
||||
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)
|
||||
@ -21,51 +20,6 @@ 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.
|
||||
|
802
Cargo.lock
generated
802
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
125
Cargo.toml
125
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.87.0"
|
||||
version = "0.106.2"
|
||||
rust-version = "1.85.1"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -24,37 +24,36 @@ pkg-fmt = "zip"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_formats",
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_inc",
|
||||
"crates/nu_plugin_polars",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_stress_internals",
|
||||
"crates/nu-cli",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-cmd-base",
|
||||
"crates/nu-cmd-extra",
|
||||
"crates/nu-cmd-lang",
|
||||
"crates/nu-cmd-plugin",
|
||||
"crates/nu-color-config",
|
||||
"crates/nu-command",
|
||||
"crates/nu-derive-value",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-experimental",
|
||||
"crates/nu-color-config",
|
||||
"crates/nu-explore",
|
||||
"crates/nu-json",
|
||||
"crates/nu-lsp",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-pretty-hex",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-derive-value",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu-plugin-core",
|
||||
"crates/nu-plugin-engine",
|
||||
"crates/nu-plugin-protocol",
|
||||
"crates/nu-plugin-test-support",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu-pretty-hex",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu_plugin_inc",
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu_plugin_formats",
|
||||
"crates/nu_plugin_polars",
|
||||
"crates/nu_plugin_stress_internals",
|
||||
"crates/nu-std",
|
||||
"crates/nu-system",
|
||||
"crates/nu-table",
|
||||
"crates/nu-term-grid",
|
||||
"crates/nu-test-support",
|
||||
@ -72,7 +71,7 @@ brotli = "7.0"
|
||||
byteorder = "1.5"
|
||||
bytes = "1"
|
||||
bytesize = "1.3.3"
|
||||
calamine = "0.28"
|
||||
calamine = "0.26"
|
||||
chardetng = "0.1.17"
|
||||
chrono = { default-features = false, version = "0.4.34" }
|
||||
chrono-humanize = "0.2.3"
|
||||
@ -83,19 +82,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.16"
|
||||
fancy-regex = "0.14"
|
||||
filesize = "0.2"
|
||||
filetime = "0.2"
|
||||
heck = "0.5.0"
|
||||
http = "1.3.1"
|
||||
human-date-parser = "0.3.0"
|
||||
indexmap = "2.10"
|
||||
indexmap = "2.9"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.2.0"
|
||||
is_executable = "1.0"
|
||||
@ -134,7 +131,7 @@ proc-macro-error2 = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
procfs = "0.17.0"
|
||||
pwd = "1.3"
|
||||
quick-xml = "0.37.5"
|
||||
quick-xml = "0.37.0"
|
||||
quickcheck = "1.0"
|
||||
quickcheck_macros = "1.1"
|
||||
quote = "1.0"
|
||||
@ -142,8 +139,8 @@ rand = "0.9"
|
||||
getrandom = "0.2" # pick same version that rand requires
|
||||
rand_chacha = "0.9"
|
||||
ratatui = "0.29"
|
||||
rayon = "1.11"
|
||||
reedline = "0.41.0"
|
||||
rayon = "1.10"
|
||||
reedline = "0.40.0"
|
||||
rmp = "0.8"
|
||||
rmp-serde = "1.3"
|
||||
roxmltree = "0.20"
|
||||
@ -151,10 +148,7 @@ rstest = { version = "0.23", default-features = false }
|
||||
rstest_reuse = "0.7"
|
||||
rusqlite = "0.31"
|
||||
rust-embed = "8.7.0"
|
||||
# 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 = { version = "0.23", default-features = false, features = ["std", "tls12"] }
|
||||
rustls-native-certs = "0.8"
|
||||
scopeguard = { version = "1.2.0" }
|
||||
serde = { version = "1.0" }
|
||||
@ -162,22 +156,21 @@ serde_json = "1.0.97"
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde_yaml = "0.9.33"
|
||||
sha2 = "0.10"
|
||||
strip-ansi-escapes = "0.2.1"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
syn = "2.0"
|
||||
sysinfo = "0.36"
|
||||
sysinfo = "0.33"
|
||||
tabled = { version = "0.20", default-features = false }
|
||||
tempfile = "3.20"
|
||||
thiserror = "2.0.12"
|
||||
titlecase = "3.6"
|
||||
toml = "0.8"
|
||||
trash = "5.2"
|
||||
update-informer = { version = "1.3.0", default-features = false, features = ["github", "ureq"] }
|
||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
|
||||
umask = "2.1"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-width = "0.2"
|
||||
ureq = { version = "=3.0.12", default-features = false, features = ["socks-proxy"] }
|
||||
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
|
||||
url = "2.2"
|
||||
uu_cp = "0.0.30"
|
||||
uu_mkdir = "0.0.30"
|
||||
@ -191,7 +184,7 @@ uuid = "1.16.0"
|
||||
v_htmlescape = "0.15.0"
|
||||
wax = "0.6"
|
||||
web-time = "1.1.0"
|
||||
which = "8.0.0"
|
||||
which = "7.0.3"
|
||||
windows = "0.56"
|
||||
windows-sys = "0.48"
|
||||
winreg = "0.52"
|
||||
@ -202,30 +195,27 @@ webpki-roots = "1.0"
|
||||
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||
# 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.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" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.2", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.105.2" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.2" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.2" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.105.2" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.105.2" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.105.2" }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
@ -254,9 +244,9 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
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" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.2" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.2" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.2" }
|
||||
assert_cmd = "2.0"
|
||||
dirs = { workspace = true }
|
||||
tango-bench = "0.6"
|
||||
@ -267,14 +257,10 @@ serial_test = "3.2"
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
# Enable all features while still avoiding mutually exclusive features.
|
||||
# Use this if `--all-features` fails.
|
||||
full = ["plugin", "rustls-tls", "system-clipboard", "trash-support", "sqlite"]
|
||||
|
||||
plugin = [
|
||||
# crates
|
||||
"dep:nu-cmd-plugin",
|
||||
"dep:nu-plugin-engine",
|
||||
"nu-cmd-plugin",
|
||||
"nu-plugin-engine",
|
||||
|
||||
# features
|
||||
"nu-cli/plugin",
|
||||
@ -300,20 +286,21 @@ stable = ["default"]
|
||||
|
||||
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
|
||||
# otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
||||
|
||||
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
||||
# Missing X server/ Wayland can cause issues
|
||||
system-clipboard = [
|
||||
"reedline/system_clipboard",
|
||||
"nu-cli/system-clipboard",
|
||||
"nu-cmd-lang/system-clipboard",
|
||||
]
|
||||
|
||||
# Stable (Default)
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||
|
||||
# SQLite commands for nushell
|
||||
sqlite = ["nu-command/sqlite", "nu-std/sqlite"]
|
||||
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s" # Optimize for size
|
||||
@ -343,7 +330,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?
|
||||
|
@ -199,7 +199,7 @@ fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
|
||||
let nested_access = ".col".repeat(n);
|
||||
bench_command(
|
||||
format!("record_nested_access_{n}"),
|
||||
format!("$record{nested_access} | ignore"),
|
||||
format!("$record{} | ignore", nested_access),
|
||||
stack,
|
||||
engine,
|
||||
)
|
||||
@ -319,7 +319,7 @@ fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks {
|
||||
let stack = Stack::new();
|
||||
bench_command(
|
||||
format!("eval_par_each_{n}"),
|
||||
format!("(1..{n}) | par-each -t 2 {{|_| 1 }} | ignore"),
|
||||
format!("(1..{}) | par-each -t 2 {{|_| 1 }} | ignore", n),
|
||||
stack,
|
||||
engine,
|
||||
)
|
||||
@ -357,7 +357,7 @@ fn encode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
let encoder = Rc::new(EncodingType::try_from_bytes(b"json").unwrap());
|
||||
|
||||
[benchmark_fn(
|
||||
format!("encode_json_{row_cnt}_{col_cnt}"),
|
||||
format!("encode_json_{}_{}", row_cnt, col_cnt),
|
||||
move |b| {
|
||||
let encoder = encoder.clone();
|
||||
let test_data = test_data.clone();
|
||||
@ -377,7 +377,7 @@ fn encode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
let encoder = Rc::new(EncodingType::try_from_bytes(b"msgpack").unwrap());
|
||||
|
||||
[benchmark_fn(
|
||||
format!("encode_msgpack_{row_cnt}_{col_cnt}"),
|
||||
format!("encode_msgpack_{}_{}", row_cnt, col_cnt),
|
||||
move |b| {
|
||||
let encoder = encoder.clone();
|
||||
let test_data = test_data.clone();
|
||||
@ -399,7 +399,7 @@ fn decode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
encoder.encode(&test_data, &mut res).unwrap();
|
||||
|
||||
[benchmark_fn(
|
||||
format!("decode_json_{row_cnt}_{col_cnt}"),
|
||||
format!("decode_json_{}_{}", row_cnt, col_cnt),
|
||||
move |b| {
|
||||
let res = res.clone();
|
||||
b.iter(move || {
|
||||
@ -422,7 +422,7 @@ fn decode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
encoder.encode(&test_data, &mut res).unwrap();
|
||||
|
||||
[benchmark_fn(
|
||||
format!("decode_msgpack_{row_cnt}_{col_cnt}"),
|
||||
format!("decode_msgpack_{}_{}", row_cnt, col_cnt),
|
||||
move |b| {
|
||||
let res = res.clone();
|
||||
b.iter(move || {
|
||||
|
@ -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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
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" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||
nu-std = { path = "../nu-std", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
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-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", features = ["os"] }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["os"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -57,7 +57,7 @@ Note that history item IDs are ignored when importing from file."#
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "[[ command cwd ]; [ foo /home ]] | history import",
|
||||
example: "[[ command_line cwd ]; [ foo /home ]] | history import",
|
||||
description: "Append `foo` ran from `/home` to the current history",
|
||||
result: None,
|
||||
},
|
||||
|
@ -118,7 +118,7 @@ fn get_suggestions_by_value(
|
||||
|| s.chars()
|
||||
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
||||
{
|
||||
format!("{s:?}")
|
||||
format!("{:?}", s)
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ impl CommandCompletion {
|
||||
continue;
|
||||
};
|
||||
let value = if matched_internal(&name) {
|
||||
format!("^{name}")
|
||||
format!("^{}", name)
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
|
@ -176,7 +176,7 @@ impl NuCompleter {
|
||||
&mut working_set,
|
||||
Some("completer"),
|
||||
// Add a placeholder `a` to the end
|
||||
format!("{line}a").as_bytes(),
|
||||
format!("{}a", line).as_bytes(),
|
||||
false,
|
||||
);
|
||||
self.fetch_completions_by_block(block, &working_set, pos, offset, line, true)
|
||||
@ -348,43 +348,8 @@ impl NuCompleter {
|
||||
for (arg_idx, arg) in call.arguments.iter().enumerate() {
|
||||
let span = arg.span();
|
||||
if span.contains(pos) {
|
||||
// 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 {
|
||||
// if customized completion specified, it has highest priority
|
||||
if let Some(decl_id) = arg.expr().and_then(|e| e.custom_completion) {
|
||||
// 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(
|
||||
@ -405,8 +370,7 @@ impl NuCompleter {
|
||||
FileCompletion,
|
||||
);
|
||||
|
||||
// Prioritize argument completions over (sub)commands
|
||||
suggestions.splice(0..0, self.process_completion(&mut completer, &ctx));
|
||||
suggestions.extend(self.process_completion(&mut completer, &ctx));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -420,39 +384,33 @@ impl NuCompleter {
|
||||
};
|
||||
self.process_completion(&mut flag_completions, &ctx)
|
||||
};
|
||||
// Prioritize argument completions over (sub)commands
|
||||
suggestions.splice(
|
||||
0..0,
|
||||
match arg {
|
||||
// flags
|
||||
Argument::Named(_) | Argument::Unknown(_)
|
||||
if prefix.starts_with(b"-") =>
|
||||
{
|
||||
flag_completion_helper()
|
||||
}
|
||||
// only when `strip` == false
|
||||
Argument::Positional(_) if prefix == b"-" => {
|
||||
flag_completion_helper()
|
||||
}
|
||||
// complete according to expression type and command head
|
||||
Argument::Positional(expr) => {
|
||||
let command_head = working_set.get_decl(call.decl_id).name();
|
||||
positional_arg_indices.push(arg_idx);
|
||||
self.argument_completion_helper(
|
||||
PositionalArguments {
|
||||
command_head,
|
||||
positional_arg_indices,
|
||||
arguments: &call.arguments,
|
||||
expr,
|
||||
},
|
||||
pos,
|
||||
&ctx,
|
||||
suggestions.is_empty(),
|
||||
)
|
||||
}
|
||||
_ => vec![],
|
||||
},
|
||||
);
|
||||
suggestions.extend(match arg {
|
||||
// flags
|
||||
Argument::Named(_) | Argument::Unknown(_)
|
||||
if prefix.starts_with(b"-") =>
|
||||
{
|
||||
flag_completion_helper()
|
||||
}
|
||||
// only when `strip` == false
|
||||
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(),
|
||||
// complete according to expression type and command head
|
||||
Argument::Positional(expr) => {
|
||||
let command_head = working_set.get_decl(call.decl_id).name();
|
||||
positional_arg_indices.push(arg_idx);
|
||||
self.argument_completion_helper(
|
||||
PositionalArguments {
|
||||
command_head,
|
||||
positional_arg_indices,
|
||||
arguments: &call.arguments,
|
||||
expr,
|
||||
},
|
||||
pos,
|
||||
&ctx,
|
||||
suggestions.is_empty(),
|
||||
)
|
||||
}
|
||||
_ => vec![],
|
||||
});
|
||||
break;
|
||||
} else if !matches!(arg, Argument::Named(_)) {
|
||||
positional_arg_indices.push(arg_idx);
|
||||
@ -504,18 +462,10 @@ impl NuCompleter {
|
||||
if let Some(external_result) =
|
||||
self.external_completion(closure, &text_spans, offset, new_span)
|
||||
{
|
||||
// Prioritize external results over (sub)commands
|
||||
suggestions.splice(0..0, external_result);
|
||||
suggestions.extend(external_result);
|
||||
return suggestions;
|
||||
}
|
||||
}
|
||||
// for external path arguments with spaces, please check issue #15790
|
||||
if suggestions.is_empty() {
|
||||
let (new_span, prefix) =
|
||||
strip_placeholder_if_any(working_set, &span, strip);
|
||||
let ctx = Context::new(working_set, new_span, prefix, offset);
|
||||
return self.process_completion(&mut FileCompletion, &ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -753,7 +703,7 @@ impl NuCompleter {
|
||||
Ok(value) => {
|
||||
log::error!(
|
||||
"External completer returned invalid value of type {}",
|
||||
value.get_type()
|
||||
value.get_type().to_string()
|
||||
);
|
||||
Some(vec![])
|
||||
}
|
||||
@ -892,7 +842,7 @@ mod completer_tests {
|
||||
for (line, has_result, begins_with, expected_values) in dataset {
|
||||
let result = completer.fetch_completions_at(line, line.len());
|
||||
// Test whether the result is empty or not
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {line}");
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
|
||||
|
||||
// Test whether the result begins with the expected value
|
||||
result
|
||||
@ -907,7 +857,8 @@ mod completer_tests {
|
||||
.filter(|x| *x)
|
||||
.count(),
|
||||
expected_values.len(),
|
||||
"line: {line}"
|
||||
"line: {}",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ pub fn escape_path(path: String) -> String {
|
||||
if path.contains('\'') {
|
||||
// decide to use double quotes
|
||||
// Path as Debug will do the escaping for `"`, `\`
|
||||
format!("{path:?}")
|
||||
format!("{:?}", path)
|
||||
} else {
|
||||
format!("'{path}'")
|
||||
}
|
||||
|
@ -102,11 +102,7 @@ impl<T> NuMatcher<'_, T> {
|
||||
options,
|
||||
needle: needle.to_owned(),
|
||||
state: State::Fuzzy {
|
||||
matcher: Matcher::new({
|
||||
let mut cfg = Config::DEFAULT;
|
||||
cfg.prefer_prefix = true;
|
||||
cfg
|
||||
}),
|
||||
matcher: Matcher::new(Config::DEFAULT),
|
||||
atom,
|
||||
items: Vec::new(),
|
||||
},
|
||||
|
@ -140,7 +140,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
||||
_ => {
|
||||
log::error!(
|
||||
"Custom completer returned invalid value of type {}",
|
||||
value.get_type()
|
||||
value.get_type().to_string()
|
||||
);
|
||||
return vec![];
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ impl Completer for DotNuCompletion {
|
||||
.take_while(|c| "`'\"".contains(*c))
|
||||
.collect::<String>();
|
||||
for path in ["std", "std-rfc"] {
|
||||
let path = format!("{surround_prefix}{path}");
|
||||
let path = format!("{}{}", surround_prefix, path);
|
||||
matcher.add(
|
||||
path.clone(),
|
||||
FileSuggestion {
|
||||
@ -146,7 +146,7 @@ impl Completer for DotNuCompletion {
|
||||
for sub_vp_id in sub_paths {
|
||||
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
||||
let path = path
|
||||
.strip_prefix(&format!("{base_dir}/"))
|
||||
.strip_prefix(&format!("{}/", base_dir))
|
||||
.unwrap_or(path)
|
||||
.to_string();
|
||||
matcher.add(
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -3,9 +3,9 @@ use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
PipelineData, ShellError, Spanned, Value,
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error::report_compile_error,
|
||||
report_parse_error, report_parse_warning,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
@ -5,9 +5,9 @@ use nu_parser::parse;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
PipelineData, ShellError, Span, Value,
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error::report_compile_error,
|
||||
report_parse_error, report_parse_warning,
|
||||
shell_error::io::*,
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::documentation::{FormatterValue, HelpStyle, get_flags_section};
|
||||
use nu_engine::documentation::{HelpStyle, get_flags_section};
|
||||
use nu_protocol::{Config, engine::EngineState, levenshtein_distance};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use reedline::{Completer, Suggestion};
|
||||
@ -66,11 +66,8 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| match v {
|
||||
FormatterValue::DefaultValue(value) => {
|
||||
value.to_parsable_string(", ", &self.config)
|
||||
}
|
||||
FormatterValue::CodeString(text) => text.to_string(),
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
||||
v.to_parsable_string(", ", &self.config)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ use std::sync::Arc;
|
||||
use nu_engine::command_prelude::*;
|
||||
use reedline::{Highlighter, StyledText};
|
||||
|
||||
use crate::syntax_highlight::highlight_syntax;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuHighlight;
|
||||
|
||||
@ -16,11 +14,6 @@ impl Command for NuHighlight {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight")
|
||||
.category(Category::Strings)
|
||||
.switch(
|
||||
"reject-garbage",
|
||||
"Return an error if invalid syntax (garbage) was encountered",
|
||||
Some('r'),
|
||||
)
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
}
|
||||
|
||||
@ -39,33 +32,19 @@ impl Command for NuHighlight {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let reject_garbage = call.has_flag(engine_state, stack, "reject-garbage")?;
|
||||
let head = call.head;
|
||||
|
||||
let signals = engine_state.signals();
|
||||
|
||||
let engine_state = Arc::new(engine_state.clone());
|
||||
let stack = Arc::new(stack.clone());
|
||||
let highlighter = crate::NuHighlighter {
|
||||
engine_state: Arc::new(engine_state.clone()),
|
||||
stack: Arc::new(stack.clone()),
|
||||
};
|
||||
|
||||
input.map(
|
||||
move |x| match x.coerce_into_string() {
|
||||
Ok(line) => {
|
||||
let result = highlight_syntax(&engine_state, &stack, &line, line.len());
|
||||
|
||||
let highlights = match (reject_garbage, result.found_garbage) {
|
||||
(false, _) => result.text,
|
||||
(true, None) => result.text,
|
||||
(true, Some(span)) => {
|
||||
let error = ShellError::OutsideSpannedLabeledError {
|
||||
src: line,
|
||||
error: "encountered invalid syntax while highlighting".into(),
|
||||
msg: "invalid syntax".into(),
|
||||
span,
|
||||
};
|
||||
return Value::error(error, head);
|
||||
}
|
||||
};
|
||||
|
||||
let highlights = highlighter.highlight(&line, line.len());
|
||||
Value::string(highlights.render_simple(), head)
|
||||
}
|
||||
Err(err) => Value::error(err, head),
|
||||
|
@ -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,10 +1047,6 @@ 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(),
|
||||
@ -1176,7 +1172,6 @@ fn edit_from_record(
|
||||
"cutfromlinestart" => EditCommand::CutFromLineStart,
|
||||
"cuttoend" => EditCommand::CutToEnd,
|
||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||
"killline" => EditCommand::KillLine,
|
||||
"cutwordleft" => EditCommand::CutWordLeft,
|
||||
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||
"cutwordright" => EditCommand::CutWordRight,
|
||||
|
@ -22,8 +22,8 @@ use nu_color_config::StyleComputer;
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_engine::exit::cleanup_exit;
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::shell_error;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use nu_protocol::{BannerKind, shell_error};
|
||||
use nu_protocol::{
|
||||
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
|
||||
config::NuCursorShape,
|
||||
@ -145,8 +145,8 @@ pub fn evaluate_repl(
|
||||
|
||||
if load_std_lib.is_none() {
|
||||
match engine_state.get_config().show_banner {
|
||||
BannerKind::None => {}
|
||||
BannerKind::Short => {
|
||||
Value::Bool { val: false, .. } => {}
|
||||
Value::String { ref val, .. } if val == "short" => {
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
@ -156,7 +156,7 @@ pub fn evaluate_repl(
|
||||
false,
|
||||
);
|
||||
}
|
||||
BannerKind::Full => {
|
||||
_ => {
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
@ -239,7 +239,7 @@ fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
|
||||
|
||||
match byte {
|
||||
// Escape bytes below 0x20
|
||||
b if b < 0x20 => format!("\\x{byte:02X}").into_bytes(),
|
||||
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
|
||||
// Escape semicolon as \x3B
|
||||
b';' => "\\x3B".to_string().into_bytes(),
|
||||
// Escape backslash as \\
|
||||
@ -325,19 +325,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
perf!("reset signals", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// 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
|
||||
// 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,
|
||||
@ -349,6 +337,18 @@ 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);
|
||||
|
||||
@ -1097,7 +1097,8 @@ fn run_shell_integration_osc633(
|
||||
// If we're in vscode, run their specific ansi escape sequence.
|
||||
// This is helpful for ctrl+g to change directories in the terminal.
|
||||
run_ansi_sequence(&format!(
|
||||
"{VSCODE_CWD_PROPERTY_MARKER_PREFIX}{path}{VSCODE_CWD_PROPERTY_MARKER_SUFFIX}"
|
||||
"{}{}{}",
|
||||
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
|
||||
));
|
||||
|
||||
perf!(
|
||||
@ -1113,7 +1114,10 @@ fn run_shell_integration_osc633(
|
||||
|
||||
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
||||
run_ansi_sequence(&format!(
|
||||
"{VSCODE_COMMANDLINE_MARKER_PREFIX}{replaced_cmd_text}{VSCODE_COMMANDLINE_MARKER_SUFFIX}"
|
||||
"{}{}{}",
|
||||
VSCODE_COMMANDLINE_MARKER_PREFIX,
|
||||
replaced_cmd_text,
|
||||
VSCODE_COMMANDLINE_MARKER_SUFFIX
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1489,7 +1493,7 @@ mod test_auto_cd {
|
||||
// Parse the input. It must be an auto-cd operation.
|
||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||
let ReplOperation::AutoCd { cwd, target, span } = op else {
|
||||
panic!("'{input}' was not parsed into an auto-cd operation")
|
||||
panic!("'{}' was not parsed into an auto-cd operation", input)
|
||||
};
|
||||
|
||||
// Perform the auto-cd operation.
|
||||
|
@ -17,173 +17,147 @@ pub struct NuHighlighter {
|
||||
}
|
||||
|
||||
impl Highlighter for NuHighlighter {
|
||||
fn highlight(&self, line: &str, cursor: usize) -> StyledText {
|
||||
let result = highlight_syntax(&self.engine_state, &self.stack, line, cursor);
|
||||
result.text
|
||||
}
|
||||
}
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
/// Result of a syntax highlight operation
|
||||
#[derive(Default)]
|
||||
pub(crate) struct HighlightResult {
|
||||
/// The highlighted text
|
||||
pub(crate) text: StyledText,
|
||||
/// The span of any garbage that was highlighted
|
||||
pub(crate) found_garbage: Option<Span>,
|
||||
}
|
||||
let config = self.stack.get_config(&self.engine_state);
|
||||
let highlight_resolved_externals = config.highlight_resolved_externals;
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut shapes = flatten_block(&working_set, &block);
|
||||
// Highlighting externals has a config point because of concerns that using which to resolve
|
||||
// externals may slow down things too much.
|
||||
if highlight_resolved_externals {
|
||||
for (span, shape) in shapes.iter_mut() {
|
||||
if *shape == FlatShape::External {
|
||||
let str_contents =
|
||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||
|
||||
pub(crate) fn highlight_syntax(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
line: &str,
|
||||
cursor: usize,
|
||||
) -> HighlightResult {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
let config = stack.get_config(engine_state);
|
||||
let highlight_resolved_externals = config.highlight_resolved_externals;
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut shapes = flatten_block(&working_set, &block);
|
||||
// Highlighting externals has a config point because of concerns that using which to resolve
|
||||
// externals may slow down things too much.
|
||||
if highlight_resolved_externals {
|
||||
for (span, shape) in shapes.iter_mut() {
|
||||
if *shape == FlatShape::External {
|
||||
let str_contents =
|
||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||
|
||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||
let paths = env::path_str(engine_state, stack, *span).ok();
|
||||
let res = if let Ok(cwd) = engine_state.cwd(Some(stack)) {
|
||||
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||
} else {
|
||||
which::which_in_global(str_word, paths.as_ref())
|
||||
.ok()
|
||||
.and_then(|mut i| i.next())
|
||||
};
|
||||
if res.is_some() {
|
||||
*shape = FlatShape::ExternalResolved;
|
||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
|
||||
#[allow(deprecated)]
|
||||
let res = if let Ok(cwd) =
|
||||
env::current_dir_str(&self.engine_state, &self.stack)
|
||||
{
|
||||
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||
} else {
|
||||
which::which_in_global(str_word, paths.as_ref())
|
||||
.ok()
|
||||
.and_then(|mut i| i.next())
|
||||
};
|
||||
if res.is_some() {
|
||||
*shape = FlatShape::ExternalResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(shapes, engine_state.next_span_start())
|
||||
};
|
||||
|
||||
let mut result = HighlightResult::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
let global_cursor_offset = cursor + global_span_offset;
|
||||
let matching_brackets_pos = find_matching_brackets(
|
||||
line,
|
||||
&working_set,
|
||||
&block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
);
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
result.text.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
result
|
||||
.text
|
||||
.push((get_shape_color(shape.as_str(), &config), text));
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
};
|
||||
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => {
|
||||
result.found_garbage.get_or_insert_with(|| {
|
||||
Span::new(
|
||||
shape.0.start - global_span_offset,
|
||||
shape.0.end - global_span_offset,
|
||||
)
|
||||
});
|
||||
add_colored_token(&shape.1, next_token)
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
let global_cursor_offset = _cursor + global_span_offset;
|
||||
let matching_brackets_pos = find_matching_brackets(
|
||||
line,
|
||||
&working_set,
|
||||
&block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
);
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::RawString => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List
|
||||
| FlatShape::Table
|
||||
| FlatShape::Record
|
||||
| FlatShape::Block
|
||||
| FlatShape::Closure => {
|
||||
let span = shape.0;
|
||||
let shape = &shape.1;
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
for (part, highlight) in spans {
|
||||
let start = part.start - span.start;
|
||||
let end = part.end - span.start;
|
||||
let text = next_token[start..end].to_string();
|
||||
let mut style = get_shape_color(shape.as_str(), &config);
|
||||
if highlight {
|
||||
style = get_matching_brackets_style(style, &config);
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
output.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
output.push((get_shape_color(shape.as_str(), &config), text));
|
||||
};
|
||||
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::RawString => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List
|
||||
| FlatShape::Table
|
||||
| FlatShape::Record
|
||||
| FlatShape::Block
|
||||
| FlatShape::Closure => {
|
||||
let span = shape.0;
|
||||
let shape = &shape.1;
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
for (part, highlight) in spans {
|
||||
let start = part.start - span.start;
|
||||
let end = part.end - span.start;
|
||||
let text = next_token[start..end].to_string();
|
||||
let mut style = get_shape_color(shape.as_str(), &config);
|
||||
if highlight {
|
||||
style = get_matching_brackets_style(style, &config);
|
||||
}
|
||||
output.push((style, text));
|
||||
}
|
||||
result.text.push((style, text));
|
||||
}
|
||||
}
|
||||
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
}
|
||||
FlatShape::Flag => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
|
||||
}
|
||||
FlatShape::Flag => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
result.text.push((Style::new(), remainder));
|
||||
}
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
output.push((Style::new(), remainder));
|
||||
}
|
||||
|
||||
result
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn split_span_by_highlight_positions(
|
||||
|
@ -5,9 +5,9 @@ use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::{Token, TokenContents, lex, parse, unescape_unquote_string};
|
||||
use nu_protocol::{
|
||||
PipelineData, ShellError, Span, Value,
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error::report_compile_error,
|
||||
report_parse_error, report_parse_warning, report_shell_error,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
|
@ -5,9 +5,3 @@ fn nu_highlight_not_expr() {
|
||||
let actual = nu!("'not false' | nu-highlight | ansi strip");
|
||||
assert_eq!(actual.out, "not false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nu_highlight_where_row_condition() {
|
||||
let actual = nu!("'ls | where a b 12345(' | nu-highlight | ansi strip");
|
||||
assert_eq!(actual.out, "ls | where a b 12345(");
|
||||
}
|
||||
|
@ -9,16 +9,14 @@ use std::{
|
||||
use nu_cli::NuCompleter;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_path::{AbsolutePathBuf, expand_tilde};
|
||||
use nu_path::expand_tilde;
|
||||
use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet};
|
||||
use nu_std::load_standard_library;
|
||||
use nu_test_support::fs;
|
||||
use reedline::{Completer, Suggestion};
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{
|
||||
completions_helpers::{
|
||||
new_dotnu_engine, new_engine_helper, new_external_engine, new_partial_engine,
|
||||
new_quote_engine,
|
||||
new_dotnu_engine, new_external_engine, new_partial_engine, new_quote_engine,
|
||||
},
|
||||
file, folder, match_suggestions, match_suggestions_by_string, new_engine,
|
||||
};
|
||||
@ -97,11 +95,8 @@ 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
|
||||
]
|
||||
@ -128,7 +123,7 @@ fn custom_completer_with_options(
|
||||
global_opts,
|
||||
completions
|
||||
.iter()
|
||||
.map(|comp| format!("'{comp}'"))
|
||||
.map(|comp| format!("'{}'", comp))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
completer_opts,
|
||||
@ -312,10 +307,10 @@ fn custom_arguments_and_subcommands() {
|
||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
||||
// including both subcommand and directory completions
|
||||
let expected = [
|
||||
"foo test bar".into(),
|
||||
folder("test_a"),
|
||||
file("test_a_symlink"),
|
||||
folder("test_b"),
|
||||
"foo test bar".into(),
|
||||
];
|
||||
match_suggestions_by_string(&expected, &suggestions);
|
||||
}
|
||||
@ -333,7 +328,7 @@ fn custom_flags_and_subcommands() {
|
||||
let completion_str = "foo --test";
|
||||
let suggestions = completer.complete(completion_str, completion_str.len());
|
||||
// including both flag and directory completions
|
||||
let expected: Vec<_> = vec!["--test", "foo --test bar"];
|
||||
let expected: Vec<_> = vec!["foo --test bar", "--test"];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
@ -721,16 +716,6 @@ fn external_completer_fallback() {
|
||||
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
||||
let suggestions = run_external_completion(block, input);
|
||||
match_suggestions_by_string(&expected, &suggestions);
|
||||
|
||||
// issue #15790
|
||||
let input = "foo `dir with space/`";
|
||||
let expected = vec!["`dir with space/bar baz`", "`dir with space/foo`"];
|
||||
let suggestions = run_external_completion_within_pwd(
|
||||
block,
|
||||
input,
|
||||
fs::fixtures().join("external_completions"),
|
||||
);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
/// Fallback to external completions for flags of `sudo`
|
||||
@ -1483,12 +1468,11 @@ fn command_watch_with_filecompletion() {
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subcommand_vs_external_completer() {
|
||||
#[rstest]
|
||||
fn subcommand_completions() {
|
||||
let (_, _, mut engine, mut stack) = new_engine();
|
||||
let commands = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
$env.config.completions.external.completer = {|spans| ["external"]}
|
||||
def foo-test-command [] {}
|
||||
def "foo-test-command bar" [] {}
|
||||
def "foo-test-command aagap bcr" [] {}
|
||||
@ -1501,7 +1485,6 @@ fn subcommand_vs_external_completer() {
|
||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||
match_suggestions(
|
||||
&vec![
|
||||
"external",
|
||||
"food bar",
|
||||
"foo-test-command bar",
|
||||
"foo-test-command aagap bcr",
|
||||
@ -1511,7 +1494,7 @@ fn subcommand_vs_external_completer() {
|
||||
|
||||
let prefix = "foot bar";
|
||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||
match_suggestions(&vec!["external", "foo-test-command bar"], &suggestions);
|
||||
match_suggestions(&vec!["foo-test-command bar"], &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1563,7 +1546,9 @@ 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",
|
||||
@ -1584,12 +1569,9 @@ 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]
|
||||
@ -2121,15 +2103,11 @@ fn alias_of_another_alias() {
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
fn run_external_completion_within_pwd(
|
||||
completer: &str,
|
||||
input: &str,
|
||||
pwd: AbsolutePathBuf,
|
||||
) -> Vec<Suggestion> {
|
||||
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
let completer = format!("$env.config.completions.external.completer = {completer}");
|
||||
|
||||
// Create a new engine
|
||||
let (_, _, mut engine_state, mut stack) = new_engine_helper(pwd);
|
||||
let (_, _, mut engine_state, mut stack) = new_engine();
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, completer.as_bytes(), false);
|
||||
@ -2141,8 +2119,7 @@ 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
|
||||
@ -2154,10 +2131,6 @@ fn run_external_completion_within_pwd(
|
||||
completer.complete(input, input.len())
|
||||
}
|
||||
|
||||
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
run_external_completion_within_pwd(completer, input, fs::fixtures().join("completions"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
@ -2266,22 +2239,6 @@ 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);
|
||||
@ -2310,17 +2267,6 @@ 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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.106.2"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
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 }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -1,11 +1,11 @@
|
||||
use miette::Result;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return, redirect_env};
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||
cli_error::{report_parse_error, report_shell_error},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||
report_error::{report_parse_error, report_shell_error},
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
@ -325,7 +325,19 @@ fn run_hook(
|
||||
}
|
||||
|
||||
// If all went fine, preserve the environment of the called block
|
||||
redirect_env(engine_state, stack, &callee_stack);
|
||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||
|
||||
// remove env vars that are present in the caller but not in the callee
|
||||
// (the callee hid them)
|
||||
for var in caller_env_vars.iter() {
|
||||
if !callee_stack.has_env_var(engine_state, var) {
|
||||
stack.remove_env_var(engine_state, var);
|
||||
}
|
||||
}
|
||||
|
||||
// add new env vars from callee to caller
|
||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||
stack.add_env_var(var, value);
|
||||
}
|
||||
Ok(pipeline_data)
|
||||
}
|
||||
|
@ -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.2"
|
||||
version = "0.105.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.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 }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-json = { version = "0.105.2", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-pretty-hex = { version = "0.105.2", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.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.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
|
@ -3,12 +3,7 @@ use nu_protocol::engine::Command;
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_examples(cmd: impl Command + 'static) {
|
||||
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);
|
||||
test_examples::test_examples(cmd);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -26,10 +21,10 @@ mod test_examples {
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn test_examples(cmd: impl Command + 'static, commands: &[&dyn Command]) {
|
||||
pub fn test_examples(cmd: impl Command + 'static) {
|
||||
let examples = cmd.examples();
|
||||
let signature = cmd.signature();
|
||||
let mut engine_state = make_engine_state(cmd.clone_box(), commands);
|
||||
let mut engine_state = make_engine_state(cmd.clone_box());
|
||||
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
|
||||
@ -43,7 +38,7 @@ mod test_examples {
|
||||
check_example_input_and_output_types_match_command_signature(
|
||||
&example,
|
||||
&cwd,
|
||||
&mut make_engine_state(cmd.clone_box(), commands),
|
||||
&mut make_engine_state(cmd.clone_box()),
|
||||
&signature.input_output_types,
|
||||
signature.operates_on_cell_paths(),
|
||||
),
|
||||
@ -62,7 +57,7 @@ mod test_examples {
|
||||
);
|
||||
}
|
||||
|
||||
fn make_engine_state(cmd: Box<dyn Command>, commands: &[&dyn Command]) -> Box<EngineState> {
|
||||
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
|
||||
let delta = {
|
||||
@ -74,10 +69,6 @@ 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,
|
||||
|
@ -12,10 +12,7 @@ impl Command for UpdateCells {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("update cells")
|
||||
.input_output_types(vec![
|
||||
(Type::table(), Type::table()),
|
||||
(Type::record(), Type::record()),
|
||||
])
|
||||
.input_output_types(vec![(Type::table(), Type::table())])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
@ -80,15 +77,6 @@ impl Command for UpdateCells {
|
||||
"2021-11-18" => Value::test_string(""),
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
example: r#"{a: 1, b: 2, c: 3} | update cells { $in + 10 }"#,
|
||||
description: "Update each value in a record.",
|
||||
result: Some(Value::test_record(record! {
|
||||
"a" => Value::test_int(11),
|
||||
"b" => Value::test_int(12),
|
||||
"c" => Value::test_int(13),
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -97,7 +85,7 @@ impl Command for UpdateCells {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
mut input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||
@ -114,51 +102,14 @@ impl Command for UpdateCells {
|
||||
|
||||
let metadata = input.metadata();
|
||||
|
||||
match input {
|
||||
PipelineData::Value(
|
||||
Value::Record {
|
||||
ref mut val,
|
||||
internal_span,
|
||||
},
|
||||
..,
|
||||
) => {
|
||||
let val = val.to_mut();
|
||||
update_record(
|
||||
val,
|
||||
&mut ClosureEval::new(engine_state, stack, closure),
|
||||
internal_span,
|
||||
columns.as_ref(),
|
||||
);
|
||||
Ok(input)
|
||||
}
|
||||
_ => Ok(UpdateCellIterator {
|
||||
iter: input.into_iter(),
|
||||
closure: ClosureEval::new(engine_state, stack, closure),
|
||||
columns,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data(head, engine_state.signals().clone())
|
||||
.set_metadata(metadata)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_record(
|
||||
record: &mut Record,
|
||||
closure: &mut ClosureEval,
|
||||
span: Span,
|
||||
cols: Option<&HashSet<String>>,
|
||||
) {
|
||||
if let Some(columns) = cols {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if columns.contains(col) {
|
||||
*val = eval_value(closure, span, std::mem::take(val));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, val) in record.iter_mut() {
|
||||
*val = eval_value(closure, span, std::mem::take(val))
|
||||
Ok(UpdateCellIterator {
|
||||
iter: input.into_iter(),
|
||||
closure: ClosureEval::new(engine_state, stack, closure),
|
||||
columns,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data(head, engine_state.signals().clone())
|
||||
.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,7 +128,18 @@ impl Iterator for UpdateCellIterator {
|
||||
|
||||
let value = if let Value::Record { val, .. } = &mut value {
|
||||
let val = val.to_mut();
|
||||
update_record(val, &mut self.closure, self.span, self.columns.as_ref());
|
||||
if let Some(columns) = &self.columns {
|
||||
for (col, val) in val.iter_mut() {
|
||||
if columns.contains(col) {
|
||||
*val = eval_value(&mut self.closure, self.span, std::mem::take(val));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, val) in val.iter_mut() {
|
||||
*val = eval_value(&mut self.closure, self.span, std::mem::take(val))
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
} else {
|
||||
eval_value(&mut self.closure, self.span, value)
|
||||
|
@ -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,26 +109,18 @@ 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",
|
||||
@ -196,7 +188,7 @@ fn get_theme_from_asset_file(
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: format!("Unknown HTML theme '{theme_name}'"),
|
||||
err_message: format!("Unknown HTML theme '{}'", theme_name),
|
||||
span: theme_span,
|
||||
});
|
||||
}
|
||||
@ -262,7 +254,6 @@ 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);
|
||||
|
||||
@ -328,15 +319,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, raw, config),
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
None => {
|
||||
let value = &vec_of_values[0];
|
||||
html_value(value.clone(), raw, config)
|
||||
html_value(value.clone(), config)
|
||||
}
|
||||
},
|
||||
_ => match headers {
|
||||
Some(headers) => html_table(vec_of_values, headers, raw, config),
|
||||
None => html_list(vec_of_values, raw, config),
|
||||
Some(headers) => html_table(vec_of_values, headers, config),
|
||||
None => html_list(vec_of_values, config),
|
||||
},
|
||||
};
|
||||
|
||||
@ -404,19 +395,19 @@ fn theme_demo(span: Span) -> PipelineData {
|
||||
})
|
||||
}
|
||||
|
||||
fn html_list(list: Vec<Value>, raw: bool, config: &Config) -> String {
|
||||
fn html_list(list: Vec<Value>, 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, raw, config));
|
||||
output_string.push_str(&html_value(value, config));
|
||||
output_string.push_str("</li>");
|
||||
}
|
||||
output_string.push_str("</ol>");
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_table(table: Vec<Value>, headers: Vec<String>, raw: bool, config: &Config) -> String {
|
||||
fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
|
||||
output_string.push_str("<table>");
|
||||
@ -439,7 +430,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, raw: bool, config: &Confi
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::nothing(span));
|
||||
output_string.push_str("<td>");
|
||||
output_string.push_str(&html_value(data, raw, config));
|
||||
output_string.push_str(&html_value(data, config));
|
||||
output_string.push_str("</td>");
|
||||
}
|
||||
output_string.push_str("</tr>");
|
||||
@ -450,7 +441,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, raw: bool, config: &Confi
|
||||
output_string
|
||||
}
|
||||
|
||||
fn html_value(value: Value, raw: bool, config: &Config) -> String {
|
||||
fn html_value(value: Value, config: &Config) -> String {
|
||||
let mut output_string = String::new();
|
||||
match value {
|
||||
Value::Binary { val, .. } => {
|
||||
@ -459,22 +450,11 @@ fn html_value(value: Value, raw: bool, config: &Config) -> String {
|
||||
output_string.push_str(&output);
|
||||
output_string.push_str("</pre>");
|
||||
}
|
||||
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>"),
|
||||
)
|
||||
}
|
||||
}
|
||||
other => output_string.push_str(
|
||||
&v_htmlescape::escape(&other.to_abbreviated_string(config))
|
||||
.to_string()
|
||||
.replace('\n', "<br>"),
|
||||
),
|
||||
}
|
||||
output_string
|
||||
}
|
||||
@ -737,10 +717,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples_with_commands;
|
||||
use nu_command::ToXml;
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples_with_commands(ToHtml {}, &[&ToXml])
|
||||
test_examples(ToHtml {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -795,7 +774,8 @@ mod tests {
|
||||
for key in required_keys {
|
||||
assert!(
|
||||
theme_map.contains_key(key),
|
||||
"Expected theme to contain key '{key}'"
|
||||
"Expected theme to contain key '{}'",
|
||||
key
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -812,13 +792,15 @@ mod tests {
|
||||
if let Err(err) = result {
|
||||
assert!(
|
||||
matches!(err, ShellError::TypeMismatch { .. }),
|
||||
"Expected TypeMismatch error, got: {err:?}"
|
||||
"Expected TypeMismatch error, got: {:?}",
|
||||
err
|
||||
);
|
||||
|
||||
if let ShellError::TypeMismatch { err_message, span } = err {
|
||||
assert!(
|
||||
err_message.contains("doesnt-exist"),
|
||||
"Error message should mention theme name, got: {err_message}"
|
||||
"Error message should mention theme name, got: {}",
|
||||
err_message
|
||||
);
|
||||
assert_eq!(span.start, 0);
|
||||
assert_eq!(span.end, 13);
|
||||
|
@ -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::byte_stream(
|
||||
Ok(PipelineData::ByteStream(
|
||||
byte_stream_to_bits(stream, head),
|
||||
metadata,
|
||||
))
|
||||
@ -161,28 +161,28 @@ fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else if let Some(v) = num.to_i16() {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else if let Some(v) = num.to_i32() {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else {
|
||||
let bytes = num.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
@ -193,7 +193,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
Value::Binary { val, .. } => {
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in val {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
@ -204,7 +204,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
let raw_bytes = val.as_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in raw_bytes {
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
|
@ -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),
|
||||
},
|
||||
|
||||
|
@ -16,11 +16,6 @@ impl Command for FormatNumber {
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("format number")
|
||||
.input_output_types(vec![(Type::Number, Type::record())])
|
||||
.switch(
|
||||
"no-prefix",
|
||||
"don't include the binary, hex or octal prefixes",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Conversions)
|
||||
}
|
||||
|
||||
@ -29,36 +24,20 @@ impl Command for FormatNumber {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a record containing multiple formats for the number 42",
|
||||
example: "42 | format number",
|
||||
result: Some(Value::test_record(record! {
|
||||
"debug" => Value::test_string("42"),
|
||||
"display" => Value::test_string("42"),
|
||||
"binary" => Value::test_string("0b101010"),
|
||||
"lowerexp" => Value::test_string("4.2e1"),
|
||||
"upperexp" => Value::test_string("4.2E1"),
|
||||
"lowerhex" => Value::test_string("0x2a"),
|
||||
"upperhex" => Value::test_string("0x2A"),
|
||||
"octal" => Value::test_string("0o52"),
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
description: "Format float without prefixes",
|
||||
example: "3.14 | format number --no-prefix",
|
||||
result: Some(Value::test_record(record! {
|
||||
"debug" => Value::test_string("3.14"),
|
||||
"display" => Value::test_string("3.14"),
|
||||
"binary" => Value::test_string("100000000001001000111101011100001010001111010111000010100011111"),
|
||||
"lowerexp" => Value::test_string("3.14e0"),
|
||||
"upperexp" => Value::test_string("3.14E0"),
|
||||
"lowerhex" => Value::test_string("40091eb851eb851f"),
|
||||
"upperhex" => Value::test_string("40091EB851EB851F"),
|
||||
"octal" => Value::test_string("400110753412172702437"),
|
||||
})),
|
||||
},
|
||||
]
|
||||
vec![Example {
|
||||
description: "Get a record containing multiple formats for the number 42",
|
||||
example: "42 | format number",
|
||||
result: Some(Value::test_record(record! {
|
||||
"binary" => Value::test_string("0b101010"),
|
||||
"debug" => Value::test_string("42"),
|
||||
"display" => Value::test_string("42"),
|
||||
"lowerexp" => Value::test_string("4.2e1"),
|
||||
"lowerhex" => Value::test_string("0x2a"),
|
||||
"octal" => Value::test_string("0o52"),
|
||||
"upperexp" => Value::test_string("4.2E1"),
|
||||
"upperhex" => Value::test_string("0x2A"),
|
||||
})),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -80,24 +59,14 @@ pub(crate) fn format_number(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let args = CellPathOnlyArgs::from(cell_paths);
|
||||
if call.has_flag(engine_state, stack, "no-prefix")? {
|
||||
operate(
|
||||
action_no_prefix,
|
||||
args,
|
||||
input,
|
||||
call.head,
|
||||
engine_state.signals(),
|
||||
)
|
||||
} else {
|
||||
operate(action, args, input, call.head, engine_state.signals())
|
||||
}
|
||||
operate(action, args, input, call.head, engine_state.signals())
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Float { val, .. } => format_f64(*val, false, span),
|
||||
Value::Int { val, .. } => format_i64(*val, false, span),
|
||||
Value::Filesize { val, .. } => format_i64(val.get(), false, span),
|
||||
Value::Float { val, .. } => format_f64(*val, span),
|
||||
Value::Int { val, .. } => format_i64(*val, span),
|
||||
Value::Filesize { val, .. } => format_i64(val.get(), span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
@ -112,80 +81,33 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn action_no_prefix(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Float { val, .. } => format_f64(*val, true, span),
|
||||
Value::Int { val, .. } => format_i64(*val, true, span),
|
||||
Value::Filesize { val, .. } => format_i64(val.get(), true, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "float, int, or filesize".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.span(),
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_i64(num: i64, no_prefix: bool, span: Span) -> Value {
|
||||
fn format_i64(num: i64, span: Span) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"binary" => Value::string(format!("{num:#b}"), span),
|
||||
"debug" => Value::string(format!("{num:#?}"), span),
|
||||
"display" => Value::string(format!("{num}"), span),
|
||||
"binary" => Value::string(
|
||||
if no_prefix { format!("{num:b}") } else { format!("{num:#b}") },
|
||||
span,
|
||||
),
|
||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||
"lowerhex" => Value::string(format!("{num:#x}"), span),
|
||||
"octal" => Value::string(format!("{num:#o}"), span),
|
||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||
"lowerhex" => Value::string(
|
||||
if no_prefix { format!("{num:x}") } else { format!("{num:#x}") },
|
||||
span,
|
||||
),
|
||||
"upperhex" => Value::string(
|
||||
if no_prefix { format!("{num:X}") } else { format!("{num:#X}") },
|
||||
span,
|
||||
),
|
||||
"octal" => Value::string(
|
||||
if no_prefix { format!("{num:o}") } else { format!("{num:#o}") },
|
||||
span,
|
||||
)
|
||||
"upperhex" => Value::string(format!("{num:#X}"), span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn format_f64(num: f64, no_prefix: bool, span: Span) -> Value {
|
||||
fn format_f64(num: f64, span: Span) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
||||
"debug" => Value::string(format!("{num:#?}"), span),
|
||||
"display" => Value::string(format!("{num}"), span),
|
||||
"binary" => Value::string(
|
||||
if no_prefix {
|
||||
format!("{:b}", num.to_bits())
|
||||
} else {
|
||||
format!("{:#b}", num.to_bits())
|
||||
},
|
||||
span,
|
||||
),
|
||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||
"lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
|
||||
"octal" => Value::string(format!("{:0o}", num.to_bits()), span),
|
||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||
"lowerhex" => Value::string(
|
||||
if no_prefix { format!("{:x}", num.to_bits()) } else { format!("{:#x}", num.to_bits()) },
|
||||
span,
|
||||
),
|
||||
"upperhex" => Value::string(
|
||||
if no_prefix { format!("{:X}", num.to_bits()) } else { format!("{:#X}", num.to_bits()) },
|
||||
span,
|
||||
),
|
||||
"octal" => Value::string(
|
||||
if no_prefix { format!("{:o}", num.to_bits()) } else { format!("{:#o}", num.to_bits()) },
|
||||
span,
|
||||
)
|
||||
"upperhex" => Value::string(format!("{:0X}", num.to_bits()), span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
|
@ -4,4 +4,4 @@ pub mod extra;
|
||||
pub use extra::*;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use example_test::{test_examples, test_examples_with_commands};
|
||||
pub use example_test::test_examples;
|
||||
|
@ -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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -15,18 +15,17 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
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" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "1.2", default-features = false }
|
||||
shadow-rs = { version = "1.1", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "1.2", default-features = false, features = ["build"] }
|
||||
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { workspace = true }
|
||||
@ -44,3 +43,8 @@ plugin = [
|
||||
"nu-protocol/plugin",
|
||||
"os",
|
||||
]
|
||||
|
||||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
|
@ -296,7 +296,7 @@ fn run(
|
||||
} else {
|
||||
let value = stream.into_value();
|
||||
let base_description = value.get_type().to_string();
|
||||
Value::string(format!("{base_description} (stream)"), head)
|
||||
Value::string(format!("{} (stream)", base_description), head)
|
||||
}
|
||||
}
|
||||
PipelineData::Value(value, ..) => {
|
||||
|
@ -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::byte_stream(
|
||||
Ok(PipelineData::ByteStream(
|
||||
ByteStream::child(child, span),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
Err(stream) => Ok(PipelineData::byte_stream(stream, metadata)),
|
||||
Err(stream) => Ok(PipelineData::ByteStream(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::byte_stream(stream, metadata))
|
||||
Ok(PipelineData::ByteStream(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::list_stream(stream, metadata))
|
||||
Ok(PipelineData::ListStream(stream, metadata))
|
||||
}
|
||||
r => r,
|
||||
}
|
||||
@ -264,7 +264,7 @@ fn bind_args_to(
|
||||
.expect("internal error: all custom parameters must have var_ids");
|
||||
if let Some(result) = val_iter.next() {
|
||||
let param_type = param.shape.to_type();
|
||||
if !result.is_subtype_of(¶m_type) {
|
||||
if required && !result.is_subtype_of(¶m_type) {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: param.shape.to_type().to_string(),
|
||||
from_type: result.get_type().to_string(),
|
||||
|
@ -229,7 +229,7 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
|
||||
error: "invalid error format.".into(),
|
||||
msg: "`$.label.start` should be smaller than `$.label.end`".into(),
|
||||
span: Some(label_span),
|
||||
help: Some(format!("{span_start} > {span_end}")),
|
||||
help: Some(format!("{} > {}", span_start, span_end)),
|
||||
inner: vec![],
|
||||
};
|
||||
}
|
||||
|
@ -60,13 +60,11 @@ impl Command for If {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let call = call.assert_ast_call()?;
|
||||
let cond = call.positional_nth(0).expect("checked through parser");
|
||||
let then_expr = call.positional_nth(1).expect("checked through parser");
|
||||
let then_block = then_expr
|
||||
let then_block = call
|
||||
.positional_nth(1)
|
||||
.expect("checked through parser")
|
||||
.as_block()
|
||||
.ok_or_else(|| ShellError::TypeMismatch {
|
||||
err_message: "expected block".into(),
|
||||
span: then_expr.span,
|
||||
})?;
|
||||
.expect("internal error: missing block");
|
||||
let else_case = call.positional_nth(2);
|
||||
|
||||
if eval_constant(working_set, cond)?.as_bool()? {
|
||||
|
@ -69,5 +69,5 @@ pub use return_::Return;
|
||||
pub use scope::*;
|
||||
pub use try_::Try;
|
||||
pub use use_::Use;
|
||||
pub use version::{VERSION_NU_FEATURES, Version};
|
||||
pub use version::Version;
|
||||
pub use while_::While;
|
||||
|
@ -96,7 +96,6 @@ impl Command for OverlayHide {
|
||||
for (name, val) in env_vars_to_keep {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
stack.update_config(engine_state)?;
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ impl Command for OverlayUse {
|
||||
}
|
||||
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
let _ = eval_block(engine_state, &mut callee_stack, block, input)?;
|
||||
let _ = eval_block(engine_state, &mut callee_stack, block, input);
|
||||
|
||||
// The export-env block should see the env vars *before* activating this overlay
|
||||
caller_stack.add_overlay(overlay_name);
|
||||
@ -178,7 +178,6 @@ impl Command for OverlayUse {
|
||||
}
|
||||
} else {
|
||||
caller_stack.add_overlay(overlay_name);
|
||||
caller_stack.update_config(engine_state)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
|
@ -1,48 +1,11 @@
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use shadow_rs::shadow;
|
||||
|
||||
shadow!(build);
|
||||
|
||||
/// Static container for the cargo features used by the `version` command.
|
||||
///
|
||||
/// This `OnceLock` holds the features from `nu`.
|
||||
/// When you build `nu_cmd_lang`, Cargo doesn't pass along the same features that `nu` itself uses.
|
||||
/// By setting this static before calling `version`, you make it show `nu`'s features instead
|
||||
/// of `nu_cmd_lang`'s.
|
||||
///
|
||||
/// Embedders can set this to any feature list they need, but in most cases you'll probably want to
|
||||
/// pass the cargo features of your host binary.
|
||||
///
|
||||
/// # How to get cargo features in your build script
|
||||
///
|
||||
/// In your binary's build script:
|
||||
/// ```rust,ignore
|
||||
/// // Re-export CARGO_CFG_FEATURE to the main binary.
|
||||
/// // It holds all the features that cargo sets for your binary as a comma-separated list.
|
||||
/// println!(
|
||||
/// "cargo:rustc-env=NU_FEATURES={}",
|
||||
/// std::env::var("CARGO_CFG_FEATURE").expect("set by cargo")
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Then, before you call `version`:
|
||||
/// ```rust,ignore
|
||||
/// // This uses static strings, but since we're using `Cow`, you can also pass owned strings.
|
||||
/// let features = env!("NU_FEATURES")
|
||||
/// .split(',')
|
||||
/// .map(Cow::Borrowed)
|
||||
/// .collect();
|
||||
///
|
||||
/// nu_cmd_lang::VERSION_NU_FEATURES
|
||||
/// .set(features)
|
||||
/// .expect("couldn't set VERSION_NU_FEATURES");
|
||||
/// ```
|
||||
pub static VERSION_NU_FEATURES: OnceLock<Vec<Cow<'static, str>>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Version;
|
||||
|
||||
@ -150,17 +113,7 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
||||
|
||||
record.push(
|
||||
"features",
|
||||
Value::string(
|
||||
VERSION_NU_FEATURES
|
||||
.get()
|
||||
.as_ref()
|
||||
.map(|v| v.as_slice())
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|f| !f.starts_with("dep:"))
|
||||
.join(", "),
|
||||
span,
|
||||
),
|
||||
Value::string(features_enabled().join(", "), span),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "plugin"))]
|
||||
@ -188,17 +141,6 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
||||
);
|
||||
}
|
||||
|
||||
record.push(
|
||||
"experimental_options",
|
||||
Value::string(
|
||||
nu_experimental::ALL
|
||||
.iter()
|
||||
.map(|option| format!("{}={}", option.identifier(), option.get()))
|
||||
.join(", "),
|
||||
span,
|
||||
),
|
||||
);
|
||||
|
||||
Ok(Value::record(record, span).into_pipeline_data())
|
||||
}
|
||||
|
||||
@ -222,12 +164,42 @@ fn global_allocator() -> &'static str {
|
||||
"standard"
|
||||
}
|
||||
|
||||
fn features_enabled() -> Vec<String> {
|
||||
let mut names = vec!["default".to_string()];
|
||||
|
||||
// NOTE: There should be another way to know features on.
|
||||
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
names.push("trash".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
{
|
||||
names.push("sqlite".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "static-link-openssl")]
|
||||
{
|
||||
names.push("static-link-openssl".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "system-clipboard")]
|
||||
{
|
||||
names.push("system-clipboard".to_string());
|
||||
}
|
||||
|
||||
names.sort();
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Version;
|
||||
use crate::test_examples;
|
||||
test_examples(Version)
|
||||
test_examples(Version {})
|
||||
}
|
||||
}
|
||||
|
@ -221,23 +221,23 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0 {
|
||||
Value::Bool { val, .. } => {
|
||||
write!(f, "{val:?}")
|
||||
write!(f, "{:?}", val)
|
||||
}
|
||||
Value::Int { val, .. } => {
|
||||
write!(f, "{val:?}")
|
||||
write!(f, "{:?}", val)
|
||||
}
|
||||
Value::Float { val, .. } => {
|
||||
write!(f, "{val:?}f")
|
||||
write!(f, "{:?}f", val)
|
||||
}
|
||||
Value::Filesize { val, .. } => {
|
||||
write!(f, "Filesize({val:?})")
|
||||
write!(f, "Filesize({:?})", val)
|
||||
}
|
||||
Value::Duration { val, .. } => {
|
||||
let duration = std::time::Duration::from_nanos(*val as u64);
|
||||
write!(f, "Duration({duration:?})")
|
||||
write!(f, "Duration({:?})", duration)
|
||||
}
|
||||
Value::Date { val, .. } => {
|
||||
write!(f, "Date({val:?})")
|
||||
write!(f, "Date({:?})", val)
|
||||
}
|
||||
Value::Range { val, .. } => match **val {
|
||||
Range::IntRange(range) => match range.end() {
|
||||
@ -280,7 +280,7 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
},
|
||||
},
|
||||
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||
write!(f, "{val:?}")
|
||||
write!(f, "{:?}", val)
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
write!(f, "{{")?;
|
||||
@ -305,22 +305,22 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
write!(f, "]")
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
write!(f, "Closure({val:?})")
|
||||
write!(f, "Closure({:?})", val)
|
||||
}
|
||||
Value::Nothing { .. } => {
|
||||
write!(f, "Nothing")
|
||||
}
|
||||
Value::Error { error, .. } => {
|
||||
write!(f, "Error({error:?})")
|
||||
write!(f, "Error({:?})", error)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
write!(f, "Binary({val:?})")
|
||||
write!(f, "Binary({:?})", val)
|
||||
}
|
||||
Value::CellPath { val, .. } => {
|
||||
write!(f, "CellPath({:?})", val.to_string())
|
||||
}
|
||||
Value::Custom { val, .. } => {
|
||||
write!(f, "CustomValue({val:?})")
|
||||
write!(f, "CustomValue({:?})", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.106.2"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
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" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,12 +14,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
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-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.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.2"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,22 +16,21 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-system = { path = "../nu-system", version = "0.105.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.105.2" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||
nu-ansi-term = { workspace = true }
|
||||
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" }
|
||||
nuon = { path = "../nuon", version = "0.105.2" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
@ -54,14 +53,12 @@ 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 }
|
||||
@ -94,7 +91,6 @@ 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 }
|
||||
@ -194,7 +190,6 @@ os = [
|
||||
"uu_uname",
|
||||
"uu_whoami",
|
||||
"which",
|
||||
"ureq/platform-verifier"
|
||||
]
|
||||
|
||||
# The dependencies listed below need 'getrandom'.
|
||||
@ -223,7 +218,7 @@ rustls-tls = [
|
||||
"dep:rustls-native-certs",
|
||||
"dep:webpki-roots",
|
||||
"update-informer/rustls-tls",
|
||||
"ureq/rustls",
|
||||
"ureq/tls", # ureq 3 will has the feature rustls instead
|
||||
]
|
||||
|
||||
plugin = ["nu-parser/plugin", "os"]
|
||||
@ -231,8 +226,8 @@ sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.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::byte_stream(stream, metadata))
|
||||
Ok(PipelineData::ByteStream(stream, metadata))
|
||||
} else {
|
||||
operate(
|
||||
map_value,
|
||||
|
@ -67,7 +67,7 @@ impl Command for BytesCollect {
|
||||
ByteStreamType::Binary,
|
||||
);
|
||||
|
||||
Ok(PipelineData::byte_stream(output, metadata))
|
||||
Ok(PipelineData::ByteStream(output, metadata))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -89,7 +89,7 @@ impl Command for Histogram {
|
||||
"frequency-column-name can't be {}",
|
||||
forbidden_column_names
|
||||
.iter()
|
||||
.map(|val| format!("'{val}'"))
|
||||
.map(|val| format!("'{}'", val))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
|
@ -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::byte_stream(
|
||||
Ok(PipelineData::ByteStream(
|
||||
stream.with_type(ByteStreamType::Binary),
|
||||
metadata,
|
||||
))
|
||||
@ -142,7 +142,7 @@ fn into_binary(
|
||||
}
|
||||
}
|
||||
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
let value = match input {
|
||||
Value::Binary { .. } => input.clone(),
|
||||
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||
@ -168,7 +168,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
),
|
||||
};
|
||||
|
||||
if args.compact {
|
||||
if _args.compact {
|
||||
let val_span = value.span();
|
||||
if let Value::Binary { val, .. } = value {
|
||||
let val = if cfg!(target_endian = "little") {
|
||||
|
@ -82,6 +82,9 @@ 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
|
||||
@ -675,7 +678,7 @@ fn parse_value_from_record_as_u32(
|
||||
Value::Int { val, .. } => {
|
||||
if *val < 0 || *val > u32::MAX as i64 {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!("incorrect value for {col}"),
|
||||
msg: format!("incorrect value for {}", col),
|
||||
val_span: *head,
|
||||
call_span: *span,
|
||||
});
|
||||
|
@ -53,6 +53,9 @@ 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()),
|
||||
@ -365,7 +368,8 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellE
|
||||
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
||||
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!("Invalid sign. Allowed signs are {allowed_signs}").to_string(),
|
||||
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
|
||||
.to_string(),
|
||||
val_span: sign.span(),
|
||||
call_span: head,
|
||||
});
|
||||
|
@ -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::byte_stream(
|
||||
Ok(PipelineData::ByteStream(
|
||||
stream.with_type(ByteStreamType::String),
|
||||
metadata,
|
||||
))
|
||||
|
@ -1,12 +1,9 @@
|
||||
use crate::{
|
||||
MEMORY_DB,
|
||||
database::values::sqlite::{open_sqlite_db, values_to_sql},
|
||||
};
|
||||
use crate::database::values::sqlite::{open_sqlite_db, values_to_sql};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nu_protocol::Signals;
|
||||
use std::{borrow::Cow, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
pub const DEFAULT_TABLE_NAME: &str = "main";
|
||||
|
||||
@ -79,17 +76,6 @@ 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)
|
||||
]))
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -103,25 +89,15 @@ 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 = 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(),
|
||||
let table_name = if let Some(table_name) = table_name {
|
||||
table_name.item
|
||||
} else {
|
||||
DEFAULT_TABLE_NAME.to_string()
|
||||
};
|
||||
|
||||
// create the sqlite database table
|
||||
let conn = open_sqlite_db(&db_path, span)?;
|
||||
let conn = open_sqlite_db(Path::new(&db_path.item), db_path.span)?;
|
||||
|
||||
Ok(Self { conn, table_name })
|
||||
}
|
||||
@ -146,8 +122,8 @@ impl Table {
|
||||
.conn
|
||||
.query_row(&table_exists_query, [], |row| row.get(0))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: format!("{err:#?}"),
|
||||
msg: format!("{err:#?}"),
|
||||
error: format!("{:#?}", err),
|
||||
msg: format!("{:#?}", err),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
@ -206,12 +182,11 @@ 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, engine_state, stack)?;
|
||||
Ok(action(engine_state, input, table, span, engine_state.signals())?.into_pipeline_data())
|
||||
let table = Table::new(&file_name, table_name)?;
|
||||
Ok(action(input, table, span, engine_state.signals())?.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn action(
|
||||
engine_state: &EngineState,
|
||||
input: PipelineData,
|
||||
table: Table,
|
||||
span: Span,
|
||||
@ -219,17 +194,17 @@ fn action(
|
||||
) -> Result<Value, ShellError> {
|
||||
match input {
|
||||
PipelineData::ListStream(stream, _) => {
|
||||
insert_in_transaction(engine_state, stream.into_iter(), span, table, signals)
|
||||
insert_in_transaction(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(engine_state, vals.into_iter(), span, table, signals)
|
||||
insert_in_transaction(vals.into_iter(), span, table, signals)
|
||||
}
|
||||
PipelineData::Value(val, _) => {
|
||||
insert_in_transaction(engine_state, std::iter::once(val), span, table, signals)
|
||||
insert_in_transaction(std::iter::once(val), span, table, signals)
|
||||
}
|
||||
_ => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "list".into(),
|
||||
@ -241,7 +216,6 @@ fn action(
|
||||
}
|
||||
|
||||
fn insert_in_transaction(
|
||||
engine_state: &EngineState,
|
||||
stream: impl Iterator<Item = Value>,
|
||||
span: Span,
|
||||
mut table: Table,
|
||||
@ -267,7 +241,7 @@ fn insert_in_transaction(
|
||||
let tx = table.try_init(&first_val)?;
|
||||
|
||||
for stream_value in stream {
|
||||
if let Err(err) = signals.check(&span) {
|
||||
if let Err(err) = signals.check(span) {
|
||||
tx.rollback().map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to rollback SQLite transaction".into(),
|
||||
msg: e.to_string(),
|
||||
@ -283,7 +257,7 @@ fn insert_in_transaction(
|
||||
let insert_statement = format!(
|
||||
"INSERT INTO [{}] ({}) VALUES ({})",
|
||||
table_name,
|
||||
Itertools::intersperse(val.columns().map(|c| format!("`{c}`")), ", ".to_string())
|
||||
Itertools::intersperse(val.columns().map(|c| format!("`{}`", c)), ", ".to_string())
|
||||
.collect::<String>(),
|
||||
Itertools::intersperse(itertools::repeat_n("?", val.len()), ", ").collect::<String>(),
|
||||
);
|
||||
@ -298,7 +272,7 @@ fn insert_in_transaction(
|
||||
inner: Vec::new(),
|
||||
})?;
|
||||
|
||||
let result = insert_value(engine_state, stream_value, span, &mut insert_statement);
|
||||
let result = insert_value(stream_value, &mut insert_statement);
|
||||
|
||||
insert_statement
|
||||
.finalize()
|
||||
@ -325,15 +299,13 @@ 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(engine_state, val.values().cloned(), call_span)?;
|
||||
let sql_vals = values_to_sql(val.values().cloned())?;
|
||||
|
||||
insert_statement
|
||||
.execute(rusqlite::params_from_iter(sql_vals))
|
||||
@ -373,7 +345,6 @@ 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.
|
||||
@ -382,13 +353,15 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
||||
|
||||
// intentionally enumerated so that any future types get handled
|
||||
Type::Any
|
||||
| Type::Block
|
||||
| Type::CellPath
|
||||
| Type::Closure
|
||||
| Type::Custom(_)
|
||||
| Type::Error
|
||||
| Type::List(_)
|
||||
| Type::Range
|
||||
| Type::Glob => Err(ShellError::OnlySupportsThisInputType {
|
||||
| Type::Record(_)
|
||||
| Type::Glob
|
||||
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "sql".into(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
dst_span: Span::unknown(),
|
||||
@ -408,9 +381,23 @@ fn get_columns_with_sqlite_types(
|
||||
.map(|name| (format!("`{}`", name.0), name.1))
|
||||
.any(|(name, _)| name == *c)
|
||||
{
|
||||
columns.push((format!("`{c}`"), nu_value_to_sqlite_type(v)?));
|
||||
columns.push((format!("`{}`", c), nu_value_to_sqlite_type(v)?));
|
||||
}
|
||||
}
|
||||
|
||||
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,36 +54,6 @@ 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),
|
||||
])
|
||||
},
|
||||
)])),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -103,7 +73,7 @@ stor open | query db "SELECT data->'baz' AS baz FROM my_table" | update baz {fro
|
||||
.get_flag(engine_state, stack, "params")?
|
||||
.unwrap_or_else(|| Value::nothing(Span::unknown()));
|
||||
|
||||
let params = nu_value_to_params(engine_state, params_value, call.head)?;
|
||||
let params = nu_value_to_params(params_value)?;
|
||||
|
||||
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,
|
||||
engine::EngineState, shell_error::io::IoError,
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
use rusqlite::{
|
||||
Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement, ToSql,
|
||||
@ -112,31 +112,16 @@ impl SQLiteDatabase {
|
||||
if self.path == PathBuf::from(MEMORY_DB) {
|
||||
open_connection_in_memory_custom()
|
||||
} else {
|
||||
let conn = Connection::open(&self.path).map_err(|e| ShellError::GenericError {
|
||||
Connection::open(&self.path).map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to open SQLite database from open_connection".into(),
|
||||
msg: e.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
conn.busy_handler(Some(SQLiteDatabase::sleeper))
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to set busy handler for SQLite database".into(),
|
||||
msg: e.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
Ok(conn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn sleeper(attempts: i32) -> bool {
|
||||
log::warn!("SQLITE_BUSY, retrying after 250ms (attempt {})", attempts);
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, SqliteError> {
|
||||
let mut table_names =
|
||||
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
||||
@ -173,7 +158,7 @@ impl SQLiteDatabase {
|
||||
filename: String,
|
||||
) -> Result<(), SqliteError> {
|
||||
//vacuum main into 'c:\\temp\\foo.db'
|
||||
conn.execute(&format!("vacuum main into '{filename}'"), [])?;
|
||||
conn.execute(&format!("vacuum main into '{}'", filename), [])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -431,44 +416,35 @@ 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(
|
||||
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)),
|
||||
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),
|
||||
val => {
|
||||
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()),
|
||||
}),
|
||||
}
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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(|v| value_to_sql(engine_state, v, call_span))
|
||||
.map(value_to_sql)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
@ -483,17 +459,13 @@ impl Default for NuSqlParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nu_value_to_params(
|
||||
engine_state: &EngineState,
|
||||
value: Value,
|
||||
call_span: Span,
|
||||
) -> Result<NuSqlParams, ShellError> {
|
||||
pub fn nu_value_to_params(value: Value) -> 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(engine_state, value, call_span)?;
|
||||
let sql_type_erased = value_to_sql(value)?;
|
||||
|
||||
if !column.starts_with([':', '@', '$']) {
|
||||
column.insert(0, ':');
|
||||
@ -508,7 +480,7 @@ pub fn nu_value_to_params(
|
||||
let mut params = Vec::with_capacity(vals.len());
|
||||
|
||||
for value in vals.into_iter() {
|
||||
let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
|
||||
let sql_type_erased = value_to_sql(value)?;
|
||||
|
||||
params.push(sql_type_erased);
|
||||
}
|
||||
@ -570,49 +542,17 @@ 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 columns: Vec<TypedColumn> = stmt
|
||||
.columns()
|
||||
.iter()
|
||||
.map(TypedColumn::from_rusqlite_column)
|
||||
.collect();
|
||||
let column_names = stmt
|
||||
.column_names()
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// I'm very sorry for this repetition
|
||||
// I tried scoping the match arms to the query_map alone, but lifetime and closure reference escapes
|
||||
@ -622,14 +562,18 @@ 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, &columns))
|
||||
Ok(convert_sqlite_row_to_nu_value(
|
||||
row,
|
||||
call_span,
|
||||
&column_names,
|
||||
))
|
||||
})?;
|
||||
|
||||
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
|
||||
let mut row_values = vec![];
|
||||
|
||||
for row_result in row_results {
|
||||
signals.check(&call_span)?;
|
||||
signals.check(call_span)?;
|
||||
if let Ok(row_value) = row_result {
|
||||
row_values.push(row_value);
|
||||
}
|
||||
@ -644,14 +588,18 @@ 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, &columns))
|
||||
Ok(convert_sqlite_row_to_nu_value(
|
||||
row,
|
||||
call_span,
|
||||
&column_names,
|
||||
))
|
||||
})?;
|
||||
|
||||
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
|
||||
let mut row_values = vec![];
|
||||
|
||||
for row_result in row_results {
|
||||
signals.check(&call_span)?;
|
||||
signals.check(call_span)?;
|
||||
if let Ok(row_value) = row_result {
|
||||
row_values.push(row_value);
|
||||
}
|
||||
@ -687,14 +635,14 @@ fn read_entire_sqlite_db(
|
||||
Ok(Value::record(tables, call_span))
|
||||
}
|
||||
|
||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, columns: &[TypedColumn]) -> Value {
|
||||
let record = columns
|
||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[String]) -> Value {
|
||||
let record = column_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, col)| {
|
||||
(
|
||||
col.name.clone(),
|
||||
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), col.decl_type, span),
|
||||
col.clone(),
|
||||
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@ -702,48 +650,31 @@ pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, columns: &[TypedCol
|
||||
Value::record(record, span)
|
||||
}
|
||||
|
||||
pub fn convert_sqlite_value_to_nu_value(
|
||||
value: ValueRef,
|
||||
decl_type: Option<DeclType>,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, 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) => 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::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::Blob(u) => Value::binary(u.to_vec(), span),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
|
||||
let flags = OpenFlags::default();
|
||||
let conn =
|
||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to open SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
conn.busy_handler(Some(SQLiteDatabase::sleeper))
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to set busy handler for SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
Ok(conn)
|
||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to open SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
||||
|
@ -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,
|
||||
))
|
||||
|
@ -1,64 +0,0 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_experimental::Status;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DebugExperimentalOptions;
|
||||
|
||||
impl Command for DebugExperimentalOptions {
|
||||
fn name(&self) -> &str {
|
||||
"debug experimental-options"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name())
|
||||
.input_output_type(
|
||||
Type::Nothing,
|
||||
Type::Table(Box::from([
|
||||
(String::from("identifier"), Type::String),
|
||||
(String::from("enabled"), Type::Bool),
|
||||
(String::from("status"), Type::String),
|
||||
(String::from("description"), Type::String),
|
||||
])),
|
||||
)
|
||||
.add_help()
|
||||
.category(Category::Debug)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Show all experimental options."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::value(
|
||||
Value::list(
|
||||
nu_experimental::ALL
|
||||
.iter()
|
||||
.map(|option| {
|
||||
Value::record(
|
||||
nu_protocol::record! {
|
||||
"identifier" => Value::string(option.identifier(), call.head),
|
||||
"enabled" => Value::bool(option.get(), call.head),
|
||||
"status" => Value::string(match option.status() {
|
||||
Status::OptIn => "opt-in",
|
||||
Status::OptOut => "opt-out",
|
||||
Status::DeprecatedDiscard => "deprecated-discard",
|
||||
Status::DeprecatedDefault => "deprecated-default"
|
||||
}, call.head),
|
||||
"description" => Value::string(option.description(), call.head),
|
||||
},
|
||||
call.head,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
call.head,
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
@ -43,17 +43,6 @@ impl Command for Metadata {
|
||||
let arg = call.positional_nth(stack, 0);
|
||||
let head = call.head;
|
||||
|
||||
if !matches!(input, PipelineData::Empty) {
|
||||
if let Some(arg_expr) = arg {
|
||||
return Err(ShellError::IncompatibleParameters {
|
||||
left_message: "pipeline input was provided".into(),
|
||||
left_span: head,
|
||||
right_message: "but a positional metadata expression was also given".into(),
|
||||
right_span: arg_expr.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
match arg {
|
||||
Some(Expression {
|
||||
expr: Expr::FullCellPath(full_cell_path),
|
||||
@ -67,6 +56,7 @@ impl Command for Metadata {
|
||||
..
|
||||
} => {
|
||||
let origin = stack.get_var_with_origin(*var_id, *span)?;
|
||||
|
||||
Ok(build_metadata_record_value(
|
||||
&origin,
|
||||
input.metadata().as_ref(),
|
||||
@ -97,9 +87,10 @@ impl Command for Metadata {
|
||||
.into_pipeline_data(),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
Ok(Value::record(build_metadata_record(&input, head), head).into_pipeline_data())
|
||||
}
|
||||
None => Ok(
|
||||
Value::record(build_metadata_record(input.metadata().as_ref(), head), head)
|
||||
.into_pipeline_data(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +116,19 @@ fn build_metadata_record_value(
|
||||
head: Span,
|
||||
) -> Value {
|
||||
let mut record = Record::new();
|
||||
record.push("span", arg.span().into_value(head));
|
||||
|
||||
let span = arg.span();
|
||||
record.push(
|
||||
"span",
|
||||
Value::record(
|
||||
record! {
|
||||
"start" => Value::int(span.start as i64,span),
|
||||
"end" => Value::int(span.end as i64, span),
|
||||
},
|
||||
head,
|
||||
),
|
||||
);
|
||||
|
||||
Value::record(extend_record_with_metadata(record, metadata, head), head)
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,10 @@ impl Command for MetadataAccess {
|
||||
// `ClosureEvalOnce` is not used as it uses `Stack::captures_to_stack` rather than
|
||||
// `Stack::captures_to_stack_preserve_out_dest`. This command shouldn't collect streams
|
||||
let mut callee_stack = caller_stack.captures_to_stack_preserve_out_dest(closure.captures);
|
||||
let metadata_record = Value::record(build_metadata_record(&input, call.head), call.head);
|
||||
let metadata_record = Value::record(
|
||||
build_metadata_record(input.metadata().as_ref(), call.head),
|
||||
call.head,
|
||||
);
|
||||
|
||||
if let Some(var_id) = block.signature.get_positional(0).and_then(|var| var.var_id) {
|
||||
callee_stack.add_var(var_id, metadata_record)
|
||||
@ -55,10 +58,12 @@ impl Command for MetadataAccess {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Access metadata and data from a stream together",
|
||||
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, content: $meta.content_type}}"#,
|
||||
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, meta: $meta}}"#,
|
||||
result: Some(Value::test_record(record! {
|
||||
"in" => Value::test_string(r#"{"foo":"bar"}"#),
|
||||
"content" => Value::test_string(r#"application/json"#)
|
||||
"meta" => Value::test_record(record! {
|
||||
"content_type" => Value::test_string(r#"application/json"#)
|
||||
})
|
||||
})),
|
||||
}]
|
||||
}
|
||||
|
@ -63,18 +63,7 @@ impl Command for MetadataSet {
|
||||
match (ds_fp, ds_ls) {
|
||||
(Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()),
|
||||
(None, true) => metadata.data_source = DataSource::Ls,
|
||||
(Some(_), true) => {
|
||||
return Err(ShellError::IncompatibleParameters {
|
||||
left_message: "cannot use `--datasource-filepath`".into(),
|
||||
left_span: call
|
||||
.get_flag_span(stack, "datasource-filepath")
|
||||
.expect("has flag"),
|
||||
right_message: "with `--datasource-ls`".into(),
|
||||
right_span: call
|
||||
.get_flag_span(stack, "datasource-ls")
|
||||
.expect("has flag"),
|
||||
});
|
||||
}
|
||||
(Some(_), true) => (), // TODO: error here
|
||||
(None, false) => (),
|
||||
}
|
||||
|
||||
@ -90,13 +79,15 @@ impl Command for MetadataSet {
|
||||
},
|
||||
Example {
|
||||
description: "Set the metadata of a file path",
|
||||
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates'",
|
||||
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set the metadata of a file path",
|
||||
example: "'crates' | metadata set --content-type text/plain | metadata | get content_type",
|
||||
result: Some(Value::test_string("text/plain")),
|
||||
example: "'crates' | metadata set --content-type text/plain | metadata",
|
||||
result: Some(Value::test_record(record! {
|
||||
"content_type" => Value::test_string("text/plain"),
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
mod ast;
|
||||
mod debug_;
|
||||
mod env;
|
||||
mod experimental_options;
|
||||
mod explain;
|
||||
mod info;
|
||||
mod inspect;
|
||||
@ -22,7 +21,6 @@ mod view_span;
|
||||
pub use ast::Ast;
|
||||
pub use debug_::Debug;
|
||||
pub use env::DebugEnv;
|
||||
pub use experimental_options::DebugExperimentalOptions;
|
||||
pub use explain::Explain;
|
||||
pub use info::DebugInfo;
|
||||
pub use inspect::Inspect;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_protocol::{DataSource, IntoValue, PipelineData, PipelineMetadata, Record, Span, Value};
|
||||
use nu_protocol::{DataSource, PipelineMetadata, Record, Span, Value};
|
||||
|
||||
pub fn extend_record_with_metadata(
|
||||
mut record: Record,
|
||||
@ -29,10 +29,6 @@ pub fn extend_record_with_metadata(
|
||||
record
|
||||
}
|
||||
|
||||
pub fn build_metadata_record(pipeline: &PipelineData, head: Span) -> Record {
|
||||
let mut record = Record::new();
|
||||
if let Some(span) = pipeline.span() {
|
||||
record.insert("span", span.into_value(head));
|
||||
}
|
||||
extend_record_with_metadata(record, pipeline.metadata().as_ref(), head)
|
||||
pub fn build_metadata_record(metadata: Option<&PipelineMetadata>, head: Span) -> Record {
|
||||
extend_record_with_metadata(Record::new(), metadata, head)
|
||||
}
|
||||
|
@ -126,10 +126,10 @@ impl Command for ViewSource {
|
||||
}
|
||||
let _ = write!(&mut final_contents, "--{}", n.long);
|
||||
if let Some(short) = n.short {
|
||||
let _ = write!(&mut final_contents, "(-{short})");
|
||||
let _ = write!(&mut final_contents, "(-{})", short);
|
||||
}
|
||||
if let Some(arg) = &n.arg {
|
||||
let _ = write!(&mut final_contents, ": {arg}");
|
||||
let _ = write!(&mut final_contents, ": {}", arg);
|
||||
}
|
||||
final_contents.push(' ');
|
||||
}
|
||||
@ -146,7 +146,7 @@ impl Command for ViewSource {
|
||||
let mut c = 0;
|
||||
for (insig, outsig) in type_signatures {
|
||||
c += 1;
|
||||
let s = format!("{insig} -> {outsig}");
|
||||
let s = format!("{} -> {}", insig, outsig);
|
||||
final_contents.push_str(&s);
|
||||
if c != len {
|
||||
final_contents.push_str(", ")
|
||||
|
@ -153,7 +153,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
Ast,
|
||||
Debug,
|
||||
DebugEnv,
|
||||
DebugExperimentalOptions,
|
||||
DebugInfo,
|
||||
DebugProfile,
|
||||
Explain,
|
||||
@ -189,9 +188,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
|
||||
// Strings
|
||||
bind_command! {
|
||||
Ansi,
|
||||
AnsiLink,
|
||||
AnsiStrip,
|
||||
Char,
|
||||
Decode,
|
||||
Encode,
|
||||
@ -254,6 +250,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
// Platform
|
||||
#[cfg(feature = "os")]
|
||||
bind_command! {
|
||||
Ansi,
|
||||
AnsiLink,
|
||||
AnsiStrip,
|
||||
Clear,
|
||||
Du,
|
||||
Input,
|
||||
|
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::byte_stream`.
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = nu_protocol::process::ChildProcess::new(
|
||||
child,
|
||||
None,
|
||||
@ -133,7 +133,7 @@ pub(super) fn start_editor(
|
||||
Some(post_wait_callback),
|
||||
)?;
|
||||
|
||||
Ok(PipelineData::byte_stream(
|
||||
Ok(PipelineData::ByteStream(
|
||||
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,7 +78,9 @@ in no particular order, regardless of the specified timeout parameter.
|
||||
|
||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
||||
|
||||
let timeout: Option<Duration> = call.get_flag(engine_state, stack, "timeout")?;
|
||||
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
|
||||
|
||||
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
|
||||
|
||||
let mut mailbox = engine_state
|
||||
.current_job
|
||||
@ -114,11 +116,6 @@ 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,17 +101,10 @@ 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 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,
|
||||
},
|
||||
]
|
||||
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,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ impl Command for Glob {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Search for files or folders that do not begin with c, C, b, M, or s",
|
||||
description: "Search for files for folders that do not begin with c, C, b, M, or s",
|
||||
example: r#"glob "[!cCbMs]*""#,
|
||||
result: None,
|
||||
},
|
||||
@ -329,7 +329,7 @@ fn glob_to_value(
|
||||
) -> ListStream {
|
||||
let map_signals = signals.clone();
|
||||
let result = glob_results.filter_map(move |entry| {
|
||||
if let Err(err) = map_signals.check(&span) {
|
||||
if let Err(err) = map_signals.check(span) {
|
||||
return Some(Value::error(err, span));
|
||||
};
|
||||
let file_type = entry.file_type();
|
||||
|
@ -341,7 +341,7 @@ fn ls_for_one_pattern(
|
||||
|
||||
let mut paths_peek = paths.peekable();
|
||||
let no_matches = paths_peek.peek().is_none();
|
||||
signals.check(&call_span)?;
|
||||
signals.check(call_span)?;
|
||||
if no_matches {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("No matches found for {:?}", path.item),
|
||||
@ -979,14 +979,14 @@ fn read_dir(
|
||||
.read_dir()
|
||||
.map_err(|err| IoError::new(err, span, f.clone()))?
|
||||
.map(move |d| {
|
||||
signals_clone.check(&span)?;
|
||||
signals_clone.check(span)?;
|
||||
d.map(|r| r.path())
|
||||
.map_err(|err| IoError::new(err, span, f.clone()))
|
||||
.map_err(ShellError::from)
|
||||
});
|
||||
if !use_threads {
|
||||
let mut collected = items.collect::<Vec<_>>();
|
||||
signals.check(&span)?;
|
||||
signals.check(span)?;
|
||||
collected.sort_by(|a, b| match (a, b) {
|
||||
(Ok(a), Ok(b)) => a.cmp(b),
|
||||
(Ok(_), Err(_)) => Ordering::Greater,
|
||||
|
@ -112,14 +112,14 @@ impl Command for Mktemp {
|
||||
.map_err(|_| ShellError::NonUtf8 { span })?,
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{e}"),
|
||||
msg: format!("{e}"),
|
||||
error: format!("{}", e),
|
||||
msg: format!("{}", e),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
};
|
||||
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::byte_stream(
|
||||
let stream = PipelineData::ByteStream(
|
||||
ByteStream::file(file, call_span, engine_state.signals().clone()),
|
||||
Some(PipelineMetadata {
|
||||
data_source: DataSource::FilePath(path.to_path_buf()),
|
||||
@ -198,7 +198,7 @@ impl Command for Open {
|
||||
let converter = exts_opt.and_then(|exts| {
|
||||
exts.iter().find_map(|ext| {
|
||||
engine_state
|
||||
.find_decl(format!("from {ext}").as_bytes(), &[])
|
||||
.find_decl(format!("from {}", ext).as_bytes(), &[])
|
||||
.map(|id| (id, ext.to_string()))
|
||||
})
|
||||
});
|
||||
@ -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 {
|
||||
@ -314,7 +314,7 @@ fn extract_extensions(filename: &str) -> Vec<String> {
|
||||
if current_extension.is_empty() {
|
||||
current_extension.push_str(part);
|
||||
} else {
|
||||
current_extension = format!("{part}.{current_extension}");
|
||||
current_extension = format!("{}.{}", part, current_extension);
|
||||
}
|
||||
extensions.push(current_extension.clone());
|
||||
}
|
||||
|
@ -339,7 +339,7 @@ fn rm(
|
||||
inner: vec![],
|
||||
});
|
||||
} else if !confirmed {
|
||||
return Ok(PipelineData::empty());
|
||||
return Ok(PipelineData::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,7 +454,7 @@ fn rm(
|
||||
});
|
||||
|
||||
for result in iter {
|
||||
engine_state.signals().check(&call.head)?;
|
||||
engine_state.signals().check(call.head)?;
|
||||
match result {
|
||||
Ok(None) => {}
|
||||
Ok(Some(msg)) => eprintln!("{msg}"),
|
||||
|
@ -91,8 +91,7 @@ impl Command for Save {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
||||
|
||||
let (file, stderr_file) =
|
||||
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||
let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||
|
||||
let size = stream.known_size();
|
||||
let signals = engine_state.signals();
|
||||
@ -191,7 +190,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() =>
|
||||
@ -202,8 +201,7 @@ impl Command for Save {
|
||||
stderr_path.as_ref(),
|
||||
)?;
|
||||
|
||||
let (mut file, _) =
|
||||
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||
for val in ls {
|
||||
file.write_all(&value_to_bytes(val)?)
|
||||
.map_err(&from_io_error)?;
|
||||
@ -228,8 +226,7 @@ impl Command for Save {
|
||||
input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?;
|
||||
|
||||
// Only open file after successful conversion
|
||||
let (mut file, _) =
|
||||
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||
|
||||
file.write_all(&bytes).map_err(&from_io_error)?;
|
||||
file.flush().map_err(&from_io_error)?;
|
||||
@ -425,14 +422,13 @@ fn prepare_path(
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file(
|
||||
engine_state: &EngineState,
|
||||
path: &Path,
|
||||
span: Span,
|
||||
append: bool,
|
||||
) -> Result<File, ShellError> {
|
||||
let file: std::io::Result<File> = match (append, path.exists()) {
|
||||
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
|
||||
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
|
||||
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
|
||||
{
|
||||
(true, true) => std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(path)
|
||||
.map_err(|err| err.into()),
|
||||
_ => {
|
||||
// This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893)
|
||||
// A TOCTOU problem exists here, which may cause wrong error message to be shown
|
||||
@ -442,51 +438,22 @@ fn open_file(
|
||||
deprecated,
|
||||
reason = "we don't get a IsADirectory error, so we need to provide it"
|
||||
)]
|
||||
Err(std::io::ErrorKind::IsADirectory.into())
|
||||
Err(nu_protocol::shell_error::io::ErrorKind::from_std(
|
||||
std::io::ErrorKind::IsADirectory,
|
||||
))
|
||||
} else {
|
||||
std::fs::File::create(path)
|
||||
std::fs::File::create(path).map_err(|err| err.into())
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
std::fs::File::create(path)
|
||||
std::fs::File::create(path).map_err(|err| err.into())
|
||||
}
|
||||
};
|
||||
|
||||
match file {
|
||||
Ok(file) => Ok(file),
|
||||
Err(err) => {
|
||||
// In caase of NotFound, search for the missing parent directory.
|
||||
// This also presents a TOCTOU (or TOUTOC, technically?)
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
if let Some(missing_component) =
|
||||
path.ancestors().skip(1).filter(|dir| !dir.exists()).last()
|
||||
{
|
||||
// By looking at the postfix to remove, rather than the prefix
|
||||
// to keep, we are able to handle relative paths too.
|
||||
let components_to_remove = path
|
||||
.strip_prefix(missing_component)
|
||||
.expect("Stripping ancestor from a path should never fail")
|
||||
.as_os_str()
|
||||
.as_encoded_bytes();
|
||||
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
ErrorKind::DirectoryNotFound,
|
||||
engine_state
|
||||
.span_match_postfix(span, components_to_remove)
|
||||
.map(|(pre, _post)| pre)
|
||||
.unwrap_or(span),
|
||||
PathBuf::from(missing_component),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShellError::Io(IoError::new(err, span, PathBuf::from(path))))
|
||||
}
|
||||
}
|
||||
file.map_err(|err_kind| ShellError::Io(IoError::new(err_kind, span, PathBuf::from(path))))
|
||||
}
|
||||
|
||||
/// Get output file and optional stderr file
|
||||
fn get_files(
|
||||
engine_state: &EngineState,
|
||||
path: &Spanned<PathBuf>,
|
||||
stderr_path: Option<&Spanned<PathBuf>>,
|
||||
append: bool,
|
||||
@ -500,7 +467,7 @@ fn get_files(
|
||||
.transpose()?;
|
||||
|
||||
// Only if both files can be used open and possibly truncate them
|
||||
let file = open_file(engine_state, path, path_span, append)?;
|
||||
let file = open_file(path, path_span, append)?;
|
||||
|
||||
let stderr_file = stderr_path_and_span
|
||||
.map(|(stderr_path, stderr_path_span)| {
|
||||
@ -513,7 +480,7 @@ fn get_files(
|
||||
inner: vec![],
|
||||
})
|
||||
} else {
|
||||
open_file(engine_state, stderr_path, stderr_path_span, append)
|
||||
open_file(stderr_path, stderr_path_span, append)
|
||||
}
|
||||
})
|
||||
.transpose()?;
|
||||
@ -543,7 +510,7 @@ fn stream_to_file(
|
||||
let mut reader = BufReader::new(source);
|
||||
|
||||
let res = loop {
|
||||
if let Err(err) = signals.check(&span) {
|
||||
if let Err(err) = signals.check(span) {
|
||||
bar.abandoned_msg("# Cancelled #".to_owned());
|
||||
return Err(err);
|
||||
}
|
||||
|
@ -45,16 +45,15 @@ 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))?;
|
||||
let full_path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
||||
|
||||
let full_path = cwd.join(path_no_whitespace);
|
||||
// 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 {
|
||||
|
@ -272,8 +272,8 @@ impl Command for UCp {
|
||||
uu_cp::Error::NotAllFilesCopied => {}
|
||||
_ => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
@ -373,7 +373,7 @@ fn parse_and_set_attribute(
|
||||
"xattr" => &mut attribute.xattr,
|
||||
_ => {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: format!("--preserve flag got an unexpected attribute \"{val}\""),
|
||||
msg: format!("--preserve flag got an unexpected attribute \"{}\"", val),
|
||||
span: value.span(),
|
||||
});
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ impl Command for UMkdir {
|
||||
for dir in directories {
|
||||
if let Err(error) = mkdir(&dir, IS_RECURSIVE, get_mode(), is_verbose) {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
|
@ -195,8 +195,8 @@ impl Command for UMv {
|
||||
};
|
||||
if let Err(error) = uu_mv::mv(&files, &options) {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
|
@ -220,7 +220,7 @@ impl Command for UTouch {
|
||||
inner: Vec::new(),
|
||||
},
|
||||
TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue {
|
||||
msg: format!("Invalid date: {date}"),
|
||||
msg: format!("Invalid date: {}", date),
|
||||
val_span: date_span.expect("touch should've been given a date"),
|
||||
call_span: call.head,
|
||||
},
|
||||
|
@ -7,10 +7,10 @@ use notify_debouncer_full::{
|
||||
};
|
||||
use nu_engine::{ClosureEval, command_prelude::*};
|
||||
use nu_protocol::{
|
||||
DeprecationEntry, DeprecationType, ReportMode, engine::Closure, report_shell_error,
|
||||
engine::{Closure, StateWorkingSet},
|
||||
format_shell_error,
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc::{RecvTimeoutError, channel},
|
||||
@ -37,20 +37,9 @@ 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")
|
||||
// actually `watch` never returns normally, but we don't have `noreturn` / `never` type yet
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
.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])),
|
||||
@ -58,13 +47,7 @@ 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 (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",
|
||||
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events",
|
||||
Some('d'),
|
||||
)
|
||||
.named(
|
||||
@ -116,14 +99,20 @@ impl Command for Watch {
|
||||
|
||||
let quiet = call.has_flag(engine_state, stack, "quiet")?;
|
||||
|
||||
let debounce_duration_flag_ms: Option<Spanned<i64>> =
|
||||
let debounce_duration_flag: Option<Spanned<i64>> =
|
||||
call.get_flag(engine_state, stack, "debounce-ms")?;
|
||||
|
||||
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 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 glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
|
||||
let glob_pattern = match glob_flag {
|
||||
@ -211,12 +200,17 @@ 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)?,
|
||||
Err(err) => report_shell_error(engine_state, &err),
|
||||
};
|
||||
Ok(val) => {
|
||||
val.print_table(engine_state, stack, false, false)?;
|
||||
}
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
eprintln!("{}", format_shell_error(&working_set, &err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -309,11 +303,6 @@ 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 }"#,
|
||||
@ -322,26 +311,3 @@ 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),
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user