mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4136aa3f4 | |||
082e8d0de8 | |||
9da0f41ebb | |||
372d576846 | |||
c795f16143 | |||
a4a3c514ba | |||
5478ec44bb | |||
6902bbe547 | |||
4e5da8cd91 | |||
d248451428 | |||
3e758e899f | |||
f69a812055 | |||
6fba4b409e | |||
cb7ac9199d | |||
a6b8e2f95c | |||
0b202d55f0 | |||
e88a6bff60 | |||
a234e6ff51 | |||
ae0cf8780d | |||
680a2fa2aa | |||
70277cc2ba | |||
574106bc03 | |||
2a8364d259 | |||
760c9ef2e9 | |||
c3079a14d9 | |||
4f7e9aac62 | |||
7ee8aa78cc | |||
d9d022733f | |||
1d032ce80c | |||
975a89269e | |||
db5b6c790f | |||
2bed202b82 | |||
8a0f2ca9f9 | |||
24ab294cda | |||
bfa95bbd24 | |||
3f700f03ad | |||
f0e90a3733 | |||
cde8a629c5 | |||
70aa7ad993 | |||
29b3512494 | |||
d961ea19cc | |||
3db9c81958 | |||
55240d98a5 | |||
fda181d566 | |||
2e484156e0 | |||
52604f8b00 | |||
2fed1f5967 | |||
5be8717fe8 | |||
091d14f085 | |||
4c19242c0d | |||
3df0177ba5 | |||
f7888fce83 | |||
cf1a53143c | |||
28a94048c5 | |||
fb691c0da5 | |||
7972aea530 | |||
aa710eeb9a | |||
91e843a6d4 | |||
ebcb26f9d5 | |||
f8b0af70ff | |||
12465193a4 | |||
bd3930d00d | |||
81e86c40e1 | |||
2fe25d6299 | |||
4aeede2dd5 | |||
0e46ef9769 | |||
962467fdfd | |||
d27232df6e | |||
d7cec2088a | |||
22d1fdcdf6 | |||
ba59f71f20 | |||
2352548467 | |||
3efbda63b8 |
25
.github/workflows/friendly-config-reminder.yml
vendored
Normal file
25
.github/workflows/friendly-config-reminder.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
uses: hustcer/setup-nu@v3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.105.1
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
@ -127,6 +127,7 @@ jobs:
|
|||||||
- armv7-unknown-linux-musleabihf
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
- loongarch64-unknown-linux-gnu
|
- loongarch64-unknown-linux-gnu
|
||||||
|
- loongarch64-unknown-linux-musl
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
@ -152,6 +153,8 @@ jobs:
|
|||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: loongarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
steps:
|
steps:
|
||||||
@ -179,36 +182,22 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
|
cache: false
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.105.1
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
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
|
- name: Create an Issue for Release Failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: JasonEtco/create-an-issue@v2
|
uses: JasonEtco/create-an-issue@v2
|
||||||
@ -228,9 +217,7 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
${{ steps.nu.outputs.msi }}
|
${{ steps.nu.outputs.msi }}
|
||||||
${{ steps.nu0.outputs.msi }}
|
|
||||||
${{ steps.nu.outputs.archive }}
|
${{ steps.nu.outputs.archive }}
|
||||||
${{ steps.nu0.outputs.archive }}
|
|
||||||
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
||||||
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
|
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
|
||||||
env:
|
env:
|
||||||
@ -276,7 +263,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.105.1
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
2
.github/workflows/release-msi.yml
vendored
2
.github/workflows/release-msi.yml
vendored
@ -58,7 +58,7 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: nightly
|
version: 0.105.1
|
||||||
|
|
||||||
- name: Release MSI Packages
|
- name: Release MSI Packages
|
||||||
id: nu
|
id: nu
|
||||||
|
8
.github/workflows/release-pkg.nu
vendored
8
.github/workflows/release-pkg.nu
vendored
@ -99,6 +99,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
|
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
|
||||||
cargo-build-nu
|
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?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -35,6 +35,7 @@ jobs:
|
|||||||
- armv7-unknown-linux-musleabihf
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
- loongarch64-unknown-linux-gnu
|
- loongarch64-unknown-linux-gnu
|
||||||
|
- loongarch64-unknown-linux-musl
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
@ -60,6 +61,8 @@ jobs:
|
|||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: loongarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
@ -90,32 +93,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
|
||||||
with:
|
with:
|
||||||
version: 0.103.0
|
version: 0.105.1
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
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.
|
# WARN: Don't upgrade this action due to the release per asset issue.
|
||||||
# See: https://github.com/softprops/action-gh-release/issues/445
|
# See: https://github.com/softprops/action-gh-release/issues/445
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
@ -125,9 +113,7 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
files: |
|
files: |
|
||||||
${{ steps.nu.outputs.msi }}
|
${{ steps.nu.outputs.msi }}
|
||||||
${{ steps.nu0.outputs.msi }}
|
|
||||||
${{ steps.nu.outputs.archive }}
|
${{ steps.nu.outputs.archive }}
|
||||||
${{ steps.nu0.outputs.archive }}
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
7
.github/workflows/winget-submission.yml
vendored
7
.github/workflows/winget-submission.yml
vendored
@ -10,6 +10,11 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
winget:
|
winget:
|
||||||
@ -25,5 +30,5 @@ jobs:
|
|||||||
installers-regex: 'msvc\.msi$'
|
installers-regex: 'msvc\.msi$'
|
||||||
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.NUSHELL_PAT }}
|
||||||
fork-user: nushell
|
fork-user: nushell
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -32,11 +32,17 @@ unstable_cargo_features.txt
|
|||||||
# Helix configuration folder
|
# Helix configuration folder
|
||||||
.helix/*
|
.helix/*
|
||||||
.helix
|
.helix
|
||||||
|
wix/bin/
|
||||||
|
wix/obj/
|
||||||
|
wix/nu/
|
||||||
|
|
||||||
# Coverage tools
|
# Coverage tools
|
||||||
lcov.info
|
lcov.info
|
||||||
tarpaulin-report.html
|
tarpaulin-report.html
|
||||||
|
|
||||||
|
# benchmarking
|
||||||
|
/tango
|
||||||
|
|
||||||
# Visual Studio
|
# Visual Studio
|
||||||
.vs/*
|
.vs/*
|
||||||
*.rsproj
|
*.rsproj
|
||||||
|
381
Cargo.lock
generated
381
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
70
Cargo.toml
70
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.85.1"
|
rust-version = "1.86.0"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ members = [
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
alphanumeric-sort = "1.5"
|
alphanumeric-sort = "1.5"
|
||||||
ansi-str = "0.8"
|
ansi-str = "0.9"
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bracoxide = "0.1.6"
|
bracoxide = "0.1.6"
|
||||||
@ -71,7 +71,7 @@ brotli = "7.0"
|
|||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytesize = "1.3.3"
|
bytesize = "1.3.3"
|
||||||
calamine = "0.26"
|
calamine = "0.28"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
chrono = { default-features = false, version = "0.4.34" }
|
chrono = { default-features = false, version = "0.4.34" }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
@ -156,14 +156,14 @@ serde_json = "1.0.97"
|
|||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9.33"
|
serde_yaml = "0.9.33"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.1"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
sysinfo = "0.33"
|
sysinfo = "0.33"
|
||||||
tabled = { version = "0.20", default-features = false }
|
tabled = { version = "0.20", default-features = false }
|
||||||
tempfile = "3.20"
|
tempfile = "3.20"
|
||||||
titlecase = "3.5"
|
titlecase = "3.6"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
|
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
|
||||||
@ -184,7 +184,7 @@ uuid = "1.16.0"
|
|||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
web-time = "1.1.0"
|
web-time = "1.1.0"
|
||||||
which = "7.0.0"
|
which = "8.0.0"
|
||||||
windows = "0.56"
|
windows = "0.56"
|
||||||
windows-sys = "0.48"
|
windows-sys = "0.48"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
@ -195,27 +195,28 @@ 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.
|
# 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"
|
# todo = "warn"
|
||||||
unchecked_duration_subtraction = "warn"
|
unchecked_duration_subtraction = "warn"
|
||||||
|
used_underscore_binding = "warn"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.105.1" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.1" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.1" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.1", optional = true }
|
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.1" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.105.1", default-features = false, features = ["os"] }
|
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.105.1" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.105.1" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.1" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.105.1" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.105.1" }
|
nu-path = { path = "./crates/nu-path", version = "0.105.2" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.1" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.2" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.2" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.105.1" }
|
nu-std = { path = "./crates/nu-std", version = "0.105.2" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.105.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.105.2" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.105.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.105.2" }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
@ -244,9 +245,9 @@ nix = { workspace = true, default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.1" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.2" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.1" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.2" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.1" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.2" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
tango-bench = "0.6"
|
tango-bench = "0.6"
|
||||||
@ -257,10 +258,14 @@ serial_test = "3.2"
|
|||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[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 = [
|
plugin = [
|
||||||
# crates
|
# crates
|
||||||
"nu-cmd-plugin",
|
"dep:nu-cmd-plugin",
|
||||||
"nu-plugin-engine",
|
"dep:nu-plugin-engine",
|
||||||
|
|
||||||
# features
|
# features
|
||||||
"nu-cli/plugin",
|
"nu-cli/plugin",
|
||||||
@ -286,21 +291,20 @@ stable = ["default"]
|
|||||||
|
|
||||||
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
|
# 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
|
# otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
static-link-openssl = ["dep:openssl"]
|
||||||
|
|
||||||
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
||||||
# Missing X server/ Wayland can cause issues
|
# Missing X server/ Wayland can cause issues
|
||||||
system-clipboard = [
|
system-clipboard = [
|
||||||
"reedline/system_clipboard",
|
"reedline/system_clipboard",
|
||||||
"nu-cli/system-clipboard",
|
"nu-cli/system-clipboard",
|
||||||
"nu-cmd-lang/system-clipboard",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Stable (Default)
|
# Stable (Default)
|
||||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
trash-support = ["nu-command/trash-support"]
|
||||||
|
|
||||||
# SQLite commands for nushell
|
# SQLite commands for nushell
|
||||||
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"]
|
sqlite = ["nu-command/sqlite", "nu-std/sqlite"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s" # Optimize for size
|
opt-level = "s" # Optimize for size
|
||||||
@ -330,7 +334,7 @@ bench = false
|
|||||||
# To use a development version of a dependency please use a global override here
|
# To use a development version of a dependency please use a global override here
|
||||||
# changing versions in each sub-crate of the workspace is tedious
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# reedline = { git = "https://github.com/nushell/reedline", 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"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
|
|
||||||
# Run all benchmarks with `cargo bench`
|
# Run all benchmarks with `cargo bench`
|
||||||
|
@ -199,7 +199,7 @@ fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
|
|||||||
let nested_access = ".col".repeat(n);
|
let nested_access = ".col".repeat(n);
|
||||||
bench_command(
|
bench_command(
|
||||||
format!("record_nested_access_{n}"),
|
format!("record_nested_access_{n}"),
|
||||||
format!("$record{} | ignore", nested_access),
|
format!("$record{nested_access} | ignore"),
|
||||||
stack,
|
stack,
|
||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
@ -319,7 +319,7 @@ fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks {
|
|||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
bench_command(
|
bench_command(
|
||||||
format!("eval_par_each_{n}"),
|
format!("eval_par_each_{n}"),
|
||||||
format!("(1..{}) | par-each -t 2 {{|_| 1 }} | ignore", n),
|
format!("(1..{n}) | par-each -t 2 {{|_| 1 }} | ignore"),
|
||||||
stack,
|
stack,
|
||||||
engine,
|
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());
|
let encoder = Rc::new(EncodingType::try_from_bytes(b"json").unwrap());
|
||||||
|
|
||||||
[benchmark_fn(
|
[benchmark_fn(
|
||||||
format!("encode_json_{}_{}", row_cnt, col_cnt),
|
format!("encode_json_{row_cnt}_{col_cnt}"),
|
||||||
move |b| {
|
move |b| {
|
||||||
let encoder = encoder.clone();
|
let encoder = encoder.clone();
|
||||||
let test_data = test_data.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());
|
let encoder = Rc::new(EncodingType::try_from_bytes(b"msgpack").unwrap());
|
||||||
|
|
||||||
[benchmark_fn(
|
[benchmark_fn(
|
||||||
format!("encode_msgpack_{}_{}", row_cnt, col_cnt),
|
format!("encode_msgpack_{row_cnt}_{col_cnt}"),
|
||||||
move |b| {
|
move |b| {
|
||||||
let encoder = encoder.clone();
|
let encoder = encoder.clone();
|
||||||
let test_data = test_data.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();
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
|
||||||
[benchmark_fn(
|
[benchmark_fn(
|
||||||
format!("decode_json_{}_{}", row_cnt, col_cnt),
|
format!("decode_json_{row_cnt}_{col_cnt}"),
|
||||||
move |b| {
|
move |b| {
|
||||||
let res = res.clone();
|
let res = res.clone();
|
||||||
b.iter(move || {
|
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();
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
|
||||||
[benchmark_fn(
|
[benchmark_fn(
|
||||||
format!("decode_msgpack_{}_{}", row_cnt, col_cnt),
|
format!("decode_msgpack_{row_cnt}_{col_cnt}"),
|
||||||
move |b| {
|
move |b| {
|
||||||
let res = res.clone();
|
let res = res.clone();
|
||||||
b.iter(move || {
|
b.iter(move || {
|
||||||
|
@ -5,29 +5,29 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.105.1" }
|
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||||
nu-std = { path = "../nu-std", version = "0.105.1" }
|
nu-std = { path = "../nu-std", version = "0.105.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.105.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", features = ["os"] }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", features = ["os"] }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.105.1" }
|
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.105.1" }
|
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.1" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.1", optional = true }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2", optional = true }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", features = ["os"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["os"] }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.1" }
|
nu-utils = { path = "../nu-utils", version = "0.105.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.105.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ fn get_suggestions_by_value(
|
|||||||
|| s.chars()
|
|| s.chars()
|
||||||
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
||||||
{
|
{
|
||||||
format!("{:?}", s)
|
format!("{s:?}")
|
||||||
} else {
|
} else {
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@ impl CommandCompletion {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let value = if matched_internal(&name) {
|
let value = if matched_internal(&name) {
|
||||||
format!("^{}", name)
|
format!("^{name}")
|
||||||
} else {
|
} else {
|
||||||
name.clone()
|
name.clone()
|
||||||
};
|
};
|
||||||
|
@ -176,7 +176,7 @@ impl NuCompleter {
|
|||||||
&mut working_set,
|
&mut working_set,
|
||||||
Some("completer"),
|
Some("completer"),
|
||||||
// Add a placeholder `a` to the end
|
// Add a placeholder `a` to the end
|
||||||
format!("{}a", line).as_bytes(),
|
format!("{line}a").as_bytes(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
self.fetch_completions_by_block(block, &working_set, pos, offset, line, true)
|
self.fetch_completions_by_block(block, &working_set, pos, offset, line, true)
|
||||||
@ -466,6 +466,14 @@ impl NuCompleter {
|
|||||||
return suggestions;
|
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);
|
||||||
|
suggestions.extend(self.process_completion(&mut FileCompletion, &ctx));
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -842,7 +850,7 @@ mod completer_tests {
|
|||||||
for (line, has_result, begins_with, expected_values) in dataset {
|
for (line, has_result, begins_with, expected_values) in dataset {
|
||||||
let result = completer.fetch_completions_at(line, line.len());
|
let result = completer.fetch_completions_at(line, line.len());
|
||||||
// Test whether the result is empty or not
|
// 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
|
// Test whether the result begins with the expected value
|
||||||
result
|
result
|
||||||
@ -857,8 +865,7 @@ mod completer_tests {
|
|||||||
.filter(|x| *x)
|
.filter(|x| *x)
|
||||||
.count(),
|
.count(),
|
||||||
expected_values.len(),
|
expected_values.len(),
|
||||||
"line: {}",
|
"line: {line}"
|
||||||
line
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,7 +314,7 @@ pub fn escape_path(path: String) -> String {
|
|||||||
if path.contains('\'') {
|
if path.contains('\'') {
|
||||||
// decide to use double quotes
|
// decide to use double quotes
|
||||||
// Path as Debug will do the escaping for `"`, `\`
|
// Path as Debug will do the escaping for `"`, `\`
|
||||||
format!("{:?}", path)
|
format!("{path:?}")
|
||||||
} else {
|
} else {
|
||||||
format!("'{path}'")
|
format!("'{path}'")
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ impl Completer for DotNuCompletion {
|
|||||||
.take_while(|c| "`'\"".contains(*c))
|
.take_while(|c| "`'\"".contains(*c))
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
for path in ["std", "std-rfc"] {
|
for path in ["std", "std-rfc"] {
|
||||||
let path = format!("{}{}", surround_prefix, path);
|
let path = format!("{surround_prefix}{path}");
|
||||||
matcher.add(
|
matcher.add(
|
||||||
path.clone(),
|
path.clone(),
|
||||||
FileSuggestion {
|
FileSuggestion {
|
||||||
@ -146,7 +146,7 @@ impl Completer for DotNuCompletion {
|
|||||||
for sub_vp_id in sub_paths {
|
for sub_vp_id in sub_paths {
|
||||||
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
||||||
let path = path
|
let path = path
|
||||||
.strip_prefix(&format!("{}/", base_dir))
|
.strip_prefix(&format!("{base_dir}/"))
|
||||||
.unwrap_or(path)
|
.unwrap_or(path)
|
||||||
.to_string();
|
.to_string();
|
||||||
matcher.add(
|
matcher.add(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use nu_engine::documentation::{HelpStyle, get_flags_section};
|
use nu_engine::documentation::{FormatterValue, HelpStyle, get_flags_section};
|
||||||
use nu_protocol::{Config, engine::EngineState, levenshtein_distance};
|
use nu_protocol::{Config, engine::EngineState, levenshtein_distance};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
@ -66,8 +66,11 @@ impl NuHelpCompleter {
|
|||||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| match v {
|
||||||
v.to_parsable_string(", ", &self.config)
|
FormatterValue::DefaultValue(value) => {
|
||||||
|
value.to_parsable_string(", ", &self.config)
|
||||||
|
}
|
||||||
|
FormatterValue::CodeString(text) => text.to_string(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ use std::sync::Arc;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
|
|
||||||
|
use crate::syntax_highlight::highlight_syntax;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuHighlight;
|
pub struct NuHighlight;
|
||||||
|
|
||||||
@ -14,6 +16,11 @@ impl Command for NuHighlight {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("nu-highlight")
|
Signature::build("nu-highlight")
|
||||||
.category(Category::Strings)
|
.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)])
|
.input_output_types(vec![(Type::String, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,19 +39,33 @@ impl Command for NuHighlight {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let reject_garbage = call.has_flag(engine_state, stack, "reject-garbage")?;
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
let signals = engine_state.signals();
|
let signals = engine_state.signals();
|
||||||
|
|
||||||
let highlighter = crate::NuHighlighter {
|
let engine_state = Arc::new(engine_state.clone());
|
||||||
engine_state: Arc::new(engine_state.clone()),
|
let stack = Arc::new(stack.clone());
|
||||||
stack: Arc::new(stack.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |x| match x.coerce_into_string() {
|
move |x| match x.coerce_into_string() {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let highlights = highlighter.highlight(&line, line.len());
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Value::string(highlights.render_simple(), head)
|
Value::string(highlights.render_simple(), head)
|
||||||
}
|
}
|
||||||
Err(err) => Value::error(err, head),
|
Err(err) => Value::error(err, head),
|
||||||
|
@ -22,8 +22,8 @@ use nu_color_config::StyleComputer;
|
|||||||
use nu_engine::env_to_strings;
|
use nu_engine::env_to_strings;
|
||||||
use nu_engine::exit::cleanup_exit;
|
use nu_engine::exit::cleanup_exit;
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
use nu_protocol::shell_error;
|
|
||||||
use nu_protocol::shell_error::io::IoError;
|
use nu_protocol::shell_error::io::IoError;
|
||||||
|
use nu_protocol::{BannerKind, shell_error};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
|
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
@ -145,8 +145,8 @@ pub fn evaluate_repl(
|
|||||||
|
|
||||||
if load_std_lib.is_none() {
|
if load_std_lib.is_none() {
|
||||||
match engine_state.get_config().show_banner {
|
match engine_state.get_config().show_banner {
|
||||||
Value::Bool { val: false, .. } => {}
|
BannerKind::None => {}
|
||||||
Value::String { ref val, .. } if val == "short" => {
|
BannerKind::Short => {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut unique_stack,
|
&mut unique_stack,
|
||||||
@ -156,7 +156,7 @@ pub fn evaluate_repl(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
BannerKind::Full => {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut unique_stack,
|
&mut unique_stack,
|
||||||
@ -239,7 +239,7 @@ fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
|
|||||||
|
|
||||||
match byte {
|
match byte {
|
||||||
// Escape bytes below 0x20
|
// Escape bytes below 0x20
|
||||||
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
|
b if b < 0x20 => format!("\\x{byte:02X}").into_bytes(),
|
||||||
// Escape semicolon as \x3B
|
// Escape semicolon as \x3B
|
||||||
b';' => "\\x3B".to_string().into_bytes(),
|
b';' => "\\x3B".to_string().into_bytes(),
|
||||||
// Escape backslash as \\
|
// Escape backslash as \\
|
||||||
@ -1097,8 +1097,7 @@ fn run_shell_integration_osc633(
|
|||||||
// If we're in vscode, run their specific ansi escape sequence.
|
// If we're in vscode, run their specific ansi escape sequence.
|
||||||
// This is helpful for ctrl+g to change directories in the terminal.
|
// This is helpful for ctrl+g to change directories in the terminal.
|
||||||
run_ansi_sequence(&format!(
|
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!(
|
perf!(
|
||||||
@ -1114,10 +1113,7 @@ fn run_shell_integration_osc633(
|
|||||||
|
|
||||||
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
||||||
run_ansi_sequence(&format!(
|
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
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1493,7 +1489,7 @@ mod test_auto_cd {
|
|||||||
// Parse the input. It must be an auto-cd operation.
|
// Parse the input. It must be an auto-cd operation.
|
||||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||||
let ReplOperation::AutoCd { cwd, target, span } = op else {
|
let ReplOperation::AutoCd { cwd, target, span } = op else {
|
||||||
panic!("'{}' was not parsed into an auto-cd operation", input)
|
panic!("'{input}' was not parsed into an auto-cd operation")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Perform the auto-cd operation.
|
// Perform the auto-cd operation.
|
||||||
|
@ -17,12 +17,32 @@ pub struct NuHighlighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Highlighter for NuHighlighter {
|
impl Highlighter for NuHighlighter {
|
||||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
fn highlight(&self, line: &str, cursor: usize) -> StyledText {
|
||||||
|
let result = highlight_syntax(&self.engine_state, &self.stack, line, cursor);
|
||||||
|
result.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn highlight_syntax(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
line: &str,
|
||||||
|
cursor: usize,
|
||||||
|
) -> HighlightResult {
|
||||||
trace!("highlighting: {}", line);
|
trace!("highlighting: {}", line);
|
||||||
|
|
||||||
let config = self.stack.get_config(&self.engine_state);
|
let config = stack.get_config(engine_state);
|
||||||
let highlight_resolved_externals = config.highlight_resolved_externals;
|
let highlight_resolved_externals = config.highlight_resolved_externals;
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||||
let (shapes, global_span_offset) = {
|
let (shapes, global_span_offset) = {
|
||||||
let mut shapes = flatten_block(&working_set, &block);
|
let mut shapes = flatten_block(&working_set, &block);
|
||||||
@ -35,11 +55,8 @@ impl Highlighter for NuHighlighter {
|
|||||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||||
|
|
||||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||||
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
|
let paths = env::path_str(engine_state, stack, *span).ok();
|
||||||
#[allow(deprecated)]
|
let res = if let Ok(cwd) = engine_state.cwd(Some(stack)) {
|
||||||
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()
|
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||||
} else {
|
} else {
|
||||||
which::which_in_global(str_word, paths.as_ref())
|
which::which_in_global(str_word, paths.as_ref())
|
||||||
@ -52,13 +69,13 @@ impl Highlighter for NuHighlighter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(shapes, self.engine_state.next_span_start())
|
(shapes, engine_state.next_span_start())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output = StyledText::default();
|
let mut result = HighlightResult::default();
|
||||||
let mut last_seen_span = global_span_offset;
|
let mut last_seen_span = global_span_offset;
|
||||||
|
|
||||||
let global_cursor_offset = _cursor + global_span_offset;
|
let global_cursor_offset = cursor + global_span_offset;
|
||||||
let matching_brackets_pos = find_matching_brackets(
|
let matching_brackets_pos = find_matching_brackets(
|
||||||
line,
|
line,
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -80,18 +97,28 @@ impl Highlighter for NuHighlighter {
|
|||||||
let gap = line
|
let gap = line
|
||||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||||
.to_string();
|
.to_string();
|
||||||
output.push((Style::new(), gap));
|
result.text.push((Style::new(), gap));
|
||||||
}
|
}
|
||||||
let next_token = line
|
let next_token = line
|
||||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||||
output.push((get_shape_color(shape.as_str(), &config), text));
|
result
|
||||||
|
.text
|
||||||
|
.push((get_shape_color(shape.as_str(), &config), text));
|
||||||
};
|
};
|
||||||
|
|
||||||
match shape.1 {
|
match shape.1 {
|
||||||
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
|
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)
|
||||||
|
}
|
||||||
FlatShape::Nothing => 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::Binary => add_colored_token(&shape.1, next_token),
|
||||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||||
@ -131,7 +158,7 @@ impl Highlighter for NuHighlighter {
|
|||||||
if highlight {
|
if highlight {
|
||||||
style = get_matching_brackets_style(style, &config);
|
style = get_matching_brackets_style(style, &config);
|
||||||
}
|
}
|
||||||
output.push((style, text));
|
result.text.push((style, text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,11 +180,10 @@ impl Highlighter for NuHighlighter {
|
|||||||
|
|
||||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||||
if !remainder.is_empty() {
|
if !remainder.is_empty() {
|
||||||
output.push((Style::new(), remainder));
|
result.text.push((Style::new(), remainder));
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
result
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_span_by_highlight_positions(
|
fn split_span_by_highlight_positions(
|
||||||
|
@ -9,14 +9,16 @@ use std::{
|
|||||||
use nu_cli::NuCompleter;
|
use nu_cli::NuCompleter;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::expand_tilde;
|
use nu_path::{AbsolutePathBuf, expand_tilde};
|
||||||
use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet};
|
use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet};
|
||||||
use nu_std::load_standard_library;
|
use nu_std::load_standard_library;
|
||||||
|
use nu_test_support::fs;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use support::{
|
use support::{
|
||||||
completions_helpers::{
|
completions_helpers::{
|
||||||
new_dotnu_engine, new_external_engine, new_partial_engine, new_quote_engine,
|
new_dotnu_engine, new_engine_helper, new_external_engine, new_partial_engine,
|
||||||
|
new_quote_engine,
|
||||||
},
|
},
|
||||||
file, folder, match_suggestions, match_suggestions_by_string, new_engine,
|
file, folder, match_suggestions, match_suggestions_by_string, new_engine,
|
||||||
};
|
};
|
||||||
@ -123,7 +125,7 @@ fn custom_completer_with_options(
|
|||||||
global_opts,
|
global_opts,
|
||||||
completions
|
completions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|comp| format!("'{}'", comp))
|
.map(|comp| format!("'{comp}'"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
completer_opts,
|
completer_opts,
|
||||||
@ -716,6 +718,16 @@ fn external_completer_fallback() {
|
|||||||
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
||||||
let suggestions = run_external_completion(block, input);
|
let suggestions = run_external_completion(block, input);
|
||||||
match_suggestions_by_string(&expected, &suggestions);
|
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`
|
/// Fallback to external completions for flags of `sudo`
|
||||||
@ -2103,11 +2115,15 @@ fn alias_of_another_alias() {
|
|||||||
match_suggestions(&expected_paths, &suggestions)
|
match_suggestions(&expected_paths, &suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
fn run_external_completion_within_pwd(
|
||||||
|
completer: &str,
|
||||||
|
input: &str,
|
||||||
|
pwd: AbsolutePathBuf,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
let completer = format!("$env.config.completions.external.completer = {completer}");
|
let completer = format!("$env.config.completions.external.completer = {completer}");
|
||||||
|
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (_, _, mut engine_state, mut stack) = new_engine();
|
let (_, _, mut engine_state, mut stack) = new_engine_helper(pwd);
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
let block = parse(&mut working_set, None, completer.as_bytes(), false);
|
let block = parse(&mut working_set, None, completer.as_bytes(), false);
|
||||||
@ -2131,6 +2147,10 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
|||||||
completer.complete(input, input.len())
|
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]
|
#[test]
|
||||||
fn unknown_command_completion() {
|
fn unknown_command_completion() {
|
||||||
let (_, _, engine, stack) = new_engine();
|
let (_, _, engine, stack) = new_engine();
|
||||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ version = "0.105.1"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.1" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.105.1" }
|
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return, redirect_env};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||||
@ -325,19 +325,7 @@ fn run_hook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If all went fine, preserve the environment of the called block
|
// If all went fine, preserve the environment of the called block
|
||||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
redirect_env(engine_state, stack, &callee_stack);
|
||||||
|
|
||||||
// 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)
|
Ok(pipeline_data)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
nu-json = { version = "0.105.1", path = "../nu-json" }
|
nu-json = { version = "0.105.2", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.1" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-pretty-hex = { version = "0.105.1", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.105.2", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.1", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
@ -37,6 +37,6 @@ itertools = { workspace = true }
|
|||||||
mime = { workspace = true }
|
mime = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||||
nu-command = { path = "../nu-command", version = "0.105.1" }
|
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.105.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||||
|
@ -12,7 +12,10 @@ impl Command for UpdateCells {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("update cells")
|
Signature::build("update cells")
|
||||||
.input_output_types(vec![(Type::table(), Type::table())])
|
.input_output_types(vec![
|
||||||
|
(Type::table(), Type::table()),
|
||||||
|
(Type::record(), Type::record()),
|
||||||
|
])
|
||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
@ -77,6 +80,15 @@ impl Command for UpdateCells {
|
|||||||
"2021-11-18" => Value::test_string(""),
|
"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),
|
||||||
|
})),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +97,7 @@ impl Command for UpdateCells {
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
mut input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let closure: Closure = call.req(engine_state, stack, 0)?;
|
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||||
@ -102,14 +114,51 @@ impl Command for UpdateCells {
|
|||||||
|
|
||||||
let metadata = input.metadata();
|
let metadata = input.metadata();
|
||||||
|
|
||||||
Ok(UpdateCellIterator {
|
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(),
|
iter: input.into_iter(),
|
||||||
closure: ClosureEval::new(engine_state, stack, closure),
|
closure: ClosureEval::new(engine_state, stack, closure),
|
||||||
columns,
|
columns,
|
||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
.into_pipeline_data(head, engine_state.signals().clone())
|
.into_pipeline_data(head, engine_state.signals().clone())
|
||||||
.set_metadata(metadata))
|
.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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,18 +177,7 @@ impl Iterator for UpdateCellIterator {
|
|||||||
|
|
||||||
let value = if let Value::Record { val, .. } = &mut value {
|
let value = if let Value::Record { val, .. } = &mut value {
|
||||||
let val = val.to_mut();
|
let val = val.to_mut();
|
||||||
if let Some(columns) = &self.columns {
|
update_record(val, &mut self.closure, self.span, self.columns.as_ref());
|
||||||
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
|
value
|
||||||
} else {
|
} else {
|
||||||
eval_value(&mut self.closure, self.span, value)
|
eval_value(&mut self.closure, self.span, value)
|
||||||
|
@ -188,7 +188,7 @@ fn get_theme_from_asset_file(
|
|||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
return Err(ShellError::TypeMismatch {
|
return Err(ShellError::TypeMismatch {
|
||||||
err_message: format!("Unknown HTML theme '{}'", theme_name),
|
err_message: format!("Unknown HTML theme '{theme_name}'"),
|
||||||
span: theme_span,
|
span: theme_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -774,8 +774,7 @@ mod tests {
|
|||||||
for key in required_keys {
|
for key in required_keys {
|
||||||
assert!(
|
assert!(
|
||||||
theme_map.contains_key(key),
|
theme_map.contains_key(key),
|
||||||
"Expected theme to contain key '{}'",
|
"Expected theme to contain key '{key}'"
|
||||||
key
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -792,15 +791,13 @@ mod tests {
|
|||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
assert!(
|
assert!(
|
||||||
matches!(err, ShellError::TypeMismatch { .. }),
|
matches!(err, ShellError::TypeMismatch { .. }),
|
||||||
"Expected TypeMismatch error, got: {:?}",
|
"Expected TypeMismatch error, got: {err:?}"
|
||||||
err
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let ShellError::TypeMismatch { err_message, span } = err {
|
if let ShellError::TypeMismatch { err_message, span } = err {
|
||||||
assert!(
|
assert!(
|
||||||
err_message.contains("doesnt-exist"),
|
err_message.contains("doesnt-exist"),
|
||||||
"Error message should mention theme name, got: {}",
|
"Error message should mention theme name, got: {err_message}"
|
||||||
err_message
|
|
||||||
);
|
);
|
||||||
assert_eq!(span.start, 0);
|
assert_eq!(span.start, 0);
|
||||||
assert_eq!(span.end, 13);
|
assert_eq!(span.end, 13);
|
||||||
|
@ -161,28 +161,28 @@ fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
|||||||
let bytes = v.to_ne_bytes();
|
let bytes = v.to_ne_bytes();
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in bytes {
|
for ch in bytes {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
} else if let Some(v) = num.to_i16() {
|
} else if let Some(v) = num.to_i16() {
|
||||||
let bytes = v.to_ne_bytes();
|
let bytes = v.to_ne_bytes();
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in bytes {
|
for ch in bytes {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
} else if let Some(v) = num.to_i32() {
|
} else if let Some(v) = num.to_i32() {
|
||||||
let bytes = v.to_ne_bytes();
|
let bytes = v.to_ne_bytes();
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in bytes {
|
for ch in bytes {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
} else {
|
} else {
|
||||||
let bytes = num.to_ne_bytes();
|
let bytes = num.to_ne_bytes();
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in bytes {
|
for ch in bytes {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
|||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in val {
|
for ch in val {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
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 raw_bytes = val.as_bytes();
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
for ch in raw_bytes {
|
for ch in raw_bytes {
|
||||||
raw_string.push_str(&format!("{:08b} ", ch));
|
raw_string.push_str(&format!("{ch:08b} "));
|
||||||
}
|
}
|
||||||
Value::string(raw_string.trim(), span)
|
Value::string(raw_string.trim(), span)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,11 @@ impl Command for FormatNumber {
|
|||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("format number")
|
Signature::build("format number")
|
||||||
.input_output_types(vec![(Type::Number, Type::record())])
|
.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)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,20 +29,36 @@ impl Command for FormatNumber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Get a record containing multiple formats for the number 42",
|
description: "Get a record containing multiple formats for the number 42",
|
||||||
example: "42 | format number",
|
example: "42 | format number",
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_record(record! {
|
||||||
"binary" => Value::test_string("0b101010"),
|
|
||||||
"debug" => Value::test_string("42"),
|
"debug" => Value::test_string("42"),
|
||||||
"display" => Value::test_string("42"),
|
"display" => Value::test_string("42"),
|
||||||
|
"binary" => Value::test_string("0b101010"),
|
||||||
"lowerexp" => Value::test_string("4.2e1"),
|
"lowerexp" => Value::test_string("4.2e1"),
|
||||||
"lowerhex" => Value::test_string("0x2a"),
|
|
||||||
"octal" => Value::test_string("0o52"),
|
|
||||||
"upperexp" => Value::test_string("4.2E1"),
|
"upperexp" => Value::test_string("4.2E1"),
|
||||||
|
"lowerhex" => Value::test_string("0x2a"),
|
||||||
"upperhex" => 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"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -59,14 +80,24 @@ pub(crate) fn format_number(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
let args = CellPathOnlyArgs::from(cell_paths);
|
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 {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Float { val, .. } => format_f64(*val, span),
|
Value::Float { val, .. } => format_f64(*val, false, span),
|
||||||
Value::Int { val, .. } => format_i64(*val, span),
|
Value::Int { val, .. } => format_i64(*val, false, span),
|
||||||
Value::Filesize { val, .. } => format_i64(val.get(), span),
|
Value::Filesize { val, .. } => format_i64(val.get(), false, span),
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
@ -81,33 +112,80 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_i64(num: i64, 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 {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"binary" => Value::string(format!("{num:#b}"), span),
|
|
||||||
"debug" => Value::string(format!("{num:#?}"), span),
|
"debug" => Value::string(format!("{num:#?}"), span),
|
||||||
"display" => 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),
|
"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),
|
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||||
"upperhex" => Value::string(format!("{num:#X}"), 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,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_f64(num: f64, span: Span) -> Value {
|
fn format_f64(num: f64, no_prefix: bool, span: Span) -> Value {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
|
||||||
"debug" => Value::string(format!("{num:#?}"), span),
|
"debug" => Value::string(format!("{num:#?}"), span),
|
||||||
"display" => 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),
|
"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),
|
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||||
"upperhex" => Value::string(format!("{:0X}", num.to_bits()), 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,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -15,17 +15,17 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.1" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.1", 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.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "1.1", default-features = false }
|
shadow-rs = { version = "1.2", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
|
shadow-rs = { version = "1.2", default-features = false, features = ["build"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = { workspace = true }
|
quickcheck = { workspace = true }
|
||||||
@ -43,8 +43,3 @@ plugin = [
|
|||||||
"nu-protocol/plugin",
|
"nu-protocol/plugin",
|
||||||
"os",
|
"os",
|
||||||
]
|
]
|
||||||
|
|
||||||
trash-support = []
|
|
||||||
sqlite = []
|
|
||||||
static-link-openssl = []
|
|
||||||
system-clipboard = []
|
|
||||||
|
@ -296,7 +296,7 @@ fn run(
|
|||||||
} else {
|
} else {
|
||||||
let value = stream.into_value();
|
let value = stream.into_value();
|
||||||
let base_description = value.get_type().to_string();
|
let base_description = value.get_type().to_string();
|
||||||
Value::string(format!("{} (stream)", base_description), head)
|
Value::string(format!("{base_description} (stream)"), head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::Value(value, ..) => {
|
PipelineData::Value(value, ..) => {
|
||||||
|
@ -229,7 +229,7 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
|
|||||||
error: "invalid error format.".into(),
|
error: "invalid error format.".into(),
|
||||||
msg: "`$.label.start` should be smaller than `$.label.end`".into(),
|
msg: "`$.label.start` should be smaller than `$.label.end`".into(),
|
||||||
span: Some(label_span),
|
span: Some(label_span),
|
||||||
help: Some(format!("{} > {}", span_start, span_end)),
|
help: Some(format!("{span_start} > {span_end}")),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -69,5 +69,5 @@ pub use return_::Return;
|
|||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use try_::Try;
|
pub use try_::Try;
|
||||||
pub use use_::Use;
|
pub use use_::Use;
|
||||||
pub use version::Version;
|
pub use version::{VERSION_NU_FEATURES, Version};
|
||||||
pub use while_::While;
|
pub use while_::While;
|
||||||
|
@ -86,12 +86,16 @@ impl Command for OverlayHide {
|
|||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// also restore env vars which has been hidden
|
||||||
|
let env_vars_to_restore = stack.get_hidden_env_vars(&overlay_name.item, engine_state);
|
||||||
stack.remove_overlay(&overlay_name.item);
|
stack.remove_overlay(&overlay_name.item);
|
||||||
|
for (name, val) in env_vars_to_restore {
|
||||||
|
stack.add_env_var(name, val);
|
||||||
|
}
|
||||||
|
|
||||||
for (name, val) in env_vars_to_keep {
|
for (name, val) in env_vars_to_keep {
|
||||||
stack.add_env_var(name, val);
|
stack.add_env_var(name, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,48 @@
|
|||||||
use std::sync::OnceLock;
|
use std::{borrow::Cow, sync::OnceLock};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use shadow_rs::shadow;
|
use shadow_rs::shadow;
|
||||||
|
|
||||||
shadow!(build);
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Version;
|
pub struct Version;
|
||||||
|
|
||||||
@ -113,7 +150,17 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
|||||||
|
|
||||||
record.push(
|
record.push(
|
||||||
"features",
|
"features",
|
||||||
Value::string(features_enabled().join(", "), span),
|
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,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "plugin"))]
|
#[cfg(not(feature = "plugin"))]
|
||||||
@ -164,42 +211,12 @@ fn global_allocator() -> &'static str {
|
|||||||
"standard"
|
"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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use super::Version;
|
use super::Version;
|
||||||
use crate::test_examples;
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Value::Bool { val, .. } => {
|
Value::Bool { val, .. } => {
|
||||||
write!(f, "{:?}", val)
|
write!(f, "{val:?}")
|
||||||
}
|
}
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
write!(f, "{:?}", val)
|
write!(f, "{val:?}")
|
||||||
}
|
}
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
write!(f, "{:?}f", val)
|
write!(f, "{val:?}f")
|
||||||
}
|
}
|
||||||
Value::Filesize { val, .. } => {
|
Value::Filesize { val, .. } => {
|
||||||
write!(f, "Filesize({:?})", val)
|
write!(f, "Filesize({val:?})")
|
||||||
}
|
}
|
||||||
Value::Duration { val, .. } => {
|
Value::Duration { val, .. } => {
|
||||||
let duration = std::time::Duration::from_nanos(*val as u64);
|
let duration = std::time::Duration::from_nanos(*val as u64);
|
||||||
write!(f, "Duration({:?})", duration)
|
write!(f, "Duration({duration:?})")
|
||||||
}
|
}
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => {
|
||||||
write!(f, "Date({:?})", val)
|
write!(f, "Date({val:?})")
|
||||||
}
|
}
|
||||||
Value::Range { val, .. } => match **val {
|
Value::Range { val, .. } => match **val {
|
||||||
Range::IntRange(range) => match range.end() {
|
Range::IntRange(range) => match range.end() {
|
||||||
@ -280,7 +280,7 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Value::String { val, .. } | Value::Glob { val, .. } => {
|
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||||
write!(f, "{:?}", val)
|
write!(f, "{val:?}")
|
||||||
}
|
}
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
write!(f, "{{")?;
|
write!(f, "{{")?;
|
||||||
@ -305,22 +305,22 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
|||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
}
|
}
|
||||||
Value::Closure { val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
write!(f, "Closure({:?})", val)
|
write!(f, "Closure({val:?})")
|
||||||
}
|
}
|
||||||
Value::Nothing { .. } => {
|
Value::Nothing { .. } => {
|
||||||
write!(f, "Nothing")
|
write!(f, "Nothing")
|
||||||
}
|
}
|
||||||
Value::Error { error, .. } => {
|
Value::Error { error, .. } => {
|
||||||
write!(f, "Error({:?})", error)
|
write!(f, "Error({error:?})")
|
||||||
}
|
}
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
write!(f, "Binary({:?})", val)
|
write!(f, "Binary({val:?})")
|
||||||
}
|
}
|
||||||
Value::CellPath { val, .. } => {
|
Value::CellPath { val, .. } => {
|
||||||
write!(f, "CellPath({:?})", val.to_string())
|
write!(f, "CellPath({:?})", val.to_string())
|
||||||
}
|
}
|
||||||
Value::Custom { val, .. } => {
|
Value::Custom { val, .. } => {
|
||||||
write!(f, "CustomValue({:?})", val)
|
write!(f, "CustomValue({val:?})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-plugin"
|
name = "nu-cmd-plugin"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ version = "0.105.1"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1" }
|
nu-engine = { path = "../nu-engine", version = "0.105.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.105.1" }
|
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", features = ["plugin"] }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["plugin"] }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.1" }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2" }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-color-config"
|
name = "nu-color-config"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -14,12 +14,12 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
nu-json = { path = "../nu-json", version = "0.105.1" }
|
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.105.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.105.1"
|
version = "0.105.2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -16,21 +16,21 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.105.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.1", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.105.1" }
|
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||||
nu-json = { path = "../nu-json", version = "0.105.1" }
|
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.1" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.105.1" }
|
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.1", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
nu-system = { path = "../nu-system", version = "0.105.1" }
|
nu-system = { path = "../nu-system", version = "0.105.2" }
|
||||||
nu-table = { path = "../nu-table", version = "0.105.1" }
|
nu-table = { path = "../nu-table", version = "0.105.2" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.105.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.1", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
nuon = { path = "../nuon", version = "0.105.1" }
|
nuon = { path = "../nuon", version = "0.105.2" }
|
||||||
|
|
||||||
alphanumeric-sort = { workspace = true }
|
alphanumeric-sort = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
@ -226,8 +226,8 @@ sqlite = ["rusqlite"]
|
|||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.105.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||||
|
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
mockito = { workspace = true, default-features = false }
|
mockito = { workspace = true, default-features = false }
|
||||||
|
@ -89,7 +89,7 @@ impl Command for Histogram {
|
|||||||
"frequency-column-name can't be {}",
|
"frequency-column-name can't be {}",
|
||||||
forbidden_column_names
|
forbidden_column_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|val| format!("'{}'", val))
|
.map(|val| format!("'{val}'"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
),
|
),
|
||||||
|
@ -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 {
|
let value = match input {
|
||||||
Value::Binary { .. } => input.clone(),
|
Value::Binary { .. } => input.clone(),
|
||||||
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
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();
|
let val_span = value.span();
|
||||||
if let Value::Binary { val, .. } = value {
|
if let Value::Binary { val, .. } = value {
|
||||||
let val = if cfg!(target_endian = "little") {
|
let val = if cfg!(target_endian = "little") {
|
||||||
|
@ -678,7 +678,7 @@ fn parse_value_from_record_as_u32(
|
|||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
if *val < 0 || *val > u32::MAX as i64 {
|
if *val < 0 || *val > u32::MAX as i64 {
|
||||||
return Err(ShellError::IncorrectValue {
|
return Err(ShellError::IncorrectValue {
|
||||||
msg: format!("incorrect value for {}", col),
|
msg: format!("incorrect value for {col}"),
|
||||||
val_span: *head,
|
val_span: *head,
|
||||||
call_span: *span,
|
call_span: *span,
|
||||||
});
|
});
|
||||||
|
@ -368,8 +368,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellE
|
|||||||
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
||||||
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
||||||
return Err(ShellError::IncorrectValue {
|
return Err(ShellError::IncorrectValue {
|
||||||
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
|
msg: format!("Invalid sign. Allowed signs are {allowed_signs}").to_string(),
|
||||||
.to_string(),
|
|
||||||
val_span: sign.span(),
|
val_span: sign.span(),
|
||||||
call_span: head,
|
call_span: head,
|
||||||
});
|
});
|
||||||
|
@ -122,8 +122,8 @@ impl Table {
|
|||||||
.conn
|
.conn
|
||||||
.query_row(&table_exists_query, [], |row| row.get(0))
|
.query_row(&table_exists_query, [], |row| row.get(0))
|
||||||
.map_err(|err| ShellError::GenericError {
|
.map_err(|err| ShellError::GenericError {
|
||||||
error: format!("{:#?}", err),
|
error: format!("{err:#?}"),
|
||||||
msg: format!("{:#?}", err),
|
msg: format!("{err:#?}"),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: Vec::new(),
|
inner: Vec::new(),
|
||||||
@ -257,7 +257,7 @@ fn insert_in_transaction(
|
|||||||
let insert_statement = format!(
|
let insert_statement = format!(
|
||||||
"INSERT INTO [{}] ({}) VALUES ({})",
|
"INSERT INTO [{}] ({}) VALUES ({})",
|
||||||
table_name,
|
table_name,
|
||||||
Itertools::intersperse(val.columns().map(|c| format!("`{}`", c)), ", ".to_string())
|
Itertools::intersperse(val.columns().map(|c| format!("`{c}`")), ", ".to_string())
|
||||||
.collect::<String>(),
|
.collect::<String>(),
|
||||||
Itertools::intersperse(itertools::repeat_n("?", val.len()), ", ").collect::<String>(),
|
Itertools::intersperse(itertools::repeat_n("?", val.len()), ", ").collect::<String>(),
|
||||||
);
|
);
|
||||||
@ -381,7 +381,7 @@ fn get_columns_with_sqlite_types(
|
|||||||
.map(|name| (format!("`{}`", name.0), name.1))
|
.map(|name| (format!("`{}`", name.0), name.1))
|
||||||
.any(|(name, _)| name == *c)
|
.any(|(name, _)| name == *c)
|
||||||
{
|
{
|
||||||
columns.push((format!("`{}`", c), nu_value_to_sqlite_type(v)?));
|
columns.push((format!("`{c}`"), nu_value_to_sqlite_type(v)?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,16 +112,31 @@ impl SQLiteDatabase {
|
|||||||
if self.path == PathBuf::from(MEMORY_DB) {
|
if self.path == PathBuf::from(MEMORY_DB) {
|
||||||
open_connection_in_memory_custom()
|
open_connection_in_memory_custom()
|
||||||
} else {
|
} else {
|
||||||
Connection::open(&self.path).map_err(|e| ShellError::GenericError {
|
let conn = Connection::open(&self.path).map_err(|e| ShellError::GenericError {
|
||||||
error: "Failed to open SQLite database from open_connection".into(),
|
error: "Failed to open SQLite database from open_connection".into(),
|
||||||
msg: e.to_string(),
|
msg: e.to_string(),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
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> {
|
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, SqliteError> {
|
||||||
let mut table_names =
|
let mut table_names =
|
||||||
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
||||||
@ -158,7 +173,7 @@ impl SQLiteDatabase {
|
|||||||
filename: String,
|
filename: String,
|
||||||
) -> Result<(), SqliteError> {
|
) -> Result<(), SqliteError> {
|
||||||
//vacuum main into 'c:\\temp\\foo.db'
|
//vacuum main into 'c:\\temp\\foo.db'
|
||||||
conn.execute(&format!("vacuum main into '{}'", filename), [])?;
|
conn.execute(&format!("vacuum main into '{filename}'"), [])?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -668,13 +683,23 @@ pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
|||||||
|
|
||||||
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
|
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
|
||||||
let flags = OpenFlags::default();
|
let flags = OpenFlags::default();
|
||||||
|
let conn =
|
||||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||||
error: "Failed to open SQLite custom connection in memory".into(),
|
error: "Failed to open SQLite custom connection in memory".into(),
|
||||||
msg: e.to_string(),
|
msg: e.to_string(),
|
||||||
span: Some(Span::test_data()),
|
span: Some(Span::test_data()),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
||||||
|
@ -87,10 +87,9 @@ impl Command for Metadata {
|
|||||||
.into_pipeline_data(),
|
.into_pipeline_data(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => Ok(
|
None => {
|
||||||
Value::record(build_metadata_record(input.metadata().as_ref(), head), head)
|
Ok(Value::record(build_metadata_record(&input, head), head).into_pipeline_data())
|
||||||
.into_pipeline_data(),
|
}
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,19 +115,7 @@ fn build_metadata_record_value(
|
|||||||
head: Span,
|
head: Span,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let mut record = Record::new();
|
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)
|
Value::record(extend_record_with_metadata(record, metadata, head), head)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +42,7 @@ impl Command for MetadataAccess {
|
|||||||
// `ClosureEvalOnce` is not used as it uses `Stack::captures_to_stack` rather than
|
// `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
|
// `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 mut callee_stack = caller_stack.captures_to_stack_preserve_out_dest(closure.captures);
|
||||||
let metadata_record = Value::record(
|
let metadata_record = Value::record(build_metadata_record(&input, call.head), call.head);
|
||||||
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) {
|
if let Some(var_id) = block.signature.get_positional(0).and_then(|var| var.var_id) {
|
||||||
callee_stack.add_var(var_id, metadata_record)
|
callee_stack.add_var(var_id, metadata_record)
|
||||||
@ -58,12 +55,10 @@ impl Command for MetadataAccess {
|
|||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Access metadata and data from a stream together",
|
description: "Access metadata and data from a stream together",
|
||||||
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, meta: $meta}}"#,
|
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, content: $meta.content_type}}"#,
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_record(record! {
|
||||||
"in" => Value::test_string(r#"{"foo":"bar"}"#),
|
"in" => Value::test_string(r#"{"foo":"bar"}"#),
|
||||||
"meta" => Value::test_record(record! {
|
"content" => Value::test_string(r#"application/json"#)
|
||||||
"content_type" => Value::test_string(r#"application/json"#)
|
|
||||||
})
|
|
||||||
})),
|
})),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -79,15 +79,13 @@ impl Command for MetadataSet {
|
|||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Set the metadata of a file path",
|
description: "Set the metadata of a file path",
|
||||||
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata",
|
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates'",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Set the metadata of a file path",
|
description: "Set the metadata of a file path",
|
||||||
example: "'crates' | metadata set --content-type text/plain | metadata",
|
example: "'crates' | metadata set --content-type text/plain | metadata | get content_type",
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_string("text/plain")),
|
||||||
"content_type" => Value::test_string("text/plain"),
|
|
||||||
})),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use nu_protocol::{DataSource, PipelineMetadata, Record, Span, Value};
|
use nu_protocol::{DataSource, IntoValue, PipelineData, PipelineMetadata, Record, Span, Value};
|
||||||
|
|
||||||
pub fn extend_record_with_metadata(
|
pub fn extend_record_with_metadata(
|
||||||
mut record: Record,
|
mut record: Record,
|
||||||
@ -29,6 +29,10 @@ pub fn extend_record_with_metadata(
|
|||||||
record
|
record
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_metadata_record(metadata: Option<&PipelineMetadata>, head: Span) -> Record {
|
pub fn build_metadata_record(pipeline: &PipelineData, head: Span) -> Record {
|
||||||
extend_record_with_metadata(Record::new(), metadata, head)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -126,10 +126,10 @@ impl Command for ViewSource {
|
|||||||
}
|
}
|
||||||
let _ = write!(&mut final_contents, "--{}", n.long);
|
let _ = write!(&mut final_contents, "--{}", n.long);
|
||||||
if let Some(short) = n.short {
|
if let Some(short) = n.short {
|
||||||
let _ = write!(&mut final_contents, "(-{})", short);
|
let _ = write!(&mut final_contents, "(-{short})");
|
||||||
}
|
}
|
||||||
if let Some(arg) = &n.arg {
|
if let Some(arg) = &n.arg {
|
||||||
let _ = write!(&mut final_contents, ": {}", arg);
|
let _ = write!(&mut final_contents, ": {arg}");
|
||||||
}
|
}
|
||||||
final_contents.push(' ');
|
final_contents.push(' ');
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ impl Command for ViewSource {
|
|||||||
let mut c = 0;
|
let mut c = 0;
|
||||||
for (insig, outsig) in type_signatures {
|
for (insig, outsig) in type_signatures {
|
||||||
c += 1;
|
c += 1;
|
||||||
let s = format!("{} -> {}", insig, outsig);
|
let s = format!("{insig} -> {outsig}");
|
||||||
final_contents.push_str(&s);
|
final_contents.push_str(&s);
|
||||||
if c != len {
|
if c != len {
|
||||||
final_contents.push_str(", ")
|
final_contents.push_str(", ")
|
||||||
|
@ -188,6 +188,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
bind_command! {
|
bind_command! {
|
||||||
|
Ansi,
|
||||||
|
AnsiLink,
|
||||||
|
AnsiStrip,
|
||||||
Char,
|
Char,
|
||||||
Decode,
|
Decode,
|
||||||
Encode,
|
Encode,
|
||||||
@ -250,9 +253,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
// Platform
|
// Platform
|
||||||
#[cfg(feature = "os")]
|
#[cfg(feature = "os")]
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Ansi,
|
|
||||||
AnsiLink,
|
|
||||||
AnsiStrip,
|
|
||||||
Clear,
|
Clear,
|
||||||
Du,
|
Du,
|
||||||
Input,
|
Input,
|
||||||
|
@ -112,8 +112,8 @@ impl Command for Mktemp {
|
|||||||
.map_err(|_| ShellError::NonUtf8 { span })?,
|
.map_err(|_| ShellError::NonUtf8 { span })?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: format!("{}", e),
|
error: format!("{e}"),
|
||||||
msg: format!("{}", e),
|
msg: format!("{e}"),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
|
@ -198,7 +198,7 @@ impl Command for Open {
|
|||||||
let converter = exts_opt.and_then(|exts| {
|
let converter = exts_opt.and_then(|exts| {
|
||||||
exts.iter().find_map(|ext| {
|
exts.iter().find_map(|ext| {
|
||||||
engine_state
|
engine_state
|
||||||
.find_decl(format!("from {}", ext).as_bytes(), &[])
|
.find_decl(format!("from {ext}").as_bytes(), &[])
|
||||||
.map(|id| (id, ext.to_string()))
|
.map(|id| (id, ext.to_string()))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -314,7 +314,7 @@ fn extract_extensions(filename: &str) -> Vec<String> {
|
|||||||
if current_extension.is_empty() {
|
if current_extension.is_empty() {
|
||||||
current_extension.push_str(part);
|
current_extension.push_str(part);
|
||||||
} else {
|
} else {
|
||||||
current_extension = format!("{}.{}", part, current_extension);
|
current_extension = format!("{part}.{current_extension}");
|
||||||
}
|
}
|
||||||
extensions.push(current_extension.clone());
|
extensions.push(current_extension.clone());
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,8 @@ impl Command for Save {
|
|||||||
PipelineData::ByteStream(stream, metadata) => {
|
PipelineData::ByteStream(stream, metadata) => {
|
||||||
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
||||||
|
|
||||||
let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
let (file, stderr_file) =
|
||||||
|
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||||
|
|
||||||
let size = stream.known_size();
|
let size = stream.known_size();
|
||||||
let signals = engine_state.signals();
|
let signals = engine_state.signals();
|
||||||
@ -201,7 +202,8 @@ impl Command for Save {
|
|||||||
stderr_path.as_ref(),
|
stderr_path.as_ref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
let (mut file, _) =
|
||||||
|
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||||
for val in ls {
|
for val in ls {
|
||||||
file.write_all(&value_to_bytes(val)?)
|
file.write_all(&value_to_bytes(val)?)
|
||||||
.map_err(&from_io_error)?;
|
.map_err(&from_io_error)?;
|
||||||
@ -226,7 +228,8 @@ impl Command for Save {
|
|||||||
input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?;
|
input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?;
|
||||||
|
|
||||||
// Only open file after successful conversion
|
// Only open file after successful conversion
|
||||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
let (mut file, _) =
|
||||||
|
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
|
||||||
|
|
||||||
file.write_all(&bytes).map_err(&from_io_error)?;
|
file.write_all(&bytes).map_err(&from_io_error)?;
|
||||||
file.flush().map_err(&from_io_error)?;
|
file.flush().map_err(&from_io_error)?;
|
||||||
@ -422,13 +425,14 @@ fn prepare_path(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
|
fn open_file(
|
||||||
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
|
engine_state: &EngineState,
|
||||||
{
|
path: &Path,
|
||||||
(true, true) => std::fs::OpenOptions::new()
|
span: Span,
|
||||||
.append(true)
|
append: bool,
|
||||||
.open(path)
|
) -> Result<File, ShellError> {
|
||||||
.map_err(|err| err.into()),
|
let file: std::io::Result<File> = match (append, path.exists()) {
|
||||||
|
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
|
||||||
_ => {
|
_ => {
|
||||||
// This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893)
|
// 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
|
// A TOCTOU problem exists here, which may cause wrong error message to be shown
|
||||||
@ -438,22 +442,51 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError>
|
|||||||
deprecated,
|
deprecated,
|
||||||
reason = "we don't get a IsADirectory error, so we need to provide it"
|
reason = "we don't get a IsADirectory error, so we need to provide it"
|
||||||
)]
|
)]
|
||||||
Err(nu_protocol::shell_error::io::ErrorKind::from_std(
|
Err(std::io::ErrorKind::IsADirectory.into())
|
||||||
std::io::ErrorKind::IsADirectory,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
std::fs::File::create(path).map_err(|err| err.into())
|
std::fs::File::create(path)
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
std::fs::File::create(path).map_err(|err| err.into())
|
std::fs::File::create(path)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
file.map_err(|err_kind| ShellError::Io(IoError::new(err_kind, span, PathBuf::from(path))))
|
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))))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get output file and optional stderr file
|
/// Get output file and optional stderr file
|
||||||
fn get_files(
|
fn get_files(
|
||||||
|
engine_state: &EngineState,
|
||||||
path: &Spanned<PathBuf>,
|
path: &Spanned<PathBuf>,
|
||||||
stderr_path: Option<&Spanned<PathBuf>>,
|
stderr_path: Option<&Spanned<PathBuf>>,
|
||||||
append: bool,
|
append: bool,
|
||||||
@ -467,7 +500,7 @@ fn get_files(
|
|||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
// Only if both files can be used open and possibly truncate them
|
// Only if both files can be used open and possibly truncate them
|
||||||
let file = open_file(path, path_span, append)?;
|
let file = open_file(engine_state, path, path_span, append)?;
|
||||||
|
|
||||||
let stderr_file = stderr_path_and_span
|
let stderr_file = stderr_path_and_span
|
||||||
.map(|(stderr_path, stderr_path_span)| {
|
.map(|(stderr_path, stderr_path_span)| {
|
||||||
@ -480,7 +513,7 @@ fn get_files(
|
|||||||
inner: vec![],
|
inner: vec![],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
open_file(stderr_path, stderr_path_span, append)
|
open_file(engine_state, stderr_path, stderr_path_span, append)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
@ -49,7 +49,8 @@ impl Command for Start {
|
|||||||
}
|
}
|
||||||
// If it's not a URL, treat it as a file path
|
// If it's not a URL, treat it as a file path
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
let cwd = engine_state.cwd(Some(stack))?;
|
||||||
let full_path = cwd.join(path_no_whitespace);
|
let full_path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
||||||
|
|
||||||
// Check if the path exists or if it's a valid file/directory
|
// Check if the path exists or if it's a valid file/directory
|
||||||
if full_path.exists() {
|
if full_path.exists() {
|
||||||
open_path(full_path, engine_state, stack, path.span)?;
|
open_path(full_path, engine_state, stack, path.span)?;
|
||||||
|
@ -272,8 +272,8 @@ impl Command for UCp {
|
|||||||
uu_cp::Error::NotAllFilesCopied => {}
|
uu_cp::Error::NotAllFilesCopied => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: format!("{}", error),
|
error: format!("{error}"),
|
||||||
msg: format!("{}", error),
|
msg: format!("{error}"),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
@ -373,7 +373,7 @@ fn parse_and_set_attribute(
|
|||||||
"xattr" => &mut attribute.xattr,
|
"xattr" => &mut attribute.xattr,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
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(),
|
span: value.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ impl Command for UMkdir {
|
|||||||
for dir in directories {
|
for dir in directories {
|
||||||
if let Err(error) = mkdir(&dir, IS_RECURSIVE, get_mode(), is_verbose) {
|
if let Err(error) = mkdir(&dir, IS_RECURSIVE, get_mode(), is_verbose) {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: format!("{}", error),
|
error: format!("{error}"),
|
||||||
msg: format!("{}", error),
|
msg: format!("{error}"),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
|
@ -195,8 +195,8 @@ impl Command for UMv {
|
|||||||
};
|
};
|
||||||
if let Err(error) = uu_mv::mv(&files, &options) {
|
if let Err(error) = uu_mv::mv(&files, &options) {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: format!("{}", error),
|
error: format!("{error}"),
|
||||||
msg: format!("{}", error),
|
msg: format!("{error}"),
|
||||||
span: None,
|
span: None,
|
||||||
help: None,
|
help: None,
|
||||||
inner: Vec::new(),
|
inner: Vec::new(),
|
||||||
|
@ -220,7 +220,7 @@ impl Command for UTouch {
|
|||||||
inner: Vec::new(),
|
inner: Vec::new(),
|
||||||
},
|
},
|
||||||
TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue {
|
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"),
|
val_span: date_span.expect("touch should've been given a date"),
|
||||||
call_span: call.head,
|
call_span: call.head,
|
||||||
},
|
},
|
||||||
|
@ -8,7 +8,7 @@ use notify_debouncer_full::{
|
|||||||
use nu_engine::{ClosureEval, command_prelude::*};
|
use nu_engine::{ClosureEval, command_prelude::*};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Closure, StateWorkingSet},
|
engine::{Closure, StateWorkingSet},
|
||||||
format_shell_error,
|
format_cli_error,
|
||||||
shell_error::io::IoError,
|
shell_error::io::IoError,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
@ -208,7 +208,7 @@ impl Command for Watch {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
eprintln!("{}", format_shell_error(&working_set, &err));
|
eprintln!("{}", format_cli_error(&working_set, &err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ fn default(
|
|||||||
|| (default_when_empty
|
|| (default_when_empty
|
||||||
&& matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
|
&& matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
|
||||||
{
|
{
|
||||||
default_value.pipeline_data()
|
default_value.single_run_pipeline_data()
|
||||||
} else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
|
} else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
|
||||||
let PipelineData::ListStream(ls, metadata) = input else {
|
let PipelineData::ListStream(ls, metadata) = input else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@ -221,7 +221,7 @@ fn default(
|
|||||||
let span = ls.span();
|
let span = ls.span();
|
||||||
let mut stream = ls.into_inner().peekable();
|
let mut stream = ls.into_inner().peekable();
|
||||||
if stream.peek().is_none() {
|
if stream.peek().is_none() {
|
||||||
return default_value.pipeline_data();
|
return default_value.single_run_pipeline_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
// stream's internal state already preserves the original signals config, so if this
|
// stream's internal state already preserves the original signals config, so if this
|
||||||
@ -278,8 +278,14 @@ impl DefaultValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pipeline_data(&mut self) -> Result<PipelineData, ShellError> {
|
/// Used when we know the value won't need to be cached to allow streaming.
|
||||||
self.value().map(|x| x.into_pipeline_data())
|
fn single_run_pipeline_data(self) -> Result<PipelineData, ShellError> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Uncalculated(mut closure) => {
|
||||||
|
closure.item.run_with_input(PipelineData::Empty)
|
||||||
|
}
|
||||||
|
DefaultValue::Calculated(val) => Ok(val.into_pipeline_data()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +334,7 @@ fn closure_variable_warning(
|
|||||||
Cow::Owned(s) if s.deref() == "$carapace_completer" => {
|
Cow::Owned(s) if s.deref() == "$carapace_completer" => {
|
||||||
carapace_suggestion.to_string()
|
carapace_suggestion.to_string()
|
||||||
}
|
}
|
||||||
_ => format!("change this to {{ {} }}", span_contents).to_string(),
|
_ => format!("change this to {{ {span_contents} }}").to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
report_shell_warning(
|
report_shell_warning(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use itertools::Either;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{PipelineIterator, Range};
|
use nu_protocol::{PipelineIterator, Range};
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -18,13 +18,11 @@ impl Command for DropNth {
|
|||||||
(Type::list(Type::Any), Type::list(Type::Any)),
|
(Type::list(Type::Any), Type::list(Type::Any)),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required(
|
.rest(
|
||||||
"row number or row range",
|
"rest",
|
||||||
// FIXME: we can make this accept either Int or Range when we can compose SyntaxShapes
|
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"The number of the row to drop or a range to drop consecutive rows.",
|
"The row numbers or ranges to drop.",
|
||||||
)
|
)
|
||||||
.rest("rest", SyntaxShape::Any, "The number of the row to drop.")
|
|
||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,110 +101,125 @@ impl Command for DropNth {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let metadata = input.metadata();
|
let metadata = input.metadata();
|
||||||
let number_or_range = extract_int_or_range(engine_state, stack, call)?;
|
|
||||||
|
|
||||||
let rows = match number_or_range.item {
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
Either::Left(row_number) => {
|
if args.is_empty() {
|
||||||
let and_rows: Vec<Spanned<i64>> = call.rest(engine_state, stack, 1)?;
|
return Ok(input);
|
||||||
let mut rows: Vec<_> = and_rows.into_iter().map(|x| x.item as usize).collect();
|
|
||||||
rows.push(row_number as usize);
|
|
||||||
rows.sort_unstable();
|
|
||||||
rows
|
|
||||||
}
|
|
||||||
Either::Right(Range::FloatRange(_)) => {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "float range".into(),
|
|
||||||
input: "value originates from here".into(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: number_or_range.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Either::Right(Range::IntRange(range)) => {
|
|
||||||
// check for negative range inputs, e.g., (2..-5)
|
|
||||||
let end_negative = match range.end() {
|
|
||||||
Bound::Included(end) | Bound::Excluded(end) => end < 0,
|
|
||||||
Bound::Unbounded => false,
|
|
||||||
};
|
|
||||||
if range.start().is_negative() || end_negative {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "drop nth accepts only positive ints".into(),
|
|
||||||
input: "value originates from here".into(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: number_or_range.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// check if the upper bound is smaller than the lower bound, e.g., do not accept 4..2
|
|
||||||
if range.step() < 0 {
|
|
||||||
return Err(ShellError::UnsupportedInput {
|
|
||||||
msg: "The upper bound needs to be equal or larger to the lower bound"
|
|
||||||
.into(),
|
|
||||||
input: "value originates from here".into(),
|
|
||||||
msg_span: head,
|
|
||||||
input_span: number_or_range.span,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = range.start() as usize;
|
let (rows_to_drop, min_unbounded_start) = get_rows_to_drop(&args, head)?;
|
||||||
|
|
||||||
let end = match range.end() {
|
let input = if let Some(cutoff) = min_unbounded_start {
|
||||||
Bound::Included(end) => end as usize,
|
input
|
||||||
Bound::Excluded(end) => (end - 1) as usize,
|
|
||||||
Bound::Unbounded => {
|
|
||||||
return Ok(input
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.take(start)
|
.take(cutoff)
|
||||||
.into_pipeline_data_with_metadata(
|
.into_pipeline_data_with_metadata(
|
||||||
head,
|
head,
|
||||||
engine_state.signals().clone(),
|
engine_state.signals().clone(),
|
||||||
metadata,
|
metadata.clone(),
|
||||||
));
|
)
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let end = if let PipelineData::Value(Value::List { vals, .. }, _) = &input {
|
|
||||||
end.min(vals.len() - 1)
|
|
||||||
} else {
|
} else {
|
||||||
end
|
input
|
||||||
};
|
|
||||||
|
|
||||||
(start..=end).collect()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(DropNthIterator {
|
Ok(DropNthIterator {
|
||||||
input: input.into_iter(),
|
input: input.into_iter(),
|
||||||
rows,
|
rows: rows_to_drop,
|
||||||
current: 0,
|
current: 0,
|
||||||
}
|
}
|
||||||
.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
|
.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_int_or_range(
|
fn get_rows_to_drop(
|
||||||
engine_state: &EngineState,
|
args: &[Value],
|
||||||
stack: &mut Stack,
|
head: Span,
|
||||||
call: &Call,
|
) -> Result<(VecDeque<usize>, Option<usize>), ShellError> {
|
||||||
) -> Result<Spanned<Either<i64, Range>>, ShellError> {
|
let mut rows_to_drop = Vec::new();
|
||||||
let value: Value = call.req(engine_state, stack, 0)?;
|
let mut min_unbounded_start: Option<usize> = None;
|
||||||
|
|
||||||
let int_opt = value.as_int().map(Either::Left).ok();
|
for value in args {
|
||||||
let range_opt = value.as_range().map(Either::Right).ok();
|
if let Ok(i) = value.as_int() {
|
||||||
|
if i < 0 {
|
||||||
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "drop nth accepts only positive ints".into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: value.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rows_to_drop.push(i as usize);
|
||||||
|
} else if let Ok(range) = value.as_range() {
|
||||||
|
match range {
|
||||||
|
Range::IntRange(range) => {
|
||||||
|
let start = range.start();
|
||||||
|
if start < 0 {
|
||||||
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "drop nth accepts only positive ints".into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: value.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
int_opt
|
match range.end() {
|
||||||
.or(range_opt)
|
Bound::Included(end) => {
|
||||||
.ok_or_else(|| ShellError::TypeMismatch {
|
if end < start {
|
||||||
err_message: "int or range".into(),
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "The upper bound must be greater than or equal to the lower bound".into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: value.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rows_to_drop.extend((start as usize)..=(end as usize));
|
||||||
|
}
|
||||||
|
Bound::Excluded(end) => {
|
||||||
|
if end <= start {
|
||||||
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "The upper bound must be greater than the lower bound"
|
||||||
|
.into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: value.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rows_to_drop.extend((start as usize)..(end as usize));
|
||||||
|
}
|
||||||
|
Bound::Unbounded => {
|
||||||
|
let start_usize = start as usize;
|
||||||
|
min_unbounded_start = Some(
|
||||||
|
min_unbounded_start.map_or(start_usize, |s| s.min(start_usize)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Range::FloatRange(_) => {
|
||||||
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "float range not supported".into(),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: value.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "Expected int or range".into(),
|
||||||
span: value.span(),
|
span: value.span(),
|
||||||
})
|
});
|
||||||
.map(|either| Spanned {
|
}
|
||||||
item: either,
|
}
|
||||||
span: value.span(),
|
|
||||||
})
|
rows_to_drop.sort_unstable();
|
||||||
|
rows_to_drop.dedup();
|
||||||
|
|
||||||
|
Ok((VecDeque::from(rows_to_drop), min_unbounded_start))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DropNthIterator {
|
struct DropNthIterator {
|
||||||
input: PipelineIterator,
|
input: PipelineIterator,
|
||||||
rows: Vec<usize>,
|
rows: VecDeque<usize>,
|
||||||
current: usize,
|
current: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,9 +228,9 @@ impl Iterator for DropNthIterator {
|
|||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(row) = self.rows.first() {
|
if let Some(row) = self.rows.front() {
|
||||||
if self.current == *row {
|
if self.current == *row {
|
||||||
self.rows.remove(0);
|
self.rows.pop_front();
|
||||||
self.current += 1;
|
self.current += 1;
|
||||||
let _ = self.input.next();
|
let _ = self.input.next();
|
||||||
continue;
|
continue;
|
||||||
|
@ -163,7 +163,12 @@ impl Command for Find {
|
|||||||
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find --regex "rr""#,
|
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find --regex "rr""#,
|
||||||
result: Some(Value::list(
|
result: Some(Value::list(
|
||||||
vec![Value::list(
|
vec![Value::list(
|
||||||
vec![Value::test_string("Larry"), Value::test_string("Moe")],
|
vec![
|
||||||
|
Value::test_string(
|
||||||
|
"\u{1b}[37mLa\u{1b}[0m\u{1b}[41;37mrr\u{1b}[0m\u{1b}[37my\u{1b}[0m",
|
||||||
|
),
|
||||||
|
Value::test_string("Moe"),
|
||||||
|
],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)],
|
)],
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
@ -344,7 +349,10 @@ fn get_match_pattern_from_arguments(
|
|||||||
// map functions
|
// map functions
|
||||||
|
|
||||||
fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
||||||
// strip haystack to remove existing ansi style
|
if !pattern.regex.is_match(&val).unwrap_or(false) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
let stripped_val = nu_utils::strip_ansi_string_unlikely(val);
|
let stripped_val = nu_utils::strip_ansi_string_unlikely(val);
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
let mut highlighted = String::new();
|
let mut highlighted = String::new();
|
||||||
@ -390,7 +398,7 @@ fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
|||||||
highlighted
|
highlighted
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_matches_in_record_or_value(
|
fn highlight_matches_in_value(
|
||||||
pattern: &MatchPattern,
|
pattern: &MatchPattern,
|
||||||
value: Value,
|
value: Value,
|
||||||
columns_to_search: &[String],
|
columns_to_search: &[String],
|
||||||
@ -412,16 +420,16 @@ fn highlight_matches_in_record_or_value(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Value::String { val: val_str, .. } = val {
|
*val = highlight_matches_in_value(pattern, std::mem::take(val), &[]);
|
||||||
if pattern.regex.is_match(val_str).unwrap_or(false) {
|
|
||||||
let val_str = std::mem::take(val_str);
|
|
||||||
*val = highlight_matches_in_string(pattern, val_str).into_value(span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::record(record, span)
|
Value::record(record, span)
|
||||||
}
|
}
|
||||||
|
Value::List { vals, .. } => vals
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| highlight_matches_in_value(pattern, item, &[]))
|
||||||
|
.collect::<Vec<Value>>()
|
||||||
|
.into_value(span),
|
||||||
Value::String { val, .. } => highlight_matches_in_string(pattern, val).into_value(span),
|
Value::String { val, .. } => highlight_matches_in_string(pattern, val).into_value(span),
|
||||||
_ => value,
|
_ => value,
|
||||||
}
|
}
|
||||||
@ -444,24 +452,22 @@ fn find_in_pipelinedata(
|
|||||||
PipelineData::Value(_, _) => input
|
PipelineData::Value(_, _) => input
|
||||||
.filter(
|
.filter(
|
||||||
move |value| {
|
move |value| {
|
||||||
record_or_value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||||
|
!= pattern.invert
|
||||||
},
|
},
|
||||||
engine_state.signals(),
|
engine_state.signals(),
|
||||||
)?
|
)?
|
||||||
.map(
|
.map(
|
||||||
move |x| {
|
move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search),
|
||||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
|
||||||
},
|
|
||||||
engine_state.signals(),
|
engine_state.signals(),
|
||||||
),
|
),
|
||||||
PipelineData::ListStream(stream, metadata) => {
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
let stream = stream.modify(|iter| {
|
let stream = stream.modify(|iter| {
|
||||||
iter.filter(move |value| {
|
iter.filter(move |value| {
|
||||||
record_or_value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||||
})
|
!= pattern.invert
|
||||||
.map(move |x| {
|
|
||||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
|
||||||
})
|
})
|
||||||
|
.map(move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search))
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(PipelineData::ListStream(stream, metadata))
|
Ok(PipelineData::ListStream(stream, metadata))
|
||||||
@ -495,7 +501,12 @@ fn string_should_be_printed(pattern: &MatchPattern, value: &str) -> bool {
|
|||||||
pattern.regex.is_match(value).unwrap_or(false)
|
pattern.regex.is_match(value).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Config) -> bool {
|
fn value_should_be_printed(
|
||||||
|
pattern: &MatchPattern,
|
||||||
|
value: &Value,
|
||||||
|
columns_to_search: &[String],
|
||||||
|
config: &Config,
|
||||||
|
) -> bool {
|
||||||
let lower_value = value.to_expanded_string("", config).to_lowercase();
|
let lower_value = value.to_expanded_string("", config).to_lowercase();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
@ -507,8 +518,7 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
|||||||
| Value::Range { .. }
|
| Value::Range { .. }
|
||||||
| Value::Float { .. }
|
| Value::Float { .. }
|
||||||
| Value::Closure { .. }
|
| Value::Closure { .. }
|
||||||
| Value::Nothing { .. }
|
| Value::Nothing { .. } => {
|
||||||
| Value::Error { .. } => {
|
|
||||||
if !pattern.lower_terms.is_empty() {
|
if !pattern.lower_terms.is_empty() {
|
||||||
// look for exact match when searching with terms
|
// look for exact match when searching with terms
|
||||||
pattern
|
pattern
|
||||||
@ -519,37 +529,25 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
|||||||
string_should_be_printed(pattern, &lower_value)
|
string_should_be_printed(pattern, &lower_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Glob { .. }
|
Value::Glob { .. } | Value::CellPath { .. } | Value::Custom { .. } => {
|
||||||
| Value::List { .. }
|
string_should_be_printed(pattern, &lower_value)
|
||||||
| Value::CellPath { .. }
|
}
|
||||||
| Value::Record { .. }
|
|
||||||
| Value::Custom { .. } => string_should_be_printed(pattern, &lower_value),
|
|
||||||
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
||||||
Value::Binary { .. } => false,
|
Value::List { vals, .. } => vals
|
||||||
}
|
.iter()
|
||||||
}
|
.any(|item| value_should_be_printed(pattern, item, &[], config)),
|
||||||
|
|
||||||
fn record_or_value_should_be_printed(
|
|
||||||
pattern: &MatchPattern,
|
|
||||||
value: &Value,
|
|
||||||
columns_to_search: &[String],
|
|
||||||
config: &Config,
|
|
||||||
) -> bool {
|
|
||||||
let match_found = match value {
|
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
// Only perform column selection if given columns.
|
|
||||||
let col_select = !columns_to_search.is_empty();
|
let col_select = !columns_to_search.is_empty();
|
||||||
record.iter().any(|(col, val)| {
|
record.iter().any(|(col, val)| {
|
||||||
if col_select && !columns_to_search.contains(col) {
|
if col_select && !columns_to_search.contains(col) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value_should_be_printed(pattern, val, config)
|
value_should_be_printed(pattern, val, &[], config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => value_should_be_printed(pattern, value, config),
|
Value::Binary { .. } => false,
|
||||||
};
|
Value::Error { .. } => true,
|
||||||
|
}
|
||||||
match_found != pattern.invert
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility
|
// utility
|
||||||
@ -574,6 +572,46 @@ fn split_string_if_multiline(input: PipelineData, head_span: Span) -> PipelineDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// function for using find from other commands
|
||||||
|
pub fn find_internal(
|
||||||
|
input: PipelineData,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
search_term: &str,
|
||||||
|
columns_to_search: &[&str],
|
||||||
|
highlight: bool,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = input.span().unwrap_or(Span::unknown());
|
||||||
|
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", span));
|
||||||
|
let highlight_style =
|
||||||
|
style_computer.compute("search_result", &Value::string("search result", span));
|
||||||
|
|
||||||
|
let regex_str = format!("(?i){}", escape(search_term));
|
||||||
|
|
||||||
|
let regex = Regex::new(regex_str.as_str()).map_err(|e| ShellError::TypeMismatch {
|
||||||
|
err_message: format!("invalid regex: {e}"),
|
||||||
|
span: Span::unknown(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pattern = MatchPattern {
|
||||||
|
regex,
|
||||||
|
lower_terms: vec![search_term.to_lowercase()],
|
||||||
|
highlight,
|
||||||
|
invert: false,
|
||||||
|
string_style,
|
||||||
|
highlight_style,
|
||||||
|
};
|
||||||
|
|
||||||
|
let columns_to_search = columns_to_search
|
||||||
|
.iter()
|
||||||
|
.map(|str| String::from(*str))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
find_in_pipelinedata(pattern, columns_to_search, engine_state, stack, input)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -379,10 +379,7 @@ fn merge_records(left: &Record, right: &Record, shared_key: Option<&str>) -> Rec
|
|||||||
let k_shared = shared_key == Some(k.as_str());
|
let k_shared = shared_key == Some(k.as_str());
|
||||||
// Do not output shared join key twice
|
// Do not output shared join key twice
|
||||||
if !(k_seen && k_shared) {
|
if !(k_seen && k_shared) {
|
||||||
record.push(
|
record.push(if k_seen { format!("{k}_") } else { k.clone() }, v.clone());
|
||||||
if k_seen { format!("{}_", k) } else { k.clone() },
|
|
||||||
v.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record
|
record
|
||||||
|
@ -70,7 +70,7 @@ pub use empty::empty;
|
|||||||
pub use enumerate::Enumerate;
|
pub use enumerate::Enumerate;
|
||||||
pub use every::Every;
|
pub use every::Every;
|
||||||
pub use filter::Filter;
|
pub use filter::Filter;
|
||||||
pub use find::Find;
|
pub use find::{Find, find_internal};
|
||||||
pub use first::First;
|
pub use first::First;
|
||||||
pub use flatten::Flatten;
|
pub use flatten::Flatten;
|
||||||
pub use get::Get;
|
pub use get::Get;
|
||||||
|
@ -203,6 +203,7 @@ mod test {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -221,6 +222,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromCsv {}));
|
working_set.add_decl(Box::new(FromCsv {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -229,7 +231,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#""a,b\n1,2" | metadata set --content-type 'text/csv' --datasource-ls | from csv | metadata | $in"#;
|
let cmd = r#""a,b\n1,2" | metadata set --content-type 'text/csv' --datasource-ls | from csv | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -248,6 +248,7 @@ fn convert_string_to_value_strict(string_input: &str, span: Span) -> Result<Valu
|
|||||||
mod test {
|
mod test {
|
||||||
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
||||||
|
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -268,6 +269,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromJson {}));
|
working_set.add_decl(Box::new(FromJson {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -276,7 +278,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#"'{"a":1,"b":2}' | metadata set --content-type 'application/json' --datasource-ls | from json | metadata | $in"#;
|
let cmd = r#"'{"a":1,"b":2}' | metadata set --content-type 'application/json' --datasource-ls | from json | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -212,7 +212,7 @@ impl From<ReadError> for ShellError {
|
|||||||
},
|
},
|
||||||
ReadError::TypeMismatch(marker, span) => ShellError::GenericError {
|
ReadError::TypeMismatch(marker, span) => ShellError::GenericError {
|
||||||
error: "Invalid marker while reading MessagePack data".into(),
|
error: "Invalid marker while reading MessagePack data".into(),
|
||||||
msg: format!("unexpected {:?} in data", marker),
|
msg: format!("unexpected {marker:?} in data"),
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
@ -514,6 +514,7 @@ fn assert_eof(input: &mut impl io::Read, span: Span) -> Result<(), ShellError> {
|
|||||||
mod test {
|
mod test {
|
||||||
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
||||||
|
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet, ToMsgpack};
|
use crate::{Metadata, MetadataSet, ToMsgpack};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -535,6 +536,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromMsgpack {}));
|
working_set.add_decl(Box::new(FromMsgpack {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -543,7 +545,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#"{a: 1 b: 2} | to msgpack | metadata set --datasource-ls | from msgpack | metadata | $in"#;
|
let cmd = r#"{a: 1 b: 2} | to msgpack | metadata set --datasource-ls | from msgpack | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -74,6 +74,7 @@ impl Command for FromNuon {
|
|||||||
mod test {
|
mod test {
|
||||||
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
||||||
|
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -94,6 +95,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromNuon {}));
|
working_set.add_decl(Box::new(FromNuon {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -102,7 +104,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#"'[[a, b]; [1, 2]]' | metadata set --content-type 'application/x-nuon' --datasource-ls | from nuon | metadata | $in"#;
|
let cmd = r#"'[[a, b]; [1, 2]]' | metadata set --content-type 'application/x-nuon' --datasource-ls | from nuon | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -167,7 +167,7 @@ fn parse_aligned_columns<'a>(
|
|||||||
let headers: Vec<(String, usize)> = indices
|
let headers: Vec<(String, usize)> = indices
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, position)| (format!("column{}", i), *position))
|
.map(|(i, position)| (format!("column{i}"), *position))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
construct(ls.iter().map(|s| s.to_owned()), headers)
|
construct(ls.iter().map(|s| s.to_owned()), headers)
|
||||||
|
@ -145,6 +145,7 @@ pub fn convert_string_to_value(string_input: String, span: Span) -> Result<Value
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -345,6 +346,7 @@ mod tests {
|
|||||||
working_set.add_decl(Box::new(FromToml {}));
|
working_set.add_decl(Box::new(FromToml {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -353,7 +355,7 @@ mod tests {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#""[a]\nb = 1\nc = 1" | metadata set --content-type 'text/x-toml' --datasource-ls | from toml | metadata | $in"#;
|
let cmd = r#""[a]\nb = 1\nc = 1" | metadata set --content-type 'text/x-toml' --datasource-ls | from toml | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -160,6 +160,7 @@ fn from_tsv(
|
|||||||
mod test {
|
mod test {
|
||||||
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
|
||||||
|
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -180,6 +181,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromTsv {}));
|
working_set.add_decl(Box::new(FromTsv {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -188,7 +190,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#""a\tb\n1\t2" | metadata set --content-type 'text/tab-separated-values' --datasource-ls | from tsv | metadata | $in"#;
|
let cmd = r#""a\tb\n1\t2" | metadata set --content-type 'text/tab-separated-values' --datasource-ls | from tsv | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -252,7 +252,7 @@ fn process_xml_parse_error(source: String, err: roxmltree::Error, span: Span) ->
|
|||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
roxmltree::Error::UnknownNamespace(prefix, pos) => {
|
roxmltree::Error::UnknownNamespace(prefix, pos) => {
|
||||||
make_xml_error_spanned(format!("Unknown prefix {}", prefix), source, pos)
|
make_xml_error_spanned(format!("Unknown prefix {prefix}"), source, pos)
|
||||||
}
|
}
|
||||||
roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => make_xml_error_spanned(
|
roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => make_xml_error_spanned(
|
||||||
format!("Unexpected close tag {actual}, expected {expected}"),
|
format!("Unexpected close tag {actual}, expected {expected}"),
|
||||||
@ -370,6 +370,7 @@ fn make_xml_error_spanned(msg: impl Into<String>, src: String, pos: TextPos) ->
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::Metadata;
|
use crate::Metadata;
|
||||||
use crate::MetadataSet;
|
use crate::MetadataSet;
|
||||||
|
use crate::Reject;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -541,6 +542,7 @@ mod tests {
|
|||||||
working_set.add_decl(Box::new(FromXml {}));
|
working_set.add_decl(Box::new(FromXml {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -552,7 +554,7 @@ mod tests {
|
|||||||
let cmd = r#"'<?xml version="1.0" encoding="UTF-8"?>
|
let cmd = r#"'<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<note>
|
<note>
|
||||||
<remember>Event</remember>
|
<remember>Event</remember>
|
||||||
</note>' | metadata set --content-type 'application/xml' --datasource-ls | from xml | metadata | $in"#;
|
</note>' | metadata set --content-type 'application/xml' --datasource-ls | from xml | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -158,27 +158,26 @@ fn convert_yaml_value_to_nu_value(
|
|||||||
}
|
}
|
||||||
serde_yaml::Value::Tagged(t) => {
|
serde_yaml::Value::Tagged(t) => {
|
||||||
let tag = &t.tag;
|
let tag = &t.tag;
|
||||||
let value = match &t.value {
|
|
||||||
|
match &t.value {
|
||||||
serde_yaml::Value::String(s) => {
|
serde_yaml::Value::String(s) => {
|
||||||
let val = format!("{} {}", tag, s).trim().to_string();
|
let val = format!("{tag} {s}").trim().to_string();
|
||||||
Value::string(val, span)
|
Value::string(val, span)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Number(n) => {
|
serde_yaml::Value::Number(n) => {
|
||||||
let val = format!("{} {}", tag, n).trim().to_string();
|
let val = format!("{tag} {n}").trim().to_string();
|
||||||
Value::string(val, span)
|
Value::string(val, span)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Bool(b) => {
|
serde_yaml::Value::Bool(b) => {
|
||||||
let val = format!("{} {}", tag, b).trim().to_string();
|
let val = format!("{tag} {b}").trim().to_string();
|
||||||
Value::string(val, span)
|
Value::string(val, span)
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Null => {
|
serde_yaml::Value::Null => {
|
||||||
let val = format!("{}", tag).trim().to_string();
|
let val = format!("{tag}").trim().to_string();
|
||||||
Value::string(val, span)
|
Value::string(val, span)
|
||||||
}
|
}
|
||||||
v => convert_yaml_value_to_nu_value(v, span, val_span)?,
|
v => convert_yaml_value_to_nu_value(v, span, val_span)?,
|
||||||
};
|
}
|
||||||
|
|
||||||
value
|
|
||||||
}
|
}
|
||||||
serde_yaml::Value::Null => Value::nothing(span),
|
serde_yaml::Value::Null => Value::nothing(span),
|
||||||
x => unimplemented!("Unsupported YAML case: {:?}", x),
|
x => unimplemented!("Unsupported YAML case: {:?}", x),
|
||||||
@ -244,6 +243,7 @@ fn from_yaml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::Reject;
|
||||||
use crate::{Metadata, MetadataSet};
|
use crate::{Metadata, MetadataSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -410,6 +410,7 @@ mod test {
|
|||||||
working_set.add_decl(Box::new(FromYaml {}));
|
working_set.add_decl(Box::new(FromYaml {}));
|
||||||
working_set.add_decl(Box::new(Metadata {}));
|
working_set.add_decl(Box::new(Metadata {}));
|
||||||
working_set.add_decl(Box::new(MetadataSet {}));
|
working_set.add_decl(Box::new(MetadataSet {}));
|
||||||
|
working_set.add_decl(Box::new(Reject {}));
|
||||||
|
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
@ -418,7 +419,7 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = r#""a: 1\nb: 2" | metadata set --content-type 'application/yaml' --datasource-ls | from yaml | metadata | $in"#;
|
let cmd = r#""a: 1\nb: 2" | metadata set --content-type 'application/yaml' --datasource-ls | from yaml | metadata | reject span | $in"#;
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
|
@ -166,14 +166,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to csv | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to csv | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("text/csv"))),
|
Value::test_string("text/csv"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ fn make_unsupported_input_error(
|
|||||||
) -> ShellError {
|
) -> ShellError {
|
||||||
ShellError::UnsupportedInput {
|
ShellError::UnsupportedInput {
|
||||||
msg: "expected table or record".to_string(),
|
msg: "expected table or record".to_string(),
|
||||||
input: format!("input type: {}", r#type),
|
input: format!("input type: {type}"),
|
||||||
msg_span: head,
|
msg_span: head,
|
||||||
input_span: span,
|
input_span: span,
|
||||||
}
|
}
|
||||||
|
@ -229,14 +229,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to json | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to json | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("application/json"))),
|
Value::test_string("application/json"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ fn table(
|
|||||||
escaped_rows.push(escaped_row);
|
escaped_rows.push(escaped_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_string = if (column_widths.is_empty() || column_widths.iter().all(|x| *x == 0))
|
if (column_widths.is_empty() || column_widths.iter().all(|x| *x == 0))
|
||||||
&& escaped_rows.is_empty()
|
&& escaped_rows.is_empty()
|
||||||
{
|
{
|
||||||
String::from("")
|
String::from("")
|
||||||
@ -260,9 +260,7 @@ fn table(
|
|||||||
)
|
)
|
||||||
.trim()
|
.trim()
|
||||||
.to_string()
|
.to_string()
|
||||||
};
|
}
|
||||||
|
|
||||||
output_string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineData, bool) {
|
pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineData, bool) {
|
||||||
@ -906,14 +904,14 @@ mod tests {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to md | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to md | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("text/markdown"))),
|
Value::test_string("text/markdown"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -348,16 +348,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to msgpack | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to msgpack | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(
|
Value::test_string("application/x-msgpack"),
|
||||||
record!("content_type" => Value::test_string("application/x-msgpack"))
|
|
||||||
),
|
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -143,14 +143,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to nuon | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to nuon | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("application/x-nuon"))),
|
Value::test_string("application/x-nuon"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use chrono::Datelike;
|
|||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{ByteStream, PipelineMetadata, format_duration, shell_error::io::IoError};
|
use nu_protocol::{ByteStream, PipelineMetadata, format_duration, shell_error::io::IoError};
|
||||||
|
use nu_utils::ObviousFloat;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
||||||
@ -164,7 +165,7 @@ fn local_into_string(
|
|||||||
match value {
|
match value {
|
||||||
Value::Bool { val, .. } => val.to_string(),
|
Value::Bool { val, .. } => val.to_string(),
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::Float { val, .. } => val.to_string(),
|
Value::Float { val, .. } => ObviousFloat(val).to_string(),
|
||||||
Value::Filesize { val, .. } => val.to_string(),
|
Value::Filesize { val, .. } => val.to_string(),
|
||||||
Value::Duration { val, .. } => format_duration(val),
|
Value::Duration { val, .. } => format_duration(val),
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => {
|
||||||
@ -272,14 +273,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to text | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to text | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("text/plain"))),
|
Value::test_string("text/plain"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -132,16 +132,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to tsv | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to tsv | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(
|
Value::test_string("text/tab-separated-values"),
|
||||||
record!("content_type" => Value::test_string("text/tab-separated-values"))
|
|
||||||
),
|
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -210,8 +210,7 @@ impl Job {
|
|||||||
from_type: "record".into(),
|
from_type: "record".into(),
|
||||||
span: entry_span,
|
span: entry_span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
"Invalid column \"{}\" in xml entry. Only \"{}\", \"{}\" and \"{}\" are permitted",
|
"Invalid column \"{bad_column}\" in xml entry. Only \"{COLUMN_TAG_NAME}\", \"{COLUMN_ATTRS_NAME}\" and \"{COLUMN_CONTENT_NAME}\" are permitted"
|
||||||
bad_column, COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME
|
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -399,7 +398,7 @@ impl Job {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_text = format!("{} {}", tag, content);
|
let content_text = format!("{tag} {content}");
|
||||||
// PI content must NOT be escaped
|
// PI content must NOT be escaped
|
||||||
// https://www.w3.org/TR/xml/#sec-pi
|
// https://www.w3.org/TR/xml/#sec-pi
|
||||||
let pi_content = BytesPI::new(content_text.as_str());
|
let pi_content = BytesPI::new(content_text.as_str());
|
||||||
@ -428,8 +427,7 @@ impl Job {
|
|||||||
from_type: Type::record().to_string(),
|
from_type: Type::record().to_string(),
|
||||||
span: tag_span,
|
span: tag_span,
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
"Incorrect tag name {}, tag name can not start with ! or ?",
|
"Incorrect tag name {tag}, tag name can not start with ! or ?"
|
||||||
tag
|
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -540,14 +538,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attributes: null content : Event}]}]} | to xml | metadata | get content_type";
|
let cmd = "{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attributes: null content : Event}]}]} | to xml | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("application/xml"))),
|
Value::test_string("application/xml"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -233,14 +233,14 @@ mod test {
|
|||||||
.merge_delta(delta)
|
.merge_delta(delta)
|
||||||
.expect("Error merging delta");
|
.expect("Error merging delta");
|
||||||
|
|
||||||
let cmd = "{a: 1 b: 2} | to yaml | metadata | get content_type";
|
let cmd = "{a: 1 b: 2} | to yaml | metadata | get content_type | $in";
|
||||||
let result = eval_pipeline_without_terminal_expression(
|
let result = eval_pipeline_without_terminal_expression(
|
||||||
cmd,
|
cmd,
|
||||||
std::env::temp_dir().as_ref(),
|
std::env::temp_dir().as_ref(),
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::test_record(record!("content_type" => Value::test_string("application/yaml"))),
|
Value::test_string("application/yaml"),
|
||||||
result.expect("There should be a result")
|
result.expect("There should be a result")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ fn parse_closure_result(
|
|||||||
} else {
|
} else {
|
||||||
let error = ShellError::GenericError {
|
let error = ShellError::GenericError {
|
||||||
error: "Invalid block return".into(),
|
error: "Invalid block return".into(),
|
||||||
msg: format!("Unexpected record key '{}'", k),
|
msg: format!("Unexpected record key '{k}'"),
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
use crate::help::{help_aliases, help_commands, help_modules};
|
use crate::help::{help_aliases, help_commands, help_modules};
|
||||||
use fancy_regex::{Regex, escape};
|
|
||||||
use nu_ansi_term::Style;
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_utils::IgnoreCaseExt;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Help;
|
pub struct Help;
|
||||||
@ -125,132 +122,3 @@ You can also learn more at https://www.nushell.sh/book/"#;
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highlight_search_in_table(
|
|
||||||
table: Vec<Value>, // list of records
|
|
||||||
search_string: &str,
|
|
||||||
searched_cols: &[&str],
|
|
||||||
string_style: &Style,
|
|
||||||
highlight_style: &Style,
|
|
||||||
) -> Result<Vec<Value>, ShellError> {
|
|
||||||
let orig_search_string = search_string;
|
|
||||||
let search_string = search_string.to_folded_case();
|
|
||||||
let mut matches = vec![];
|
|
||||||
|
|
||||||
for mut value in table {
|
|
||||||
let Value::Record {
|
|
||||||
val: ref mut record,
|
|
||||||
..
|
|
||||||
} = value
|
|
||||||
else {
|
|
||||||
return Err(ShellError::NushellFailedSpanned {
|
|
||||||
msg: "Expected record".to_string(),
|
|
||||||
label: format!("got {}", value.get_type()),
|
|
||||||
span: value.span(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let has_match = record.to_mut().iter_mut().try_fold(
|
|
||||||
false,
|
|
||||||
|acc: bool, (col, val)| -> Result<bool, ShellError> {
|
|
||||||
if !searched_cols.contains(&col.as_str()) {
|
|
||||||
// don't search this column
|
|
||||||
return Ok(acc);
|
|
||||||
}
|
|
||||||
let span = val.span();
|
|
||||||
if let Value::String { val: s, .. } = val {
|
|
||||||
if s.to_folded_case().contains(&search_string) {
|
|
||||||
*val = Value::string(
|
|
||||||
highlight_search_string(
|
|
||||||
s,
|
|
||||||
orig_search_string,
|
|
||||||
string_style,
|
|
||||||
highlight_style,
|
|
||||||
)?,
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// column does not contain the searched string
|
|
||||||
// ignore non-string values
|
|
||||||
Ok(acc)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if has_match {
|
|
||||||
matches.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(matches)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
|
||||||
pub fn highlight_search_string(
|
|
||||||
haystack: &str,
|
|
||||||
needle: &str,
|
|
||||||
string_style: &Style,
|
|
||||||
highlight_style: &Style,
|
|
||||||
) -> Result<String, ShellError> {
|
|
||||||
let escaped_needle = escape(needle);
|
|
||||||
let regex_string = format!("(?i){escaped_needle}");
|
|
||||||
let regex = match Regex::new(®ex_string) {
|
|
||||||
Ok(regex) => regex,
|
|
||||||
Err(err) => {
|
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: "Could not compile regex".into(),
|
|
||||||
msg: err.to_string(),
|
|
||||||
span: Some(Span::test_data()),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// strip haystack to remove existing ansi style
|
|
||||||
let stripped_haystack = nu_utils::strip_ansi_string_unlikely(haystack.to_string());
|
|
||||||
let mut last_match_end = 0;
|
|
||||||
let mut highlighted = String::new();
|
|
||||||
|
|
||||||
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
|
||||||
match cap {
|
|
||||||
Ok(capture) => {
|
|
||||||
let start = match capture.get(0) {
|
|
||||||
Some(acap) => acap.start(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
let end = match capture.get(0) {
|
|
||||||
Some(acap) => acap.end(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
highlighted.push_str(
|
|
||||||
&string_style
|
|
||||||
.paint(&stripped_haystack[last_match_end..start])
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
highlighted.push_str(
|
|
||||||
&highlight_style
|
|
||||||
.paint(&stripped_haystack[start..end])
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
last_match_end = end;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: "Error with regular expression capture".into(),
|
|
||||||
msg: e.to_string(),
|
|
||||||
span: None,
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
highlighted.push_str(
|
|
||||||
&string_style
|
|
||||||
.paint(&stripped_haystack[last_match_end..])
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
Ok(highlighted)
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::help::highlight_search_in_table;
|
use crate::filters::find_internal;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
||||||
use nu_engine::{command_prelude::*, scope::ScopeData};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HelpAliases;
|
pub struct HelpAliases;
|
||||||
@ -72,31 +71,20 @@ pub fn help_aliases(
|
|||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
// 🚩The following two-lines are copied from filters/find.rs:
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
||||||
// Currently, search results all use the same style.
|
|
||||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
|
||||||
// defined for "string").
|
|
||||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
||||||
let highlight_style =
|
|
||||||
style_computer.compute("search_result", &Value::string("search result", head));
|
|
||||||
|
|
||||||
if let Some(f) = find {
|
if let Some(f) = find {
|
||||||
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||||
let found_cmds_vec = highlight_search_in_table(
|
return find_internal(
|
||||||
all_cmds_vec,
|
all_cmds_vec,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
&f.item,
|
&f.item,
|
||||||
&["name", "description"],
|
&["name", "description"],
|
||||||
&string_style,
|
true,
|
||||||
&highlight_style,
|
);
|
||||||
)?;
|
|
||||||
|
|
||||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
Ok(build_help_aliases(engine_state, stack, head))
|
||||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
||||||
@ -113,50 +101,25 @@ pub fn help_aliases(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(alias) = engine_state.get_decl(alias).as_alias() else {
|
let alias = engine_state.get_decl(alias);
|
||||||
|
|
||||||
|
if alias.as_alias().is_none() {
|
||||||
return Err(ShellError::AliasNotFound {
|
return Err(ShellError::AliasNotFound {
|
||||||
span: Span::merge_many(rest.iter().map(|s| s.span)),
|
span: Span::merge_many(rest.iter().map(|s| s.span)),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let alias_expansion =
|
let help = get_full_help(alias, engine_state, stack);
|
||||||
String::from_utf8_lossy(engine_state.get_span_contents(alias.wrapped_call.span));
|
|
||||||
let description = alias.description();
|
|
||||||
let extra_desc = alias.extra_description();
|
|
||||||
|
|
||||||
// TODO: merge this into documentation.rs at some point
|
Ok(Value::string(help, call.head).into_pipeline_data())
|
||||||
const G: &str = "\x1b[32m"; // green
|
|
||||||
const C: &str = "\x1b[36m"; // cyan
|
|
||||||
const RESET: &str = "\x1b[0m"; // reset
|
|
||||||
|
|
||||||
let mut long_desc = String::new();
|
|
||||||
|
|
||||||
long_desc.push_str(description);
|
|
||||||
long_desc.push_str("\n\n");
|
|
||||||
|
|
||||||
if !extra_desc.is_empty() {
|
|
||||||
long_desc.push_str(extra_desc);
|
|
||||||
long_desc.push_str("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
|
||||||
long_desc.push_str("\n\n");
|
|
||||||
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
|
||||||
|
|
||||||
let config = stack.get_config(engine_state);
|
|
||||||
if !config.use_ansi_coloring.get(engine_state) {
|
|
||||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Value::string(long_desc, call.head).into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
|
||||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
scope_data.populate_decls();
|
scope_data.populate_decls();
|
||||||
|
|
||||||
scope_data.collect_aliases(span)
|
Value::list(scope_data.collect_aliases(span), span).into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::help::highlight_search_in_table;
|
use crate::filters::find_internal;
|
||||||
use nu_color_config::StyleComputer;
|
|
||||||
use nu_engine::{command_prelude::*, get_full_help};
|
use nu_engine::{command_prelude::*, get_full_help};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -52,31 +51,20 @@ pub fn help_commands(
|
|||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
// 🚩The following two-lines are copied from filters/find.rs:
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
||||||
// Currently, search results all use the same style.
|
|
||||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
|
||||||
// defined for "string").
|
|
||||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
||||||
let highlight_style =
|
|
||||||
style_computer.compute("search_result", &Value::string("search result", head));
|
|
||||||
|
|
||||||
if let Some(f) = find {
|
if let Some(f) = find {
|
||||||
let all_cmds_vec = build_help_commands(engine_state, head);
|
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||||
let found_cmds_vec = highlight_search_in_table(
|
return find_internal(
|
||||||
all_cmds_vec,
|
all_cmds_vec,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
&f.item,
|
&f.item,
|
||||||
&["name", "description", "search_terms"],
|
&["name", "description", "search_terms"],
|
||||||
&string_style,
|
true,
|
||||||
&highlight_style,
|
);
|
||||||
)?;
|
|
||||||
|
|
||||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
let found_cmds_vec = build_help_commands(engine_state, head);
|
Ok(build_help_commands(engine_state, head))
|
||||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
||||||
@ -99,7 +87,7 @@ pub fn help_commands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
fn build_help_commands(engine_state: &EngineState, span: Span) -> PipelineData {
|
||||||
let commands = engine_state.get_decls_sorted(false);
|
let commands = engine_state.get_decls_sorted(false);
|
||||||
let mut found_cmds_vec = Vec::new();
|
let mut found_cmds_vec = Vec::new();
|
||||||
|
|
||||||
@ -156,7 +144,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
|||||||
for named_param in &sig.named {
|
for named_param in &sig.named {
|
||||||
let name = if let Some(short) = named_param.short {
|
let name = if let Some(short) = named_param.short {
|
||||||
if named_param.long.is_empty() {
|
if named_param.long.is_empty() {
|
||||||
format!("-{}", short)
|
format!("-{short}")
|
||||||
} else {
|
} else {
|
||||||
format!("--{}(-{})", named_param.long, short)
|
format!("--{}(-{})", named_param.long, short)
|
||||||
}
|
}
|
||||||
@ -215,7 +203,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
|||||||
found_cmds_vec.push(Value::record(record, span));
|
found_cmds_vec.push(Value::record(record, span));
|
||||||
}
|
}
|
||||||
|
|
||||||
found_cmds_vec
|
Value::list(found_cmds_vec, span).into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::help::highlight_search_in_table;
|
use crate::filters::find_internal;
|
||||||
use nu_color_config::StyleComputer;
|
|
||||||
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -72,31 +71,20 @@ pub fn help_externs(
|
|||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
// 🚩The following two-lines are copied from filters/find.rs:
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
||||||
// Currently, search results all use the same style.
|
|
||||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
|
||||||
// defined for "string").
|
|
||||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
||||||
let highlight_style =
|
|
||||||
style_computer.compute("search_result", &Value::string("search result", head));
|
|
||||||
|
|
||||||
if let Some(f) = find {
|
if let Some(f) = find {
|
||||||
let all_cmds_vec = build_help_externs(engine_state, stack, head);
|
let all_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||||
let found_cmds_vec = highlight_search_in_table(
|
return find_internal(
|
||||||
all_cmds_vec,
|
all_cmds_vec,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
&f.item,
|
&f.item,
|
||||||
&["name", "description"],
|
&["name", "description"],
|
||||||
&string_style,
|
true,
|
||||||
&highlight_style,
|
);
|
||||||
)?;
|
|
||||||
|
|
||||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
let found_cmds_vec = build_help_externs(engine_state, stack, head);
|
Ok(build_help_externs(engine_state, stack, head))
|
||||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
||||||
@ -119,10 +107,10 @@ pub fn help_externs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
|
||||||
let mut scope = ScopeData::new(engine_state, stack);
|
let mut scope = ScopeData::new(engine_state, stack);
|
||||||
scope.populate_decls();
|
scope.populate_decls();
|
||||||
scope.collect_externs(span)
|
Value::list(scope.collect_externs(span), span).into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::help::highlight_search_in_table;
|
use crate::filters::find_internal;
|
||||||
use nu_color_config::StyleComputer;
|
|
||||||
use nu_engine::{command_prelude::*, scope::ScopeData};
|
use nu_engine::{command_prelude::*, scope::ScopeData};
|
||||||
use nu_protocol::DeclId;
|
use nu_protocol::DeclId;
|
||||||
|
|
||||||
@ -79,31 +78,20 @@ pub fn help_modules(
|
|||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
// 🚩The following two-lines are copied from filters/find.rs:
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
||||||
// Currently, search results all use the same style.
|
|
||||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
|
||||||
// defined for "string").
|
|
||||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
||||||
let highlight_style =
|
|
||||||
style_computer.compute("search_result", &Value::string("search result", head));
|
|
||||||
|
|
||||||
if let Some(f) = find {
|
if let Some(f) = find {
|
||||||
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||||
let found_cmds_vec = highlight_search_in_table(
|
return find_internal(
|
||||||
all_cmds_vec,
|
all_cmds_vec,
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
&f.item,
|
&f.item,
|
||||||
&["name", "description"],
|
&["name", "description"],
|
||||||
&string_style,
|
true,
|
||||||
&highlight_style,
|
);
|
||||||
)?;
|
|
||||||
|
|
||||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
Ok(build_help_modules(engine_state, stack, head))
|
||||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
|
||||||
} else {
|
} else {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
||||||
@ -239,11 +227,11 @@ pub fn help_modules(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
|
||||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
scope_data.populate_modules();
|
scope_data.populate_modules();
|
||||||
|
|
||||||
scope_data.collect_modules(span)
|
Value::list(scope_data.collect_modules(span), span).into_pipeline_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -68,6 +68,29 @@ impl Command for HelpOperators {
|
|||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|op| {
|
.map(|op| {
|
||||||
|
if op == Operator::Comparison(Comparison::RegexMatch) {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"type" => Value::string(op_type(&op), head),
|
||||||
|
"operator" => Value::string("=~, like", head),
|
||||||
|
"name" => Value::string(name(&op), head),
|
||||||
|
"description" => Value::string(description(&op), head),
|
||||||
|
"precedence" => Value::int(op.precedence().into(), head),
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
} else if op == Operator::Comparison(Comparison::NotRegexMatch) {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
"type" => Value::string(op_type(&op), head),
|
||||||
|
"operator" => Value::string("!~, not-like", head),
|
||||||
|
"name" => Value::string(name(&op), head),
|
||||||
|
"description" => Value::string(description(&op), head),
|
||||||
|
"precedence" => Value::int(op.precedence().into(), head),
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"type" => Value::string(op_type(&op), head),
|
"type" => Value::string(op_type(&op), head),
|
||||||
@ -78,6 +101,7 @@ impl Command for HelpOperators {
|
|||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ pub use help_modules::HelpModules;
|
|||||||
pub use help_operators::HelpOperators;
|
pub use help_operators::HelpOperators;
|
||||||
pub use help_pipe_and_redirect::HelpPipeAndRedirect;
|
pub use help_pipe_and_redirect::HelpPipeAndRedirect;
|
||||||
|
|
||||||
pub(crate) use help_::highlight_search_in_table;
|
|
||||||
pub(crate) use help_aliases::help_aliases;
|
pub(crate) use help_aliases::help_aliases;
|
||||||
pub(crate) use help_commands::help_commands;
|
pub(crate) use help_commands::help_commands;
|
||||||
pub(crate) use help_modules::help_modules;
|
pub(crate) use help_modules::help_modules;
|
||||||
|
@ -80,9 +80,9 @@ pub fn http_parse_url(
|
|||||||
) -> Result<(String, Url), ShellError> {
|
) -> Result<(String, Url), ShellError> {
|
||||||
let mut requested_url = raw_url.coerce_into_string()?;
|
let mut requested_url = raw_url.coerce_into_string()?;
|
||||||
if requested_url.starts_with(':') {
|
if requested_url.starts_with(':') {
|
||||||
requested_url = format!("http://localhost{}", requested_url);
|
requested_url = format!("http://localhost{requested_url}");
|
||||||
} else if !requested_url.contains("://") {
|
} else if !requested_url.contains("://") {
|
||||||
requested_url = format!("http://{}", requested_url);
|
requested_url = format!("http://{requested_url}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = match url::Url::parse(&requested_url) {
|
let url = match url::Url::parse(&requested_url) {
|
||||||
@ -382,8 +382,7 @@ fn send_multipart_request(
|
|||||||
"Content-Type: application/octet-stream".to_string(),
|
"Content-Type: application/octet-stream".to_string(),
|
||||||
"Content-Transfer-Encoding: binary".to_string(),
|
"Content-Transfer-Encoding: binary".to_string(),
|
||||||
format!(
|
format!(
|
||||||
"Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"",
|
"Content-Disposition: form-data; name=\"{col}\"; filename=\"{col}\""
|
||||||
col, col
|
|
||||||
),
|
),
|
||||||
format!("Content-Length: {}", val.len()),
|
format!("Content-Length: {}", val.len()),
|
||||||
];
|
];
|
||||||
@ -391,7 +390,7 @@ fn send_multipart_request(
|
|||||||
.add(&mut Cursor::new(val), &headers.join("\r\n"))
|
.add(&mut Cursor::new(val), &headers.join("\r\n"))
|
||||||
.map_err(err)?;
|
.map_err(err)?;
|
||||||
} else {
|
} else {
|
||||||
let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col);
|
let headers = format!(r#"Content-Disposition: form-data; name="{col}""#);
|
||||||
builder
|
builder
|
||||||
.add(val.coerce_into_string()?.as_bytes(), &headers)
|
.add(val.coerce_into_string()?.as_bytes(), &headers)
|
||||||
.map_err(err)?;
|
.map_err(err)?;
|
||||||
@ -400,7 +399,7 @@ fn send_multipart_request(
|
|||||||
builder.finish();
|
builder.finish();
|
||||||
|
|
||||||
let (boundary, data) = (builder.boundary, builder.data);
|
let (boundary, data) = (builder.boundary, builder.data);
|
||||||
let content_type = format!("multipart/form-data; boundary={}", boundary);
|
let content_type = format!("multipart/form-data; boundary={boundary}");
|
||||||
|
|
||||||
move || req.set("Content-Type", &content_type).send_bytes(&data)
|
move || req.set("Content-Type", &content_type).send_bytes(&data)
|
||||||
}
|
}
|
||||||
@ -703,8 +702,7 @@ fn transform_response_using_content_type(
|
|||||||
.expect("Failed to parse content type, and failed to default to text/plain");
|
.expect("Failed to parse content type, and failed to default to text/plain");
|
||||||
|
|
||||||
let ext = match (content_type.type_(), content_type.subtype()) {
|
let ext = match (content_type.type_(), content_type.subtype()) {
|
||||||
(mime::TEXT, mime::PLAIN) => {
|
(mime::TEXT, mime::PLAIN) => url::Url::parse(requested_url)
|
||||||
let path_extension = url::Url::parse(requested_url)
|
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
LabeledError::new(err.to_string())
|
LabeledError::new(err.to_string())
|
||||||
.with_help("cannot parse")
|
.with_help("cannot parse")
|
||||||
@ -720,9 +718,7 @@ fn transform_response_using_content_type(
|
|||||||
PathBuf::from(name)
|
PathBuf::from(name)
|
||||||
.extension()
|
.extension()
|
||||||
.map(|name| name.to_string_lossy().to_string())
|
.map(|name| name.to_string_lossy().to_string())
|
||||||
});
|
}),
|
||||||
path_extension
|
|
||||||
}
|
|
||||||
_ => Some(content_type.subtype().to_string()),
|
_ => Some(content_type.subtype().to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ struct Arguments {
|
|||||||
redirect: Option<Spanned<String>>,
|
redirect: Option<Spanned<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_get(
|
pub fn run_get(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use nu_engine::{command_prelude::*, get_full_help};
|
use nu_engine::{command_prelude::*, get_full_help};
|
||||||
|
|
||||||
|
use super::get::run_get;
|
||||||
|
use super::post::run_post;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Http;
|
pub struct Http;
|
||||||
|
|
||||||
@ -10,7 +13,76 @@ impl Command for Http {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("http")
|
Signature::build("http")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||||
|
// common to get more than help. Get by default
|
||||||
|
.optional(
|
||||||
|
"URL",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The URL to fetch the contents from.",
|
||||||
|
)
|
||||||
|
// post
|
||||||
|
.optional(
|
||||||
|
"data",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"The contents of the post body. Required unless part of a pipeline.",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"content-type",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the MIME type of content to post",
|
||||||
|
Some('t'),
|
||||||
|
)
|
||||||
|
// common
|
||||||
|
.named(
|
||||||
|
"user",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the username when authenticating",
|
||||||
|
Some('u'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"password",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the password when authenticating",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"max-time",
|
||||||
|
SyntaxShape::Duration,
|
||||||
|
"max duration before timeout occurs",
|
||||||
|
Some('m'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"headers",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"custom headers you want to add ",
|
||||||
|
Some('H'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"raw",
|
||||||
|
"fetch contents as text rather than a table",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"insecure",
|
||||||
|
"allow insecure server connections when using SSL",
|
||||||
|
Some('k'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"full",
|
||||||
|
"returns the full response instead of only the body",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"allow-errors",
|
||||||
|
"do not fail if the server returns an error code",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"redirect-mode",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
|
||||||
|
Some('R')
|
||||||
|
)
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +91,7 @@ impl Command for Http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
"Without a subcommand but with a URL provided, it performs a GET request by default or a POST request if data is provided. You can use one of the following subcommands. Using this command as-is will only display this help message."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
@ -33,8 +105,41 @@ impl Command for Http {
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
let url = call.opt::<Value>(engine_state, stack, 0)?;
|
||||||
|
let data = call.opt::<Value>(engine_state, stack, 1)?;
|
||||||
|
match (url.is_some(), data.is_some()) {
|
||||||
|
(true, true) => run_post(engine_state, stack, call, input),
|
||||||
|
(true, false) => run_get(engine_state, stack, call, input),
|
||||||
|
(false, true) => Err(ShellError::NushellFailed {
|
||||||
|
msg: (String::from("Default verb is get with a payload. Impossible state")),
|
||||||
|
}),
|
||||||
|
(false, false) => Ok(Value::string(
|
||||||
|
get_full_help(self, engine_state, stack),
|
||||||
|
call.head,
|
||||||
|
)
|
||||||
|
.into_pipeline_data()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get content from example.com with default verb",
|
||||||
|
example: "http https://www.example.com",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Post content to example.com with default verb",
|
||||||
|
example: "http https://www.example.com 'body'",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get content from example.com with explicit verb",
|
||||||
|
example: "http get https://www.example.com",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ struct Arguments {
|
|||||||
redirect: Option<Spanned<String>>,
|
redirect: Option<Spanned<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_post(
|
pub fn run_post(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
|
@ -64,7 +64,7 @@ impl Registry for NuShellNightly {
|
|||||||
tag_name: String,
|
tag_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = format!("https://api.github.com/repos/{}/releases", pkg);
|
let url = format!("https://api.github.com/repos/{pkg}/releases");
|
||||||
let versions = http_client
|
let versions = http_client
|
||||||
.add_header("Accept", "application/vnd.github.v3+json")
|
.add_header("Accept", "application/vnd.github.v3+json")
|
||||||
.add_header("User-Agent", "update-informer")
|
.add_header("User-Agent", "update-informer")
|
||||||
@ -128,7 +128,7 @@ pub fn check_for_latest_nushell_version() -> Value {
|
|||||||
|
|
||||||
if let Ok(Some(new_version)) = informer.check_version() {
|
if let Ok(Some(new_version)) = informer.check_version() {
|
||||||
rec.push("current", Value::test_bool(false));
|
rec.push("current", Value::test_bool(false));
|
||||||
rec.push("latest", Value::test_string(format!("{}", new_version)));
|
rec.push("latest", Value::test_string(format!("{new_version}")));
|
||||||
Value::test_record(rec)
|
Value::test_record(rec)
|
||||||
} else {
|
} else {
|
||||||
rec.push("current", Value::test_bool(true));
|
rec.push("current", Value::test_bool(true));
|
||||||
@ -148,7 +148,7 @@ pub fn check_for_latest_nushell_version() -> Value {
|
|||||||
|
|
||||||
if let Ok(Some(new_version)) = informer.check_version() {
|
if let Ok(Some(new_version)) = informer.check_version() {
|
||||||
rec.push("current", Value::test_bool(false));
|
rec.push("current", Value::test_bool(false));
|
||||||
rec.push("latest", Value::test_string(format!("{}", new_version)));
|
rec.push("latest", Value::test_string(format!("{new_version}")));
|
||||||
Value::test_record(rec)
|
Value::test_record(rec)
|
||||||
} else {
|
} else {
|
||||||
rec.push("current", Value::test_bool(true));
|
rec.push("current", Value::test_bool(true));
|
||||||
|
@ -208,7 +208,7 @@ impl EventTypeFilter {
|
|||||||
|
|
||||||
fn wrong_type_error(head: Span, val: &str, val_span: Span) -> ShellError {
|
fn wrong_type_error(head: Span, val: &str, val_span: Span) -> ShellError {
|
||||||
ShellError::UnsupportedInput {
|
ShellError::UnsupportedInput {
|
||||||
msg: format!("{} is not a valid event type", val),
|
msg: format!("{val} is not a valid event type"),
|
||||||
input: "value originates from here".into(),
|
input: "value originates from here".into(),
|
||||||
msg_span: head,
|
msg_span: head,
|
||||||
input_span: val_span,
|
input_span: val_span,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
mod ansi;
|
|
||||||
mod clear;
|
mod clear;
|
||||||
mod dir_info;
|
mod dir_info;
|
||||||
mod input;
|
mod input;
|
||||||
@ -10,7 +9,6 @@ mod term;
|
|||||||
mod ulimit;
|
mod ulimit;
|
||||||
mod whoami;
|
mod whoami;
|
||||||
|
|
||||||
pub use ansi::{Ansi, AnsiLink, AnsiStrip};
|
|
||||||
pub use clear::Clear;
|
pub use clear::Clear;
|
||||||
pub use dir_info::{DirBuilder, DirInfo, FileInfo};
|
pub use dir_info::{DirBuilder, DirInfo, FileInfo};
|
||||||
pub use input::Input;
|
pub use input::Input;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ListStream;
|
use nu_protocol::ListStream;
|
||||||
use rand::random_range;
|
use rand::random_range;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RandomDice;
|
pub struct RandomDice;
|
||||||
@ -70,8 +71,16 @@ fn dice(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
|
|
||||||
let dice: usize = call.get_flag(engine_state, stack, "dice")?.unwrap_or(1);
|
let sides: NonZeroUsize = call
|
||||||
let sides: usize = call.get_flag(engine_state, stack, "sides")?.unwrap_or(6);
|
.get_flag(engine_state, stack, "sides")?
|
||||||
|
.unwrap_or_else(|| NonZeroUsize::new(6).expect("default sides must be non-zero"));
|
||||||
|
|
||||||
|
let dice: NonZeroUsize = call
|
||||||
|
.get_flag(engine_state, stack, "dice")?
|
||||||
|
.unwrap_or_else(|| NonZeroUsize::new(1).expect("default dice count must be non-zero"));
|
||||||
|
|
||||||
|
let sides = sides.get();
|
||||||
|
let dice = dice.get();
|
||||||
|
|
||||||
let iter = (0..dice).map(move |_| Value::int(random_range(1..sides + 1) as i64, span));
|
let iter = (0..dice).map(move |_| Value::int(random_range(1..sides + 1) as i64, span));
|
||||||
|
|
||||||
|
@ -135,8 +135,7 @@ fn uuid(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::IncorrectValue {
|
return Err(ShellError::IncorrectValue {
|
||||||
msg: format!(
|
msg: format!(
|
||||||
"Unsupported UUID version: {}. Supported versions are 1, 3, 4, 5, and 7.",
|
"Unsupported UUID version: {version}. Supported versions are 1, 3, 4, 5, and 7."
|
||||||
version
|
|
||||||
),
|
),
|
||||||
val_span: span,
|
val_span: span,
|
||||||
call_span: span,
|
call_span: span,
|
||||||
@ -190,8 +189,7 @@ fn validate_flags(
|
|||||||
if v != 4 && v != 7 {
|
if v != 4 && v != 7 {
|
||||||
return Err(ShellError::IncorrectValue {
|
return Err(ShellError::IncorrectValue {
|
||||||
msg: format!(
|
msg: format!(
|
||||||
"Unsupported UUID version: {}. Supported versions are 1, 3, 4, 5, and 7.",
|
"Unsupported UUID version: {v}. Supported versions are 1, 3, 4, 5, and 7."
|
||||||
v
|
|
||||||
),
|
),
|
||||||
val_span: span,
|
val_span: span,
|
||||||
call_span: span,
|
call_span: span,
|
||||||
@ -202,7 +200,7 @@ fn validate_flags(
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: format!("version {} uuid does not take mac as a parameter", v),
|
msg: format!("version {v} uuid does not take mac as a parameter"),
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -211,7 +209,7 @@ fn validate_flags(
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: format!("version {} uuid does not take namespace as a parameter", v),
|
msg: format!("version {v} uuid does not take namespace as a parameter"),
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -220,7 +218,7 @@ fn validate_flags(
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Err(ShellError::IncompatibleParametersSingle {
|
return Err(ShellError::IncompatibleParametersSingle {
|
||||||
msg: format!("version {} uuid does not take name as a parameter", v),
|
msg: format!("version {v} uuid does not take name as a parameter"),
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user