mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 22:50:14 +02:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
1fe62ee613 | |||
126d11fcb7 |
@ -30,10 +30,4 @@ rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-stati
|
||||
[target.aarch64-apple-darwin]
|
||||
# We can guarantee that this target will always run on a CPU with _at least_
|
||||
# these capabilities, so let's optimize for them
|
||||
rustflags = ["-Ctarget-cpu=apple-m1"]
|
||||
|
||||
# This is required for uutils/coreutils version 0.1.0 and later
|
||||
# It looks like this is what they use to name their executable
|
||||
# https://github.com/uutils/coreutils/blob/61bd11a55118458704c4cbbf4e628cd657238d3e/src/uucore/src/lib/lib.rs#L201-L218
|
||||
[env]
|
||||
PROJECT_NAME_FOR_VERSION_STRING = "nushell"
|
||||
rustflags = ["-Ctarget-cpu=apple-m1"]
|
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
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.103.0
|
||||
version: 0.105.1
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
@ -127,6 +127,7 @@ jobs:
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
@ -152,6 +153,8 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
@ -179,36 +182,22 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||
with:
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
with:
|
||||
version: 0.103.0
|
||||
version: 0.105.1
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build Nu for Windows ARM64
|
||||
id: nu0
|
||||
shell: pwsh
|
||||
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||
run: |
|
||||
$env:OS = 'windows'
|
||||
$env:REF = '${{ github.ref }}'
|
||||
$env:TARGET = '${{ matrix.target }}'
|
||||
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||
./nu.exe -c 'version'
|
||||
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||
|
||||
- name: Create an Issue for Release Failure
|
||||
if: ${{ failure() }}
|
||||
uses: JasonEtco/create-an-issue@v2
|
||||
@ -228,9 +217,7 @@ jobs:
|
||||
prerelease: true
|
||||
files: |
|
||||
${{ steps.nu.outputs.msi }}
|
||||
${{ steps.nu0.outputs.msi }}
|
||||
${{ steps.nu.outputs.archive }}
|
||||
${{ steps.nu0.outputs.archive }}
|
||||
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
||||
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
|
||||
env:
|
||||
@ -276,7 +263,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.103.0
|
||||
version: 0.105.1
|
||||
|
||||
# Keep the last a few 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
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: nightly
|
||||
version: 0.105.1
|
||||
|
||||
- name: Release MSI Packages
|
||||
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'
|
||||
cargo-build-nu
|
||||
}
|
||||
'loongarch64-unknown-linux-musl' => {
|
||||
print $"(ansi g)Downloading LoongArch64 musl cross-compilation toolchain...(ansi reset)"
|
||||
aria2c -q https://github.com/LoongsonLab/oscomp-toolchains-for-oskernel/releases/download/loongarch64-linux-musl-cross-gcc-13.2.0/loongarch64-linux-musl-cross.tgz
|
||||
tar -xf loongarch64-linux-musl-cross.tgz
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.PWD)/loongarch64-linux-musl-cross/bin')
|
||||
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_MUSL_LINKER = "loongarch64-linux-musl-gcc"
|
||||
cargo-build-nu
|
||||
}
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -35,6 +35,7 @@ jobs:
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-gnu
|
||||
- loongarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
@ -60,6 +61,8 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: loongarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
@ -90,32 +93,17 @@ jobs:
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
with:
|
||||
version: 0.103.0
|
||||
version: 0.105.1
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
|
||||
- name: Build Nu for Windows ARM64
|
||||
id: nu0
|
||||
shell: pwsh
|
||||
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||
run: |
|
||||
$env:OS = 'windows'
|
||||
$env:REF = '${{ github.ref }}'
|
||||
$env:TARGET = '${{ matrix.target }}'
|
||||
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||
./nu.exe -c 'version'
|
||||
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||
|
||||
# WARN: Don't upgrade this action due to the release per asset issue.
|
||||
# See: https://github.com/softprops/action-gh-release/issues/445
|
||||
- name: Publish Archive
|
||||
@ -125,9 +113,7 @@ jobs:
|
||||
draft: true
|
||||
files: |
|
||||
${{ steps.nu.outputs.msi }}
|
||||
${{ steps.nu0.outputs.msi }}
|
||||
${{ steps.nu.outputs.archive }}
|
||||
${{ steps.nu0.outputs.archive }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
7
.github/workflows/winget-submission.yml
vendored
7
.github/workflows/winget-submission.yml
vendored
@ -10,6 +10,11 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
|
||||
winget:
|
||||
@ -25,5 +30,5 @@ jobs:
|
||||
installers-regex: 'msvc\.msi$'
|
||||
version: ${{ 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
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -32,11 +32,17 @@ unstable_cargo_features.txt
|
||||
# Helix configuration folder
|
||||
.helix/*
|
||||
.helix
|
||||
wix/bin/
|
||||
wix/obj/
|
||||
wix/nu/
|
||||
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
||||
# benchmarking
|
||||
/tango
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
*.rsproj
|
||||
|
615
Cargo.lock
generated
615
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
86
Cargo.toml
86
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.85.1"
|
||||
version = "0.105.0"
|
||||
rust-version = "1.86.0"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -63,7 +63,7 @@ members = [
|
||||
|
||||
[workspace.dependencies]
|
||||
alphanumeric-sort = "1.5"
|
||||
ansi-str = "0.8"
|
||||
ansi-str = "0.9"
|
||||
anyhow = "1.0.82"
|
||||
base64 = "0.22.1"
|
||||
bracoxide = "0.1.6"
|
||||
@ -71,7 +71,7 @@ brotli = "7.0"
|
||||
byteorder = "1.5"
|
||||
bytes = "1"
|
||||
bytesize = "1.3.3"
|
||||
calamine = "0.26"
|
||||
calamine = "0.28"
|
||||
chardetng = "0.1.17"
|
||||
chrono = { default-features = false, version = "0.4.34" }
|
||||
chrono-humanize = "0.2.3"
|
||||
@ -156,14 +156,14 @@ serde_json = "1.0.97"
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde_yaml = "0.9.33"
|
||||
sha2 = "0.10"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
strip-ansi-escapes = "0.2.1"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
syn = "2.0"
|
||||
sysinfo = "0.33"
|
||||
tabled = { version = "0.20", default-features = false }
|
||||
tempfile = "3.20"
|
||||
titlecase = "3.5"
|
||||
titlecase = "3.6"
|
||||
toml = "0.8"
|
||||
trash = "5.2"
|
||||
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
|
||||
@ -172,19 +172,19 @@ unicode-segmentation = "1.12"
|
||||
unicode-width = "0.2"
|
||||
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
|
||||
url = "2.2"
|
||||
uu_cp = "0.1.0"
|
||||
uu_mkdir = "0.1.0"
|
||||
uu_mktemp = "0.1.0"
|
||||
uu_mv = "0.1.0"
|
||||
uu_touch = "0.1.0"
|
||||
uu_whoami = "0.1.0"
|
||||
uu_uname = "0.1.0"
|
||||
uucore = "0.1.0"
|
||||
uu_cp = "0.0.30"
|
||||
uu_mkdir = "0.0.30"
|
||||
uu_mktemp = "0.0.30"
|
||||
uu_mv = "0.0.30"
|
||||
uu_touch = "0.0.30"
|
||||
uu_whoami = "0.0.30"
|
||||
uu_uname = "0.0.30"
|
||||
uucore = "0.0.30"
|
||||
uuid = "1.16.0"
|
||||
v_htmlescape = "0.15.0"
|
||||
wax = "0.6"
|
||||
web-time = "1.1.0"
|
||||
which = "7.0.0"
|
||||
which = "8.0.0"
|
||||
windows = "0.56"
|
||||
windows-sys = "0.48"
|
||||
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.
|
||||
# todo = "warn"
|
||||
unchecked_duration_subtraction = "warn"
|
||||
used_underscore_binding = "warn"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.105.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.0", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.105.0", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.105.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.105.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.105.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.105.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.105.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.105.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.105.0" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.2", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.105.2" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.2" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.2" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.105.2" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.105.2" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.105.2" }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
@ -244,9 +245,9 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.2" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.2" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.2" }
|
||||
assert_cmd = "2.0"
|
||||
dirs = { workspace = true }
|
||||
tango-bench = "0.6"
|
||||
@ -257,10 +258,14 @@ serial_test = "3.2"
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
# Enable all features while still avoiding mutually exclusive features.
|
||||
# Use this if `--all-features` fails.
|
||||
full = ["plugin", "rustls-tls", "system-clipboard", "trash-support", "sqlite"]
|
||||
|
||||
plugin = [
|
||||
# crates
|
||||
"nu-cmd-plugin",
|
||||
"nu-plugin-engine",
|
||||
"dep:nu-cmd-plugin",
|
||||
"dep:nu-plugin-engine",
|
||||
|
||||
# features
|
||||
"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/);
|
||||
# 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.
|
||||
# Missing X server/ Wayland can cause issues
|
||||
system-clipboard = [
|
||||
"reedline/system_clipboard",
|
||||
"nu-cli/system-clipboard",
|
||||
"nu-cmd-lang/system-clipboard",
|
||||
]
|
||||
|
||||
# Stable (Default)
|
||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
|
||||
# SQLite commands for nushell
|
||||
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"]
|
||||
sqlite = ["nu-command/sqlite", "nu-std/sqlite"]
|
||||
|
||||
[profile.release]
|
||||
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
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Run all benchmarks with `cargo bench`
|
||||
|
@ -199,7 +199,7 @@ fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
|
||||
let nested_access = ".col".repeat(n);
|
||||
bench_command(
|
||||
format!("record_nested_access_{n}"),
|
||||
format!("$record{} | ignore", nested_access),
|
||||
format!("$record{nested_access} | ignore"),
|
||||
stack,
|
||||
engine,
|
||||
)
|
||||
@ -319,7 +319,7 @@ fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks {
|
||||
let stack = Stack::new();
|
||||
bench_command(
|
||||
format!("eval_par_each_{n}"),
|
||||
format!("(1..{}) | par-each -t 2 {{|_| 1 }} | ignore", n),
|
||||
format!("(1..{n}) | par-each -t 2 {{|_| 1 }} | ignore"),
|
||||
stack,
|
||||
engine,
|
||||
)
|
||||
@ -357,7 +357,7 @@ fn encode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
let encoder = Rc::new(EncodingType::try_from_bytes(b"json").unwrap());
|
||||
|
||||
[benchmark_fn(
|
||||
format!("encode_json_{}_{}", row_cnt, col_cnt),
|
||||
format!("encode_json_{row_cnt}_{col_cnt}"),
|
||||
move |b| {
|
||||
let encoder = encoder.clone();
|
||||
let test_data = test_data.clone();
|
||||
@ -377,7 +377,7 @@ fn encode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
let encoder = Rc::new(EncodingType::try_from_bytes(b"msgpack").unwrap());
|
||||
|
||||
[benchmark_fn(
|
||||
format!("encode_msgpack_{}_{}", row_cnt, col_cnt),
|
||||
format!("encode_msgpack_{row_cnt}_{col_cnt}"),
|
||||
move |b| {
|
||||
let encoder = encoder.clone();
|
||||
let test_data = test_data.clone();
|
||||
@ -399,7 +399,7 @@ fn decode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
encoder.encode(&test_data, &mut res).unwrap();
|
||||
|
||||
[benchmark_fn(
|
||||
format!("decode_json_{}_{}", row_cnt, col_cnt),
|
||||
format!("decode_json_{row_cnt}_{col_cnt}"),
|
||||
move |b| {
|
||||
let res = res.clone();
|
||||
b.iter(move || {
|
||||
@ -422,7 +422,7 @@ fn decode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
|
||||
encoder.encode(&test_data, &mut res).unwrap();
|
||||
|
||||
[benchmark_fn(
|
||||
format!("decode_msgpack_{}_{}", row_cnt, col_cnt),
|
||||
format!("decode_msgpack_{row_cnt}_{col_cnt}"),
|
||||
move |b| {
|
||||
let res = res.clone();
|
||||
b.iter(move || {
|
||||
|
@ -5,29 +5,29 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.0" }
|
||||
nu-std = { path = "../nu-std", version = "0.105.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||
nu-std = { path = "../nu-std", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", features = ["os"] }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", features = ["os"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", features = ["os"] }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["os"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -118,7 +118,7 @@ fn get_suggestions_by_value(
|
||||
|| s.chars()
|
||||
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
||||
{
|
||||
format!("{:?}", s)
|
||||
format!("{s:?}")
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ impl CommandCompletion {
|
||||
continue;
|
||||
};
|
||||
let value = if matched_internal(&name) {
|
||||
format!("^{}", name)
|
||||
format!("^{name}")
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
|
@ -176,7 +176,7 @@ impl NuCompleter {
|
||||
&mut working_set,
|
||||
Some("completer"),
|
||||
// Add a placeholder `a` to the end
|
||||
format!("{}a", line).as_bytes(),
|
||||
format!("{line}a").as_bytes(),
|
||||
false,
|
||||
);
|
||||
self.fetch_completions_by_block(block, &working_set, pos, offset, line, true)
|
||||
@ -466,6 +466,14 @@ impl NuCompleter {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -842,7 +850,7 @@ mod completer_tests {
|
||||
for (line, has_result, begins_with, expected_values) in dataset {
|
||||
let result = completer.fetch_completions_at(line, line.len());
|
||||
// Test whether the result is empty or not
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {line}");
|
||||
|
||||
// Test whether the result begins with the expected value
|
||||
result
|
||||
@ -857,8 +865,7 @@ mod completer_tests {
|
||||
.filter(|x| *x)
|
||||
.count(),
|
||||
expected_values.len(),
|
||||
"line: {}",
|
||||
line
|
||||
"line: {line}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ pub fn escape_path(path: String) -> String {
|
||||
if path.contains('\'') {
|
||||
// decide to use double quotes
|
||||
// Path as Debug will do the escaping for `"`, `\`
|
||||
format!("{:?}", path)
|
||||
format!("{path:?}")
|
||||
} else {
|
||||
format!("'{path}'")
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ impl Completer for DotNuCompletion {
|
||||
.take_while(|c| "`'\"".contains(*c))
|
||||
.collect::<String>();
|
||||
for path in ["std", "std-rfc"] {
|
||||
let path = format!("{}{}", surround_prefix, path);
|
||||
let path = format!("{surround_prefix}{path}");
|
||||
matcher.add(
|
||||
path.clone(),
|
||||
FileSuggestion {
|
||||
@ -146,7 +146,7 @@ impl Completer for DotNuCompletion {
|
||||
for sub_vp_id in sub_paths {
|
||||
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
||||
let path = path
|
||||
.strip_prefix(&format!("{}/", base_dir))
|
||||
.strip_prefix(&format!("{base_dir}/"))
|
||||
.unwrap_or(path)
|
||||
.to_string();
|
||||
matcher.add(
|
||||
|
@ -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_utils::IgnoreCaseExt;
|
||||
use reedline::{Completer, Suggestion};
|
||||
@ -66,8 +66,11 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
||||
v.to_parsable_string(", ", &self.config)
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| match v {
|
||||
FormatterValue::DefaultValue(value) => {
|
||||
value.to_parsable_string(", ", &self.config)
|
||||
}
|
||||
FormatterValue::CodeString(text) => text.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ use std::sync::Arc;
|
||||
use nu_engine::command_prelude::*;
|
||||
use reedline::{Highlighter, StyledText};
|
||||
|
||||
use crate::syntax_highlight::highlight_syntax;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuHighlight;
|
||||
|
||||
@ -14,6 +16,11 @@ impl Command for NuHighlight {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight")
|
||||
.category(Category::Strings)
|
||||
.switch(
|
||||
"reject-garbage",
|
||||
"Return an error if invalid syntax (garbage) was encountered",
|
||||
Some('r'),
|
||||
)
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
}
|
||||
|
||||
@ -32,19 +39,33 @@ impl Command for NuHighlight {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let reject_garbage = call.has_flag(engine_state, stack, "reject-garbage")?;
|
||||
let head = call.head;
|
||||
|
||||
let signals = engine_state.signals();
|
||||
|
||||
let highlighter = crate::NuHighlighter {
|
||||
engine_state: Arc::new(engine_state.clone()),
|
||||
stack: Arc::new(stack.clone()),
|
||||
};
|
||||
let engine_state = Arc::new(engine_state.clone());
|
||||
let stack = Arc::new(stack.clone());
|
||||
|
||||
input.map(
|
||||
move |x| match x.coerce_into_string() {
|
||||
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)
|
||||
}
|
||||
Err(err) => Value::error(err, head),
|
||||
|
@ -22,8 +22,8 @@ use nu_color_config::StyleComputer;
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_engine::exit::cleanup_exit;
|
||||
use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::shell_error;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use nu_protocol::{BannerKind, shell_error};
|
||||
use nu_protocol::{
|
||||
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
|
||||
config::NuCursorShape,
|
||||
@ -145,8 +145,8 @@ pub fn evaluate_repl(
|
||||
|
||||
if load_std_lib.is_none() {
|
||||
match engine_state.get_config().show_banner {
|
||||
Value::Bool { val: false, .. } => {}
|
||||
Value::String { ref val, .. } if val == "short" => {
|
||||
BannerKind::None => {}
|
||||
BannerKind::Short => {
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
@ -156,7 +156,7 @@ pub fn evaluate_repl(
|
||||
false,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
BannerKind::Full => {
|
||||
eval_source(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
@ -239,7 +239,7 @@ fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
|
||||
|
||||
match byte {
|
||||
// Escape bytes below 0x20
|
||||
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
|
||||
b if b < 0x20 => format!("\\x{byte:02X}").into_bytes(),
|
||||
// Escape semicolon as \x3B
|
||||
b';' => "\\x3B".to_string().into_bytes(),
|
||||
// Escape backslash as \\
|
||||
@ -1097,8 +1097,7 @@ fn run_shell_integration_osc633(
|
||||
// If we're in vscode, run their specific ansi escape sequence.
|
||||
// This is helpful for ctrl+g to change directories in the terminal.
|
||||
run_ansi_sequence(&format!(
|
||||
"{}{}{}",
|
||||
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
|
||||
"{VSCODE_CWD_PROPERTY_MARKER_PREFIX}{path}{VSCODE_CWD_PROPERTY_MARKER_SUFFIX}"
|
||||
));
|
||||
|
||||
perf!(
|
||||
@ -1114,10 +1113,7 @@ fn run_shell_integration_osc633(
|
||||
|
||||
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
||||
run_ansi_sequence(&format!(
|
||||
"{}{}{}",
|
||||
VSCODE_COMMANDLINE_MARKER_PREFIX,
|
||||
replaced_cmd_text,
|
||||
VSCODE_COMMANDLINE_MARKER_SUFFIX
|
||||
"{VSCODE_COMMANDLINE_MARKER_PREFIX}{replaced_cmd_text}{VSCODE_COMMANDLINE_MARKER_SUFFIX}"
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1493,7 +1489,7 @@ mod test_auto_cd {
|
||||
// Parse the input. It must be an auto-cd operation.
|
||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||
let ReplOperation::AutoCd { cwd, target, span } = op else {
|
||||
panic!("'{}' was not parsed into an auto-cd operation", input)
|
||||
panic!("'{input}' was not parsed into an auto-cd operation")
|
||||
};
|
||||
|
||||
// Perform the auto-cd operation.
|
||||
|
@ -17,147 +17,173 @@ pub struct NuHighlighter {
|
||||
}
|
||||
|
||||
impl Highlighter for NuHighlighter {
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||
trace!("highlighting: {}", line);
|
||||
fn highlight(&self, line: &str, cursor: usize) -> StyledText {
|
||||
let result = highlight_syntax(&self.engine_state, &self.stack, line, cursor);
|
||||
result.text
|
||||
}
|
||||
}
|
||||
|
||||
let config = self.stack.get_config(&self.engine_state);
|
||||
let highlight_resolved_externals = config.highlight_resolved_externals;
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut shapes = flatten_block(&working_set, &block);
|
||||
// Highlighting externals has a config point because of concerns that using which to resolve
|
||||
// externals may slow down things too much.
|
||||
if highlight_resolved_externals {
|
||||
for (span, shape) in shapes.iter_mut() {
|
||||
if *shape == FlatShape::External {
|
||||
let str_contents =
|
||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||
/// Result of a syntax highlight operation
|
||||
#[derive(Default)]
|
||||
pub(crate) struct HighlightResult {
|
||||
/// The highlighted text
|
||||
pub(crate) text: StyledText,
|
||||
/// The span of any garbage that was highlighted
|
||||
pub(crate) found_garbage: Option<Span>,
|
||||
}
|
||||
|
||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
|
||||
#[allow(deprecated)]
|
||||
let res = if let Ok(cwd) =
|
||||
env::current_dir_str(&self.engine_state, &self.stack)
|
||||
{
|
||||
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||
} else {
|
||||
which::which_in_global(str_word, paths.as_ref())
|
||||
.ok()
|
||||
.and_then(|mut i| i.next())
|
||||
};
|
||||
if res.is_some() {
|
||||
*shape = FlatShape::ExternalResolved;
|
||||
}
|
||||
pub(crate) fn highlight_syntax(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
line: &str,
|
||||
cursor: usize,
|
||||
) -> HighlightResult {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
let config = stack.get_config(engine_state);
|
||||
let highlight_resolved_externals = config.highlight_resolved_externals;
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let block = parse(&mut working_set, None, line.as_bytes(), false);
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut shapes = flatten_block(&working_set, &block);
|
||||
// Highlighting externals has a config point because of concerns that using which to resolve
|
||||
// externals may slow down things too much.
|
||||
if highlight_resolved_externals {
|
||||
for (span, shape) in shapes.iter_mut() {
|
||||
if *shape == FlatShape::External {
|
||||
let str_contents =
|
||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||
|
||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||
let paths = env::path_str(engine_state, stack, *span).ok();
|
||||
let res = if let Ok(cwd) = engine_state.cwd(Some(stack)) {
|
||||
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||
} else {
|
||||
which::which_in_global(str_word, paths.as_ref())
|
||||
.ok()
|
||||
.and_then(|mut i| i.next())
|
||||
};
|
||||
if res.is_some() {
|
||||
*shape = FlatShape::ExternalResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
}
|
||||
(shapes, engine_state.next_span_start())
|
||||
};
|
||||
|
||||
let mut result = HighlightResult::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
let global_cursor_offset = cursor + global_span_offset;
|
||||
let matching_brackets_pos = find_matching_brackets(
|
||||
line,
|
||||
&working_set,
|
||||
&block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
);
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
result.text.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
result
|
||||
.text
|
||||
.push((get_shape_color(shape.as_str(), &config), text));
|
||||
};
|
||||
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
let global_cursor_offset = _cursor + global_span_offset;
|
||||
let matching_brackets_pos = find_matching_brackets(
|
||||
line,
|
||||
&working_set,
|
||||
&block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
);
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => {
|
||||
result.found_garbage.get_or_insert_with(|| {
|
||||
Span::new(
|
||||
shape.0.start - global_span_offset,
|
||||
shape.0.end - global_span_offset,
|
||||
)
|
||||
});
|
||||
add_colored_token(&shape.1, next_token)
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
output.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
output.push((get_shape_color(shape.as_str(), &config), text));
|
||||
};
|
||||
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::RawString => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List
|
||||
| FlatShape::Table
|
||||
| FlatShape::Record
|
||||
| FlatShape::Block
|
||||
| FlatShape::Closure => {
|
||||
let span = shape.0;
|
||||
let shape = &shape.1;
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
for (part, highlight) in spans {
|
||||
let start = part.start - span.start;
|
||||
let end = part.end - span.start;
|
||||
let text = next_token[start..end].to_string();
|
||||
let mut style = get_shape_color(shape.as_str(), &config);
|
||||
if highlight {
|
||||
style = get_matching_brackets_style(style, &config);
|
||||
}
|
||||
output.push((style, text));
|
||||
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::External => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::RawString => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List
|
||||
| FlatShape::Table
|
||||
| FlatShape::Record
|
||||
| FlatShape::Block
|
||||
| FlatShape::Closure => {
|
||||
let span = shape.0;
|
||||
let shape = &shape.1;
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
for (part, highlight) in spans {
|
||||
let start = part.start - span.start;
|
||||
let end = part.end - span.start;
|
||||
let text = next_token[start..end].to_string();
|
||||
let mut style = get_shape_color(shape.as_str(), &config);
|
||||
if highlight {
|
||||
style = get_matching_brackets_style(style, &config);
|
||||
}
|
||||
result.text.push((style, text));
|
||||
}
|
||||
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
}
|
||||
FlatShape::Flag => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
output.push((Style::new(), remainder));
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
}
|
||||
FlatShape::Flag => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
|
||||
}
|
||||
|
||||
output
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
result.text.push((Style::new(), remainder));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn split_span_by_highlight_positions(
|
||||
|
@ -9,14 +9,16 @@ use std::{
|
||||
use nu_cli::NuCompleter;
|
||||
use nu_engine::eval_block;
|
||||
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_std::load_standard_library;
|
||||
use nu_test_support::fs;
|
||||
use reedline::{Completer, Suggestion};
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{
|
||||
completions_helpers::{
|
||||
new_dotnu_engine, new_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,
|
||||
};
|
||||
@ -123,7 +125,7 @@ fn custom_completer_with_options(
|
||||
global_opts,
|
||||
completions
|
||||
.iter()
|
||||
.map(|comp| format!("'{}'", comp))
|
||||
.map(|comp| format!("'{comp}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
completer_opts,
|
||||
@ -716,6 +718,16 @@ fn external_completer_fallback() {
|
||||
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
|
||||
let suggestions = run_external_completion(block, input);
|
||||
match_suggestions_by_string(&expected, &suggestions);
|
||||
|
||||
// issue #15790
|
||||
let input = "foo `dir with space/`";
|
||||
let expected = vec!["`dir with space/bar baz`", "`dir with space/foo`"];
|
||||
let suggestions = run_external_completion_within_pwd(
|
||||
block,
|
||||
input,
|
||||
fs::fixtures().join("external_completions"),
|
||||
);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
/// Fallback to external completions for flags of `sudo`
|
||||
@ -2103,11 +2115,15 @@ fn alias_of_another_alias() {
|
||||
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}");
|
||||
|
||||
// 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 mut working_set = StateWorkingSet::new(&engine_state);
|
||||
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())
|
||||
}
|
||||
|
||||
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
run_external_completion_within_pwd(completer, input, fs::fixtures().join("completions"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.105.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -1,5 +1,5 @@
|
||||
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_protocol::{
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,13 +16,13 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", default-features = false }
|
||||
nu-json = { version = "0.105.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.0" }
|
||||
nu-pretty-hex = { version = "0.105.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.0", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-json = { version = "0.105.2", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-pretty-hex = { version = "0.105.2", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
@ -37,6 +37,6 @@ itertools = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-command = { path = "../nu-command", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
|
@ -12,7 +12,10 @@ impl Command for UpdateCells {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
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(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
@ -77,6 +80,15 @@ impl Command for UpdateCells {
|
||||
"2021-11-18" => Value::test_string(""),
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
example: r#"{a: 1, b: 2, c: 3} | update cells { $in + 10 }"#,
|
||||
description: "Update each value in a record.",
|
||||
result: Some(Value::test_record(record! {
|
||||
"a" => Value::test_int(11),
|
||||
"b" => Value::test_int(12),
|
||||
"c" => Value::test_int(13),
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -85,7 +97,7 @@ impl Command for UpdateCells {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
mut input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||
@ -102,14 +114,51 @@ impl Command for UpdateCells {
|
||||
|
||||
let metadata = input.metadata();
|
||||
|
||||
Ok(UpdateCellIterator {
|
||||
iter: input.into_iter(),
|
||||
closure: ClosureEval::new(engine_state, stack, closure),
|
||||
columns,
|
||||
span: head,
|
||||
match input {
|
||||
PipelineData::Value(
|
||||
Value::Record {
|
||||
ref mut val,
|
||||
internal_span,
|
||||
},
|
||||
..,
|
||||
) => {
|
||||
let val = val.to_mut();
|
||||
update_record(
|
||||
val,
|
||||
&mut ClosureEval::new(engine_state, stack, closure),
|
||||
internal_span,
|
||||
columns.as_ref(),
|
||||
);
|
||||
Ok(input)
|
||||
}
|
||||
_ => Ok(UpdateCellIterator {
|
||||
iter: input.into_iter(),
|
||||
closure: ClosureEval::new(engine_state, stack, closure),
|
||||
columns,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data(head, engine_state.signals().clone())
|
||||
.set_metadata(metadata)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_record(
|
||||
record: &mut Record,
|
||||
closure: &mut ClosureEval,
|
||||
span: Span,
|
||||
cols: Option<&HashSet<String>>,
|
||||
) {
|
||||
if let Some(columns) = cols {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if columns.contains(col) {
|
||||
*val = eval_value(closure, span, std::mem::take(val));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, val) in record.iter_mut() {
|
||||
*val = eval_value(closure, span, std::mem::take(val))
|
||||
}
|
||||
.into_pipeline_data(head, engine_state.signals().clone())
|
||||
.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,18 +177,7 @@ impl Iterator for UpdateCellIterator {
|
||||
|
||||
let value = if let Value::Record { val, .. } = &mut value {
|
||||
let val = val.to_mut();
|
||||
if let Some(columns) = &self.columns {
|
||||
for (col, val) in val.iter_mut() {
|
||||
if columns.contains(col) {
|
||||
*val = eval_value(&mut self.closure, self.span, std::mem::take(val));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, val) in val.iter_mut() {
|
||||
*val = eval_value(&mut self.closure, self.span, std::mem::take(val))
|
||||
}
|
||||
}
|
||||
|
||||
update_record(val, &mut self.closure, self.span, self.columns.as_ref());
|
||||
value
|
||||
} else {
|
||||
eval_value(&mut self.closure, self.span, value)
|
||||
|
@ -188,7 +188,7 @@ fn get_theme_from_asset_file(
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: format!("Unknown HTML theme '{}'", theme_name),
|
||||
err_message: format!("Unknown HTML theme '{theme_name}'"),
|
||||
span: theme_span,
|
||||
});
|
||||
}
|
||||
@ -774,8 +774,7 @@ mod tests {
|
||||
for key in required_keys {
|
||||
assert!(
|
||||
theme_map.contains_key(key),
|
||||
"Expected theme to contain key '{}'",
|
||||
key
|
||||
"Expected theme to contain key '{key}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -792,15 +791,13 @@ mod tests {
|
||||
if let Err(err) = result {
|
||||
assert!(
|
||||
matches!(err, ShellError::TypeMismatch { .. }),
|
||||
"Expected TypeMismatch error, got: {:?}",
|
||||
err
|
||||
"Expected TypeMismatch error, got: {err:?}"
|
||||
);
|
||||
|
||||
if let ShellError::TypeMismatch { err_message, span } = err {
|
||||
assert!(
|
||||
err_message.contains("doesnt-exist"),
|
||||
"Error message should mention theme name, got: {}",
|
||||
err_message
|
||||
"Error message should mention theme name, got: {err_message}"
|
||||
);
|
||||
assert_eq!(span.start, 0);
|
||||
assert_eq!(span.end, 13);
|
||||
|
@ -161,28 +161,28 @@ fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else if let Some(v) = num.to_i16() {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else if let Some(v) = num.to_i32() {
|
||||
let bytes = v.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
} else {
|
||||
let bytes = num.to_ne_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in bytes {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
@ -193,7 +193,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
Value::Binary { val, .. } => {
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in val {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
@ -204,7 +204,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
let raw_bytes = val.as_bytes();
|
||||
let mut raw_string = "".to_string();
|
||||
for ch in raw_bytes {
|
||||
raw_string.push_str(&format!("{:08b} ", ch));
|
||||
raw_string.push_str(&format!("{ch:08b} "));
|
||||
}
|
||||
Value::string(raw_string.trim(), span)
|
||||
}
|
||||
|
@ -16,6 +16,11 @@ impl Command for FormatNumber {
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("format number")
|
||||
.input_output_types(vec![(Type::Number, Type::record())])
|
||||
.switch(
|
||||
"no-prefix",
|
||||
"don't include the binary, hex or octal prefixes",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Conversions)
|
||||
}
|
||||
|
||||
@ -24,20 +29,36 @@ impl Command for FormatNumber {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get a record containing multiple formats for the number 42",
|
||||
example: "42 | format number",
|
||||
result: Some(Value::test_record(record! {
|
||||
"binary" => Value::test_string("0b101010"),
|
||||
"debug" => Value::test_string("42"),
|
||||
"display" => Value::test_string("42"),
|
||||
"lowerexp" => Value::test_string("4.2e1"),
|
||||
"lowerhex" => Value::test_string("0x2a"),
|
||||
"octal" => Value::test_string("0o52"),
|
||||
"upperexp" => Value::test_string("4.2E1"),
|
||||
"upperhex" => Value::test_string("0x2A"),
|
||||
})),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a record containing multiple formats for the number 42",
|
||||
example: "42 | format number",
|
||||
result: Some(Value::test_record(record! {
|
||||
"debug" => Value::test_string("42"),
|
||||
"display" => Value::test_string("42"),
|
||||
"binary" => Value::test_string("0b101010"),
|
||||
"lowerexp" => Value::test_string("4.2e1"),
|
||||
"upperexp" => Value::test_string("4.2E1"),
|
||||
"lowerhex" => Value::test_string("0x2a"),
|
||||
"upperhex" => Value::test_string("0x2A"),
|
||||
"octal" => Value::test_string("0o52"),
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
description: "Format float without prefixes",
|
||||
example: "3.14 | format number --no-prefix",
|
||||
result: Some(Value::test_record(record! {
|
||||
"debug" => Value::test_string("3.14"),
|
||||
"display" => Value::test_string("3.14"),
|
||||
"binary" => Value::test_string("100000000001001000111101011100001010001111010111000010100011111"),
|
||||
"lowerexp" => Value::test_string("3.14e0"),
|
||||
"upperexp" => Value::test_string("3.14E0"),
|
||||
"lowerhex" => Value::test_string("40091eb851eb851f"),
|
||||
"upperhex" => Value::test_string("40091EB851EB851F"),
|
||||
"octal" => Value::test_string("400110753412172702437"),
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -59,14 +80,24 @@ pub(crate) fn format_number(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let args = CellPathOnlyArgs::from(cell_paths);
|
||||
operate(action, args, input, call.head, engine_state.signals())
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Float { val, .. } => format_f64(*val, span),
|
||||
Value::Int { val, .. } => format_i64(*val, span),
|
||||
Value::Filesize { val, .. } => format_i64(val.get(), span),
|
||||
Value::Float { val, .. } => format_f64(*val, false, span),
|
||||
Value::Int { val, .. } => format_i64(*val, false, span),
|
||||
Value::Filesize { val, .. } => format_i64(val.get(), false, span),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
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(
|
||||
record! {
|
||||
"binary" => Value::string(format!("{num:#b}"), span),
|
||||
"debug" => Value::string(format!("{num:#?}"), span),
|
||||
"display" => Value::string(format!("{num}"), span),
|
||||
"binary" => Value::string(
|
||||
if no_prefix { format!("{num:b}") } else { format!("{num:#b}") },
|
||||
span,
|
||||
),
|
||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||
"lowerhex" => Value::string(format!("{num:#x}"), span),
|
||||
"octal" => Value::string(format!("{num:#o}"), span),
|
||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||
"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,
|
||||
)
|
||||
}
|
||||
|
||||
fn format_f64(num: f64, span: Span) -> Value {
|
||||
fn format_f64(num: f64, no_prefix: bool, span: Span) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
||||
"debug" => Value::string(format!("{num:#?}"), span),
|
||||
"display" => Value::string(format!("{num}"), span),
|
||||
"binary" => Value::string(
|
||||
if no_prefix {
|
||||
format!("{:b}", num.to_bits())
|
||||
} else {
|
||||
format!("{:#b}", num.to_bits())
|
||||
},
|
||||
span,
|
||||
),
|
||||
"lowerexp" => Value::string(format!("{num:#e}"), span),
|
||||
"lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
|
||||
"octal" => Value::string(format!("{:0o}", num.to_bits()), span),
|
||||
"upperexp" => Value::string(format!("{num:#E}"), span),
|
||||
"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,
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -15,17 +15,17 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.0", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "1.1", default-features = false }
|
||||
shadow-rs = { version = "1.2", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
|
||||
shadow-rs = { version = "1.2", default-features = false, features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { workspace = true }
|
||||
@ -43,8 +43,3 @@ plugin = [
|
||||
"nu-protocol/plugin",
|
||||
"os",
|
||||
]
|
||||
|
||||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
|
@ -296,7 +296,7 @@ fn run(
|
||||
} else {
|
||||
let value = stream.into_value();
|
||||
let base_description = value.get_type().to_string();
|
||||
Value::string(format!("{} (stream)", base_description), head)
|
||||
Value::string(format!("{base_description} (stream)"), head)
|
||||
}
|
||||
}
|
||||
PipelineData::Value(value, ..) => {
|
||||
|
@ -229,7 +229,7 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
|
||||
error: "invalid error format.".into(),
|
||||
msg: "`$.label.start` should be smaller than `$.label.end`".into(),
|
||||
span: Some(label_span),
|
||||
help: Some(format!("{} > {}", span_start, span_end)),
|
||||
help: Some(format!("{span_start} > {span_end}")),
|
||||
inner: vec![],
|
||||
};
|
||||
}
|
||||
|
@ -69,5 +69,5 @@ pub use return_::Return;
|
||||
pub use scope::*;
|
||||
pub use try_::Try;
|
||||
pub use use_::Use;
|
||||
pub use version::Version;
|
||||
pub use version::{VERSION_NU_FEATURES, Version};
|
||||
pub use while_::While;
|
||||
|
@ -86,12 +86,16 @@ impl Command for OverlayHide {
|
||||
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);
|
||||
for (name, val) in env_vars_to_restore {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
|
||||
for (name, val) in env_vars_to_keep {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
|
||||
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_protocol::engine::StateWorkingSet;
|
||||
use shadow_rs::shadow;
|
||||
|
||||
shadow!(build);
|
||||
|
||||
/// Static container for the cargo features used by the `version` command.
|
||||
///
|
||||
/// This `OnceLock` holds the features from `nu`.
|
||||
/// When you build `nu_cmd_lang`, Cargo doesn't pass along the same features that `nu` itself uses.
|
||||
/// By setting this static before calling `version`, you make it show `nu`'s features instead
|
||||
/// of `nu_cmd_lang`'s.
|
||||
///
|
||||
/// Embedders can set this to any feature list they need, but in most cases you'll probably want to
|
||||
/// pass the cargo features of your host binary.
|
||||
///
|
||||
/// # How to get cargo features in your build script
|
||||
///
|
||||
/// In your binary's build script:
|
||||
/// ```rust,ignore
|
||||
/// // Re-export CARGO_CFG_FEATURE to the main binary.
|
||||
/// // It holds all the features that cargo sets for your binary as a comma-separated list.
|
||||
/// println!(
|
||||
/// "cargo:rustc-env=NU_FEATURES={}",
|
||||
/// std::env::var("CARGO_CFG_FEATURE").expect("set by cargo")
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Then, before you call `version`:
|
||||
/// ```rust,ignore
|
||||
/// // This uses static strings, but since we're using `Cow`, you can also pass owned strings.
|
||||
/// let features = env!("NU_FEATURES")
|
||||
/// .split(',')
|
||||
/// .map(Cow::Borrowed)
|
||||
/// .collect();
|
||||
///
|
||||
/// nu_cmd_lang::VERSION_NU_FEATURES
|
||||
/// .set(features)
|
||||
/// .expect("couldn't set VERSION_NU_FEATURES");
|
||||
/// ```
|
||||
pub static VERSION_NU_FEATURES: OnceLock<Vec<Cow<'static, str>>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Version;
|
||||
|
||||
@ -113,7 +150,17 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
||||
|
||||
record.push(
|
||||
"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"))]
|
||||
@ -164,42 +211,12 @@ fn global_allocator() -> &'static str {
|
||||
"standard"
|
||||
}
|
||||
|
||||
fn features_enabled() -> Vec<String> {
|
||||
let mut names = vec!["default".to_string()];
|
||||
|
||||
// NOTE: There should be another way to know features on.
|
||||
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
names.push("trash".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
{
|
||||
names.push("sqlite".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "static-link-openssl")]
|
||||
{
|
||||
names.push("static-link-openssl".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "system-clipboard")]
|
||||
{
|
||||
names.push("system-clipboard".to_string());
|
||||
}
|
||||
|
||||
names.sort();
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Version;
|
||||
use crate::test_examples;
|
||||
test_examples(Version {})
|
||||
test_examples(Version)
|
||||
}
|
||||
}
|
||||
|
@ -221,23 +221,23 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0 {
|
||||
Value::Bool { val, .. } => {
|
||||
write!(f, "{:?}", val)
|
||||
write!(f, "{val:?}")
|
||||
}
|
||||
Value::Int { val, .. } => {
|
||||
write!(f, "{:?}", val)
|
||||
write!(f, "{val:?}")
|
||||
}
|
||||
Value::Float { val, .. } => {
|
||||
write!(f, "{:?}f", val)
|
||||
write!(f, "{val:?}f")
|
||||
}
|
||||
Value::Filesize { val, .. } => {
|
||||
write!(f, "Filesize({:?})", val)
|
||||
write!(f, "Filesize({val:?})")
|
||||
}
|
||||
Value::Duration { val, .. } => {
|
||||
let duration = std::time::Duration::from_nanos(*val as u64);
|
||||
write!(f, "Duration({:?})", duration)
|
||||
write!(f, "Duration({duration:?})")
|
||||
}
|
||||
Value::Date { val, .. } => {
|
||||
write!(f, "Date({:?})", val)
|
||||
write!(f, "Date({val:?})")
|
||||
}
|
||||
Value::Range { val, .. } => match **val {
|
||||
Range::IntRange(range) => match range.end() {
|
||||
@ -280,7 +280,7 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
},
|
||||
},
|
||||
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||
write!(f, "{:?}", val)
|
||||
write!(f, "{val:?}")
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
write!(f, "{{")?;
|
||||
@ -305,22 +305,22 @@ impl std::fmt::Debug for DebuggableValue<'_> {
|
||||
write!(f, "]")
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
write!(f, "Closure({:?})", val)
|
||||
write!(f, "Closure({val:?})")
|
||||
}
|
||||
Value::Nothing { .. } => {
|
||||
write!(f, "Nothing")
|
||||
}
|
||||
Value::Error { error, .. } => {
|
||||
write!(f, "Error({:?})", error)
|
||||
write!(f, "Error({error:?})")
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
write!(f, "Binary({:?})", val)
|
||||
write!(f, "Binary({val:?})")
|
||||
}
|
||||
Value::CellPath { val, .. } => {
|
||||
write!(f, "CellPath({:?})", val.to_string())
|
||||
}
|
||||
Value::Custom { val, .. } => {
|
||||
write!(f, "CustomValue({:?})", val)
|
||||
write!(f, "CustomValue({val:?})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-plugin"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,10 +13,10 @@ version = "0.105.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,12 +14,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.105.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.105.0"
|
||||
version = "0.105.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -16,21 +16,21 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.0", default-features = false }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.105.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.0", default-features = false }
|
||||
nu-system = { path = "../nu-system", version = "0.105.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.105.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.105.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.0", default-features = false }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.2" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||
nu-system = { path = "../nu-system", version = "0.105.2" }
|
||||
nu-table = { path = "../nu-table", version = "0.105.2" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nuon = { path = "../nuon", version = "0.105.0" }
|
||||
nuon = { path = "../nuon", version = "0.105.2" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
@ -226,8 +226,8 @@ sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
|
||||
|
||||
dirs = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -89,7 +89,7 @@ impl Command for Histogram {
|
||||
"frequency-column-name can't be {}",
|
||||
forbidden_column_names
|
||||
.iter()
|
||||
.map(|val| format!("'{}'", val))
|
||||
.map(|val| format!("'{val}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
|
@ -142,7 +142,7 @@ fn into_binary(
|
||||
}
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let value = match input {
|
||||
Value::Binary { .. } => input.clone(),
|
||||
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||
@ -168,7 +168,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
),
|
||||
};
|
||||
|
||||
if _args.compact {
|
||||
if args.compact {
|
||||
let val_span = value.span();
|
||||
if let Value::Binary { val, .. } = value {
|
||||
let val = if cfg!(target_endian = "little") {
|
||||
|
@ -678,7 +678,7 @@ fn parse_value_from_record_as_u32(
|
||||
Value::Int { val, .. } => {
|
||||
if *val < 0 || *val > u32::MAX as i64 {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!("incorrect value for {}", col),
|
||||
msg: format!("incorrect value for {col}"),
|
||||
val_span: *head,
|
||||
call_span: *span,
|
||||
});
|
||||
|
@ -368,8 +368,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellE
|
||||
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
||||
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
|
||||
.to_string(),
|
||||
msg: format!("Invalid sign. Allowed signs are {allowed_signs}").to_string(),
|
||||
val_span: sign.span(),
|
||||
call_span: head,
|
||||
});
|
||||
|
@ -122,8 +122,8 @@ impl Table {
|
||||
.conn
|
||||
.query_row(&table_exists_query, [], |row| row.get(0))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: format!("{:#?}", err),
|
||||
msg: format!("{:#?}", err),
|
||||
error: format!("{err:#?}"),
|
||||
msg: format!("{err:#?}"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
@ -257,7 +257,7 @@ fn insert_in_transaction(
|
||||
let insert_statement = format!(
|
||||
"INSERT INTO [{}] ({}) VALUES ({})",
|
||||
table_name,
|
||||
Itertools::intersperse(val.columns().map(|c| format!("`{}`", c)), ", ".to_string())
|
||||
Itertools::intersperse(val.columns().map(|c| format!("`{c}`")), ", ".to_string())
|
||||
.collect::<String>(),
|
||||
Itertools::intersperse(itertools::repeat_n("?", val.len()), ", ").collect::<String>(),
|
||||
);
|
||||
@ -381,7 +381,7 @@ fn get_columns_with_sqlite_types(
|
||||
.map(|name| (format!("`{}`", name.0), name.1))
|
||||
.any(|(name, _)| name == *c)
|
||||
{
|
||||
columns.push((format!("`{}`", c), nu_value_to_sqlite_type(v)?));
|
||||
columns.push((format!("`{c}`"), nu_value_to_sqlite_type(v)?));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,16 +112,31 @@ impl SQLiteDatabase {
|
||||
if self.path == PathBuf::from(MEMORY_DB) {
|
||||
open_connection_in_memory_custom()
|
||||
} 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(),
|
||||
msg: e.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
})?;
|
||||
conn.busy_handler(Some(SQLiteDatabase::sleeper))
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to set busy handler for SQLite database".into(),
|
||||
msg: e.to_string(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
Ok(conn)
|
||||
}
|
||||
}
|
||||
|
||||
fn sleeper(attempts: i32) -> bool {
|
||||
log::warn!("SQLITE_BUSY, retrying after 250ms (attempt {})", attempts);
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, SqliteError> {
|
||||
let mut table_names =
|
||||
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
||||
@ -158,7 +173,7 @@ impl SQLiteDatabase {
|
||||
filename: String,
|
||||
) -> Result<(), SqliteError> {
|
||||
//vacuum main into 'c:\\temp\\foo.db'
|
||||
conn.execute(&format!("vacuum main into '{}'", filename), [])?;
|
||||
conn.execute(&format!("vacuum main into '{filename}'"), [])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -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> {
|
||||
let flags = OpenFlags::default();
|
||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to open SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
let conn =
|
||||
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to open SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
conn.busy_handler(Some(SQLiteDatabase::sleeper))
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Failed to set busy handler for SQLite custom connection in memory".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
||||
|
@ -126,10 +126,10 @@ impl Command for ViewSource {
|
||||
}
|
||||
let _ = write!(&mut final_contents, "--{}", n.long);
|
||||
if let Some(short) = n.short {
|
||||
let _ = write!(&mut final_contents, "(-{})", short);
|
||||
let _ = write!(&mut final_contents, "(-{short})");
|
||||
}
|
||||
if let Some(arg) = &n.arg {
|
||||
let _ = write!(&mut final_contents, ": {}", arg);
|
||||
let _ = write!(&mut final_contents, ": {arg}");
|
||||
}
|
||||
final_contents.push(' ');
|
||||
}
|
||||
@ -146,7 +146,7 @@ impl Command for ViewSource {
|
||||
let mut c = 0;
|
||||
for (insig, outsig) in type_signatures {
|
||||
c += 1;
|
||||
let s = format!("{} -> {}", insig, outsig);
|
||||
let s = format!("{insig} -> {outsig}");
|
||||
final_contents.push_str(&s);
|
||||
if c != len {
|
||||
final_contents.push_str(", ")
|
||||
|
@ -188,6 +188,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
|
||||
// Strings
|
||||
bind_command! {
|
||||
Ansi,
|
||||
AnsiLink,
|
||||
AnsiStrip,
|
||||
Char,
|
||||
Decode,
|
||||
Encode,
|
||||
@ -250,9 +253,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
// Platform
|
||||
#[cfg(feature = "os")]
|
||||
bind_command! {
|
||||
Ansi,
|
||||
AnsiLink,
|
||||
AnsiStrip,
|
||||
Clear,
|
||||
Du,
|
||||
Input,
|
||||
|
@ -112,8 +112,8 @@ impl Command for Mktemp {
|
||||
.map_err(|_| ShellError::NonUtf8 { span })?,
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{}", e),
|
||||
msg: format!("{}", e),
|
||||
error: format!("{e}"),
|
||||
msg: format!("{e}"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
|
@ -198,7 +198,7 @@ impl Command for Open {
|
||||
let converter = exts_opt.and_then(|exts| {
|
||||
exts.iter().find_map(|ext| {
|
||||
engine_state
|
||||
.find_decl(format!("from {}", ext).as_bytes(), &[])
|
||||
.find_decl(format!("from {ext}").as_bytes(), &[])
|
||||
.map(|id| (id, ext.to_string()))
|
||||
})
|
||||
});
|
||||
@ -314,7 +314,7 @@ fn extract_extensions(filename: &str) -> Vec<String> {
|
||||
if current_extension.is_empty() {
|
||||
current_extension.push_str(part);
|
||||
} else {
|
||||
current_extension = format!("{}.{}", part, current_extension);
|
||||
current_extension = format!("{part}.{current_extension}");
|
||||
}
|
||||
extensions.push(current_extension.clone());
|
||||
}
|
||||
|
@ -91,7 +91,8 @@ impl Command for Save {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
||||
|
||||
let (file, stderr_file) = get_files(&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 signals = engine_state.signals();
|
||||
@ -201,7 +202,8 @@ impl Command for Save {
|
||||
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 {
|
||||
file.write_all(&value_to_bytes(val)?)
|
||||
.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)?;
|
||||
|
||||
// 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.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> {
|
||||
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
|
||||
{
|
||||
(true, true) => std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(path)
|
||||
.map_err(|err| err.into()),
|
||||
fn open_file(
|
||||
engine_state: &EngineState,
|
||||
path: &Path,
|
||||
span: Span,
|
||||
append: bool,
|
||||
) -> Result<File, ShellError> {
|
||||
let file: std::io::Result<File> = match (append, path.exists()) {
|
||||
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
|
||||
_ => {
|
||||
// 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
|
||||
@ -438,22 +442,51 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError>
|
||||
deprecated,
|
||||
reason = "we don't get a IsADirectory error, so we need to provide it"
|
||||
)]
|
||||
Err(nu_protocol::shell_error::io::ErrorKind::from_std(
|
||||
std::io::ErrorKind::IsADirectory,
|
||||
))
|
||||
Err(std::io::ErrorKind::IsADirectory.into())
|
||||
} else {
|
||||
std::fs::File::create(path).map_err(|err| err.into())
|
||||
std::fs::File::create(path)
|
||||
}
|
||||
#[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
|
||||
fn get_files(
|
||||
engine_state: &EngineState,
|
||||
path: &Spanned<PathBuf>,
|
||||
stderr_path: Option<&Spanned<PathBuf>>,
|
||||
append: bool,
|
||||
@ -467,7 +500,7 @@ fn get_files(
|
||||
.transpose()?;
|
||||
|
||||
// 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
|
||||
.map(|(stderr_path, stderr_path_span)| {
|
||||
@ -480,7 +513,7 @@ fn get_files(
|
||||
inner: vec![],
|
||||
})
|
||||
} else {
|
||||
open_file(stderr_path, stderr_path_span, append)
|
||||
open_file(engine_state, stderr_path, stderr_path_span, append)
|
||||
}
|
||||
})
|
||||
.transpose()?;
|
||||
|
@ -49,7 +49,8 @@ impl Command for Start {
|
||||
}
|
||||
// If it's not a URL, treat it as a file path
|
||||
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
|
||||
if full_path.exists() {
|
||||
open_path(full_path, engine_state, stack, path.span)?;
|
||||
|
@ -119,9 +119,9 @@ impl Command for UCp {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let interactive = call.has_flag(engine_state, stack, "interactive")?;
|
||||
let (update, copy_mode) = if call.has_flag(engine_state, stack, "update")? {
|
||||
(UpdateMode::IfOlder, CopyMode::Update)
|
||||
(UpdateMode::ReplaceIfOlder, CopyMode::Update)
|
||||
} else {
|
||||
(UpdateMode::All, CopyMode::Copy)
|
||||
(UpdateMode::ReplaceAll, CopyMode::Copy)
|
||||
};
|
||||
|
||||
let force = call.has_flag(engine_state, stack, "force")?;
|
||||
@ -252,7 +252,7 @@ impl Command for UCp {
|
||||
dereference: !recursive,
|
||||
progress_bar: progress,
|
||||
attributes_only: false,
|
||||
backup: BackupMode::None,
|
||||
backup: BackupMode::NoBackup,
|
||||
copy_contents: false,
|
||||
cli_dereference: false,
|
||||
copy_mode,
|
||||
@ -264,8 +264,6 @@ impl Command for UCp {
|
||||
backup_suffix: String::from("~"),
|
||||
target_dir: None,
|
||||
update,
|
||||
set_selinux_context: false,
|
||||
context: None,
|
||||
};
|
||||
|
||||
if let Err(error) = uu_cp::copy(&sources, &target_path, &options) {
|
||||
@ -274,8 +272,8 @@ impl Command for UCp {
|
||||
uu_cp::Error::NotAllFilesCopied => {}
|
||||
_ => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
@ -375,7 +373,7 @@ fn parse_and_set_attribute(
|
||||
"xattr" => &mut attribute.xattr,
|
||||
_ => {
|
||||
return Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: format!("--preserve flag got an unexpected attribute \"{}\"", val),
|
||||
msg: format!("--preserve flag got an unexpected attribute \"{val}\""),
|
||||
span: value.span(),
|
||||
});
|
||||
}
|
||||
|
@ -74,19 +74,11 @@ impl Command for UMkdir {
|
||||
});
|
||||
}
|
||||
|
||||
let config = uu_mkdir::Config {
|
||||
recursive: IS_RECURSIVE,
|
||||
mode: get_mode(),
|
||||
verbose: is_verbose,
|
||||
set_selinux_context: false,
|
||||
context: None,
|
||||
};
|
||||
|
||||
for dir in directories {
|
||||
if let Err(error) = mkdir(&dir, &config) {
|
||||
if let Err(error) = mkdir(&dir, IS_RECURSIVE, get_mode(), is_verbose) {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
|
@ -95,9 +95,9 @@ impl Command for UMv {
|
||||
uu_mv::OverwriteMode::Force
|
||||
};
|
||||
let update = if call.has_flag(engine_state, stack, "update")? {
|
||||
UpdateMode::IfOlder
|
||||
UpdateMode::ReplaceIfOlder
|
||||
} else {
|
||||
UpdateMode::All
|
||||
UpdateMode::ReplaceAll
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
@ -186,7 +186,7 @@ impl Command for UMv {
|
||||
progress_bar: progress,
|
||||
verbose,
|
||||
suffix: String::from("~"),
|
||||
backup: BackupMode::None,
|
||||
backup: BackupMode::NoBackup,
|
||||
update,
|
||||
target_dir: None,
|
||||
no_target_dir: false,
|
||||
@ -195,8 +195,8 @@ impl Command for UMv {
|
||||
};
|
||||
if let Err(error) = uu_mv::mv(&files, &options) {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{}", error),
|
||||
msg: format!("{}", error),
|
||||
error: format!("{error}"),
|
||||
msg: format!("{error}"),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
|
@ -220,7 +220,7 @@ impl Command for UTouch {
|
||||
inner: Vec::new(),
|
||||
},
|
||||
TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue {
|
||||
msg: format!("Invalid date: {}", date),
|
||||
msg: format!("Invalid date: {date}"),
|
||||
val_span: date_span.expect("touch should've been given a date"),
|
||||
call_span: call.head,
|
||||
},
|
||||
|
@ -8,7 +8,7 @@ use notify_debouncer_full::{
|
||||
use nu_engine::{ClosureEval, command_prelude::*};
|
||||
use nu_protocol::{
|
||||
engine::{Closure, StateWorkingSet},
|
||||
format_shell_error,
|
||||
format_cli_error,
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
use std::{
|
||||
@ -208,7 +208,7 @@ impl Command for Watch {
|
||||
}
|
||||
Err(err) => {
|
||||
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
|
||||
&& 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(..)) {
|
||||
let PipelineData::ListStream(ls, metadata) = input else {
|
||||
unreachable!()
|
||||
@ -221,7 +221,7 @@ fn default(
|
||||
let span = ls.span();
|
||||
let mut stream = ls.into_inner().peekable();
|
||||
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
|
||||
@ -278,8 +278,14 @@ impl DefaultValue {
|
||||
}
|
||||
}
|
||||
|
||||
fn pipeline_data(&mut self) -> Result<PipelineData, ShellError> {
|
||||
self.value().map(|x| x.into_pipeline_data())
|
||||
/// Used when we know the value won't need to be cached to allow streaming.
|
||||
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" => {
|
||||
carapace_suggestion.to_string()
|
||||
}
|
||||
_ => format!("change this to {{ {} }}", span_contents).to_string(),
|
||||
_ => format!("change this to {{ {span_contents} }}").to_string(),
|
||||
};
|
||||
|
||||
report_shell_warning(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use itertools::Either;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{PipelineIterator, Range};
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -18,13 +18,11 @@ impl Command for DropNth {
|
||||
(Type::list(Type::Any), Type::list(Type::Any)),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.required(
|
||||
"row number or row range",
|
||||
// FIXME: we can make this accept either Int or Range when we can compose SyntaxShapes
|
||||
.rest(
|
||||
"rest",
|
||||
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)
|
||||
}
|
||||
|
||||
@ -103,110 +101,125 @@ impl Command for DropNth {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let metadata = input.metadata();
|
||||
let number_or_range = extract_int_or_range(engine_state, stack, call)?;
|
||||
|
||||
let rows = match number_or_range.item {
|
||||
Either::Left(row_number) => {
|
||||
let and_rows: Vec<Spanned<i64>> = call.rest(engine_state, stack, 1)?;
|
||||
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 args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
if args.is_empty() {
|
||||
return Ok(input);
|
||||
}
|
||||
|
||||
let start = range.start() as usize;
|
||||
let (rows_to_drop, min_unbounded_start) = get_rows_to_drop(&args, head)?;
|
||||
|
||||
let end = match range.end() {
|
||||
Bound::Included(end) => end as usize,
|
||||
Bound::Excluded(end) => (end - 1) as usize,
|
||||
Bound::Unbounded => {
|
||||
return Ok(input
|
||||
.into_iter()
|
||||
.take(start)
|
||||
.into_pipeline_data_with_metadata(
|
||||
head,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let end = if let PipelineData::Value(Value::List { vals, .. }, _) = &input {
|
||||
end.min(vals.len() - 1)
|
||||
} else {
|
||||
end
|
||||
};
|
||||
|
||||
(start..=end).collect()
|
||||
}
|
||||
let input = if let Some(cutoff) = min_unbounded_start {
|
||||
input
|
||||
.into_iter()
|
||||
.take(cutoff)
|
||||
.into_pipeline_data_with_metadata(
|
||||
head,
|
||||
engine_state.signals().clone(),
|
||||
metadata.clone(),
|
||||
)
|
||||
} else {
|
||||
input
|
||||
};
|
||||
|
||||
Ok(DropNthIterator {
|
||||
input: input.into_iter(),
|
||||
rows,
|
||||
rows: rows_to_drop,
|
||||
current: 0,
|
||||
}
|
||||
.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_int_or_range(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Spanned<Either<i64, Range>>, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
fn get_rows_to_drop(
|
||||
args: &[Value],
|
||||
head: Span,
|
||||
) -> Result<(VecDeque<usize>, Option<usize>), ShellError> {
|
||||
let mut rows_to_drop = Vec::new();
|
||||
let mut min_unbounded_start: Option<usize> = None;
|
||||
|
||||
let int_opt = value.as_int().map(Either::Left).ok();
|
||||
let range_opt = value.as_range().map(Either::Right).ok();
|
||||
for value in args {
|
||||
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
|
||||
.or(range_opt)
|
||||
.ok_or_else(|| ShellError::TypeMismatch {
|
||||
err_message: "int or range".into(),
|
||||
span: value.span(),
|
||||
})
|
||||
.map(|either| Spanned {
|
||||
item: either,
|
||||
span: value.span(),
|
||||
})
|
||||
match range.end() {
|
||||
Bound::Included(end) => {
|
||||
if end < start {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
rows_to_drop.sort_unstable();
|
||||
rows_to_drop.dedup();
|
||||
|
||||
Ok((VecDeque::from(rows_to_drop), min_unbounded_start))
|
||||
}
|
||||
|
||||
struct DropNthIterator {
|
||||
input: PipelineIterator,
|
||||
rows: Vec<usize>,
|
||||
rows: VecDeque<usize>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
@ -215,9 +228,9 @@ impl Iterator for DropNthIterator {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(row) = self.rows.first() {
|
||||
if let Some(row) = self.rows.front() {
|
||||
if self.current == *row {
|
||||
self.rows.remove(0);
|
||||
self.rows.pop_front();
|
||||
self.current += 1;
|
||||
let _ = self.input.next();
|
||||
continue;
|
||||
|
@ -163,7 +163,12 @@ impl Command for Find {
|
||||
example: r#"[["Larry", "Moe"], ["Victor", "Marina"]] | find --regex "rr""#,
|
||||
result: Some(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(),
|
||||
@ -344,7 +349,10 @@ fn get_match_pattern_from_arguments(
|
||||
// map functions
|
||||
|
||||
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 mut last_match_end = 0;
|
||||
let mut highlighted = String::new();
|
||||
@ -390,7 +398,7 @@ fn highlight_matches_in_string(pattern: &MatchPattern, val: String) -> String {
|
||||
highlighted
|
||||
}
|
||||
|
||||
fn highlight_matches_in_record_or_value(
|
||||
fn highlight_matches_in_value(
|
||||
pattern: &MatchPattern,
|
||||
value: Value,
|
||||
columns_to_search: &[String],
|
||||
@ -412,16 +420,16 @@ fn highlight_matches_in_record_or_value(
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Value::String { val: val_str, .. } = 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)
|
||||
}
|
||||
}
|
||||
*val = highlight_matches_in_value(pattern, std::mem::take(val), &[]);
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
@ -444,24 +452,22 @@ fn find_in_pipelinedata(
|
||||
PipelineData::Value(_, _) => input
|
||||
.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
|
||||
},
|
||||
engine_state.signals(),
|
||||
)?
|
||||
.map(
|
||||
move |x| {
|
||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
||||
},
|
||||
move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search),
|
||||
engine_state.signals(),
|
||||
),
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let stream = stream.modify(|iter| {
|
||||
iter.filter(move |value| {
|
||||
record_or_value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||
})
|
||||
.map(move |x| {
|
||||
highlight_matches_in_record_or_value(&map_pattern, x, &map_columns_to_search)
|
||||
value_should_be_printed(&pattern, value, &columns_to_search, &config)
|
||||
!= pattern.invert
|
||||
})
|
||||
.map(move |x| highlight_matches_in_value(&map_pattern, x, &map_columns_to_search))
|
||||
});
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
match value {
|
||||
@ -507,8 +518,7 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Closure { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => {
|
||||
| Value::Nothing { .. } => {
|
||||
if !pattern.lower_terms.is_empty() {
|
||||
// look for exact match when searching with terms
|
||||
pattern
|
||||
@ -519,37 +529,25 @@ fn value_should_be_printed(pattern: &MatchPattern, value: &Value, config: &Confi
|
||||
string_should_be_printed(pattern, &lower_value)
|
||||
}
|
||||
}
|
||||
Value::Glob { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::Record { .. }
|
||||
| Value::Custom { .. } => string_should_be_printed(pattern, &lower_value),
|
||||
Value::Glob { .. } | Value::CellPath { .. } | Value::Custom { .. } => {
|
||||
string_should_be_printed(pattern, &lower_value)
|
||||
}
|
||||
Value::String { val, .. } => string_should_be_printed(pattern, val),
|
||||
Value::Binary { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn record_or_value_should_be_printed(
|
||||
pattern: &MatchPattern,
|
||||
value: &Value,
|
||||
columns_to_search: &[String],
|
||||
config: &Config,
|
||||
) -> bool {
|
||||
let match_found = match value {
|
||||
Value::List { vals, .. } => vals
|
||||
.iter()
|
||||
.any(|item| value_should_be_printed(pattern, item, &[], config)),
|
||||
Value::Record { val: record, .. } => {
|
||||
// Only perform column selection if given columns.
|
||||
let col_select = !columns_to_search.is_empty();
|
||||
record.iter().any(|(col, val)| {
|
||||
if col_select && !columns_to_search.contains(col) {
|
||||
return false;
|
||||
}
|
||||
value_should_be_printed(pattern, val, config)
|
||||
value_should_be_printed(pattern, val, &[], config)
|
||||
})
|
||||
}
|
||||
_ => value_should_be_printed(pattern, value, config),
|
||||
};
|
||||
|
||||
match_found != pattern.invert
|
||||
Value::Binary { .. } => false,
|
||||
Value::Error { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)]
|
||||
mod tests {
|
||||
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());
|
||||
// Do not output shared join key twice
|
||||
if !(k_seen && k_shared) {
|
||||
record.push(
|
||||
if k_seen { format!("{}_", k) } else { k.clone() },
|
||||
v.clone(),
|
||||
);
|
||||
record.push(if k_seen { format!("{k}_") } else { k.clone() }, v.clone());
|
||||
}
|
||||
}
|
||||
record
|
||||
|
@ -70,7 +70,7 @@ pub use empty::empty;
|
||||
pub use enumerate::Enumerate;
|
||||
pub use every::Every;
|
||||
pub use filter::Filter;
|
||||
pub use find::Find;
|
||||
pub use find::{Find, find_internal};
|
||||
pub use first::First;
|
||||
pub use flatten::Flatten;
|
||||
pub use get::Get;
|
||||
|
@ -212,7 +212,7 @@ impl From<ReadError> for ShellError {
|
||||
},
|
||||
ReadError::TypeMismatch(marker, span) => ShellError::GenericError {
|
||||
error: "Invalid marker while reading MessagePack data".into(),
|
||||
msg: format!("unexpected {:?} in data", marker),
|
||||
msg: format!("unexpected {marker:?} in data"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
|
@ -167,7 +167,7 @@ fn parse_aligned_columns<'a>(
|
||||
let headers: Vec<(String, usize)> = indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, position)| (format!("column{}", i), *position))
|
||||
.map(|(i, position)| (format!("column{i}"), *position))
|
||||
.collect();
|
||||
|
||||
construct(ls.iter().map(|s| s.to_owned()), headers)
|
||||
|
@ -252,7 +252,7 @@ fn process_xml_parse_error(source: String, err: roxmltree::Error, span: Span) ->
|
||||
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(
|
||||
format!("Unexpected close tag {actual}, expected {expected}"),
|
||||
|
@ -158,27 +158,26 @@ fn convert_yaml_value_to_nu_value(
|
||||
}
|
||||
serde_yaml::Value::Tagged(t) => {
|
||||
let tag = &t.tag;
|
||||
let value = match &t.value {
|
||||
|
||||
match &t.value {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
serde_yaml::Value::Null => {
|
||||
let val = format!("{}", tag).trim().to_string();
|
||||
let val = format!("{tag}").trim().to_string();
|
||||
Value::string(val, span)
|
||||
}
|
||||
v => convert_yaml_value_to_nu_value(v, span, val_span)?,
|
||||
};
|
||||
|
||||
value
|
||||
}
|
||||
}
|
||||
serde_yaml::Value::Null => Value::nothing(span),
|
||||
x => unimplemented!("Unsupported YAML case: {:?}", x),
|
||||
|
@ -50,7 +50,7 @@ fn make_unsupported_input_error(
|
||||
) -> ShellError {
|
||||
ShellError::UnsupportedInput {
|
||||
msg: "expected table or record".to_string(),
|
||||
input: format!("input type: {}", r#type),
|
||||
input: format!("input type: {type}"),
|
||||
msg_span: head,
|
||||
input_span: span,
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ fn table(
|
||||
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()
|
||||
{
|
||||
String::from("")
|
||||
@ -260,9 +260,7 @@ fn table(
|
||||
)
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
output_string
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineData, bool) {
|
||||
|
@ -2,6 +2,7 @@ use chrono::Datelike;
|
||||
use chrono_humanize::HumanTime;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ByteStream, PipelineMetadata, format_duration, shell_error::io::IoError};
|
||||
use nu_utils::ObviousFloat;
|
||||
use std::io::Write;
|
||||
|
||||
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
||||
@ -164,7 +165,7 @@ fn local_into_string(
|
||||
match value {
|
||||
Value::Bool { 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::Duration { val, .. } => format_duration(val),
|
||||
Value::Date { val, .. } => {
|
||||
|
@ -210,8 +210,7 @@ impl Job {
|
||||
from_type: "record".into(),
|
||||
span: entry_span,
|
||||
help: Some(format!(
|
||||
"Invalid column \"{}\" in xml entry. Only \"{}\", \"{}\" and \"{}\" are permitted",
|
||||
bad_column, COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME
|
||||
"Invalid column \"{bad_column}\" in xml entry. Only \"{COLUMN_TAG_NAME}\", \"{COLUMN_ATTRS_NAME}\" and \"{COLUMN_CONTENT_NAME}\" are permitted"
|
||||
)),
|
||||
});
|
||||
}
|
||||
@ -399,7 +398,7 @@ impl Job {
|
||||
});
|
||||
}
|
||||
|
||||
let content_text = format!("{} {}", tag, content);
|
||||
let content_text = format!("{tag} {content}");
|
||||
// PI content must NOT be escaped
|
||||
// https://www.w3.org/TR/xml/#sec-pi
|
||||
let pi_content = BytesPI::new(content_text.as_str());
|
||||
@ -428,8 +427,7 @@ impl Job {
|
||||
from_type: Type::record().to_string(),
|
||||
span: tag_span,
|
||||
help: Some(format!(
|
||||
"Incorrect tag name {}, tag name can not start with ! or ?",
|
||||
tag
|
||||
"Incorrect tag name {tag}, tag name can not start with ! or ?"
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ fn parse_closure_result(
|
||||
} else {
|
||||
let error = ShellError::GenericError {
|
||||
error: "Invalid block return".into(),
|
||||
msg: format!("Unexpected record key '{}'", k),
|
||||
msg: format!("Unexpected record key '{k}'"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
|
@ -1,8 +1,5 @@
|
||||
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_utils::IgnoreCaseExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
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 nu_color_config::StyleComputer;
|
||||
use nu_engine::{command_prelude::*, scope::ScopeData};
|
||||
use crate::filters::find_internal;
|
||||
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpAliases;
|
||||
@ -72,31 +71,20 @@ pub fn help_aliases(
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
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 {
|
||||
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,
|
||||
engine_state,
|
||||
stack,
|
||||
&f.item,
|
||||
&["name", "description"],
|
||||
&string_style,
|
||||
&highlight_style,
|
||||
)?;
|
||||
|
||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
||||
Ok(build_help_aliases(engine_state, stack, head))
|
||||
} else {
|
||||
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 {
|
||||
span: Span::merge_many(rest.iter().map(|s| s.span)),
|
||||
});
|
||||
};
|
||||
|
||||
let alias_expansion =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(alias.wrapped_call.span));
|
||||
let description = alias.description();
|
||||
let extra_desc = alias.extra_description();
|
||||
let help = get_full_help(alias, engine_state, stack);
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
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())
|
||||
Ok(Value::string(help, 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);
|
||||
scope_data.populate_decls();
|
||||
|
||||
scope_data.collect_aliases(span)
|
||||
Value::list(scope_data.collect_aliases(span), span).into_pipeline_data()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use crate::filters::find_internal;
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -52,31 +51,20 @@ pub fn help_commands(
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
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 {
|
||||
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||
let found_cmds_vec = highlight_search_in_table(
|
||||
return find_internal(
|
||||
all_cmds_vec,
|
||||
engine_state,
|
||||
stack,
|
||||
&f.item,
|
||||
&["name", "description", "search_terms"],
|
||||
&string_style,
|
||||
&highlight_style,
|
||||
)?;
|
||||
|
||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_commands(engine_state, head);
|
||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
||||
Ok(build_help_commands(engine_state, head))
|
||||
} else {
|
||||
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 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 {
|
||||
let name = if let Some(short) = named_param.short {
|
||||
if named_param.long.is_empty() {
|
||||
format!("-{}", short)
|
||||
format!("-{short}")
|
||||
} else {
|
||||
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
|
||||
Value::list(found_cmds_vec, span).into_pipeline_data()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use crate::filters::find_internal;
|
||||
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -72,31 +71,20 @@ pub fn help_externs(
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
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 {
|
||||
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,
|
||||
engine_state,
|
||||
stack,
|
||||
&f.item,
|
||||
&["name", "description"],
|
||||
&string_style,
|
||||
&highlight_style,
|
||||
)?;
|
||||
|
||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
||||
Ok(build_help_externs(engine_state, stack, head))
|
||||
} else {
|
||||
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);
|
||||
scope.populate_decls();
|
||||
scope.collect_externs(span)
|
||||
Value::list(scope.collect_externs(span), span).into_pipeline_data()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use crate::filters::find_internal;
|
||||
use nu_engine::{command_prelude::*, scope::ScopeData};
|
||||
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 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 {
|
||||
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,
|
||||
engine_state,
|
||||
stack,
|
||||
&f.item,
|
||||
&["name", "description"],
|
||||
&string_style,
|
||||
&highlight_style,
|
||||
)?;
|
||||
|
||||
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
||||
Ok(build_help_modules(engine_state, stack, head))
|
||||
} else {
|
||||
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);
|
||||
scope_data.populate_modules();
|
||||
|
||||
scope_data.collect_modules(span)
|
||||
Value::list(scope_data.collect_modules(span), span).into_pipeline_data()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -68,16 +68,40 @@ impl Command for HelpOperators {
|
||||
]
|
||||
.into_iter()
|
||||
.map(|op| {
|
||||
Value::record(
|
||||
record! {
|
||||
"type" => Value::string(op_type(&op), head),
|
||||
"operator" => Value::string(op.to_string(), head),
|
||||
"name" => Value::string(name(&op), head),
|
||||
"description" => Value::string(description(&op), head),
|
||||
"precedence" => Value::int(op.precedence().into(), head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
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(
|
||||
record! {
|
||||
"type" => Value::string(op_type(&op), head),
|
||||
"operator" => Value::string(op.to_string(), head),
|
||||
"name" => Value::string(name(&op), head),
|
||||
"description" => Value::string(description(&op), head),
|
||||
"precedence" => Value::int(op.precedence().into(), head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -16,7 +16,6 @@ pub use help_modules::HelpModules;
|
||||
pub use help_operators::HelpOperators;
|
||||
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_commands::help_commands;
|
||||
pub(crate) use help_modules::help_modules;
|
||||
|
@ -80,9 +80,9 @@ pub fn http_parse_url(
|
||||
) -> Result<(String, Url), ShellError> {
|
||||
let mut requested_url = raw_url.coerce_into_string()?;
|
||||
if requested_url.starts_with(':') {
|
||||
requested_url = format!("http://localhost{}", requested_url);
|
||||
requested_url = format!("http://localhost{requested_url}");
|
||||
} 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) {
|
||||
@ -382,8 +382,7 @@ fn send_multipart_request(
|
||||
"Content-Type: application/octet-stream".to_string(),
|
||||
"Content-Transfer-Encoding: binary".to_string(),
|
||||
format!(
|
||||
"Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"",
|
||||
col, col
|
||||
"Content-Disposition: form-data; name=\"{col}\"; filename=\"{col}\""
|
||||
),
|
||||
format!("Content-Length: {}", val.len()),
|
||||
];
|
||||
@ -391,7 +390,7 @@ fn send_multipart_request(
|
||||
.add(&mut Cursor::new(val), &headers.join("\r\n"))
|
||||
.map_err(err)?;
|
||||
} else {
|
||||
let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col);
|
||||
let headers = format!(r#"Content-Disposition: form-data; name="{col}""#);
|
||||
builder
|
||||
.add(val.coerce_into_string()?.as_bytes(), &headers)
|
||||
.map_err(err)?;
|
||||
@ -400,7 +399,7 @@ fn send_multipart_request(
|
||||
builder.finish();
|
||||
|
||||
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)
|
||||
}
|
||||
@ -703,26 +702,23 @@ fn transform_response_using_content_type(
|
||||
.expect("Failed to parse content type, and failed to default to text/plain");
|
||||
|
||||
let ext = match (content_type.type_(), content_type.subtype()) {
|
||||
(mime::TEXT, mime::PLAIN) => {
|
||||
let path_extension = url::Url::parse(requested_url)
|
||||
.map_err(|err| {
|
||||
LabeledError::new(err.to_string())
|
||||
.with_help("cannot parse")
|
||||
.with_label(
|
||||
format!("Cannot parse URL: {requested_url}"),
|
||||
Span::unknown(),
|
||||
)
|
||||
})?
|
||||
.path_segments()
|
||||
.and_then(|mut segments| segments.next_back())
|
||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||
.and_then(|name| {
|
||||
PathBuf::from(name)
|
||||
.extension()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
});
|
||||
path_extension
|
||||
}
|
||||
(mime::TEXT, mime::PLAIN) => url::Url::parse(requested_url)
|
||||
.map_err(|err| {
|
||||
LabeledError::new(err.to_string())
|
||||
.with_help("cannot parse")
|
||||
.with_label(
|
||||
format!("Cannot parse URL: {requested_url}"),
|
||||
Span::unknown(),
|
||||
)
|
||||
})?
|
||||
.path_segments()
|
||||
.and_then(|mut segments| segments.next_back())
|
||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||
.and_then(|name| {
|
||||
PathBuf::from(name)
|
||||
.extension()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
}),
|
||||
_ => Some(content_type.subtype().to_string()),
|
||||
};
|
||||
|
||||
|
@ -141,7 +141,7 @@ struct Arguments {
|
||||
redirect: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
fn run_get(
|
||||
pub fn run_get(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
|
@ -1,5 +1,8 @@
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
|
||||
use super::get::run_get;
|
||||
use super::post::run_post;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Http;
|
||||
|
||||
@ -10,7 +13,76 @@ impl Command for Http {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -19,7 +91,7 @@ impl Command for Http {
|
||||
}
|
||||
|
||||
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> {
|
||||
@ -33,8 +105,41 @@ impl Command for Http {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> 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>>,
|
||||
}
|
||||
|
||||
fn run_post(
|
||||
pub fn run_post(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
|
@ -64,7 +64,7 @@ impl Registry for NuShellNightly {
|
||||
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
|
||||
.add_header("Accept", "application/vnd.github.v3+json")
|
||||
.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() {
|
||||
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)
|
||||
} else {
|
||||
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() {
|
||||
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)
|
||||
} else {
|
||||
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 {
|
||||
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(),
|
||||
msg_span: head,
|
||||
input_span: val_span,
|
||||
|
@ -1,4 +1,3 @@
|
||||
mod ansi;
|
||||
mod clear;
|
||||
mod dir_info;
|
||||
mod input;
|
||||
@ -10,7 +9,6 @@ mod term;
|
||||
mod ulimit;
|
||||
mod whoami;
|
||||
|
||||
pub use ansi::{Ansi, AnsiLink, AnsiStrip};
|
||||
pub use clear::Clear;
|
||||
pub use dir_info::{DirBuilder, DirInfo, FileInfo};
|
||||
pub use input::Input;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ListStream;
|
||||
use rand::random_range;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomDice;
|
||||
@ -70,8 +71,16 @@ fn dice(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
|
||||
let dice: usize = call.get_flag(engine_state, stack, "dice")?.unwrap_or(1);
|
||||
let sides: usize = call.get_flag(engine_state, stack, "sides")?.unwrap_or(6);
|
||||
let sides: NonZeroUsize = call
|
||||
.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));
|
||||
|
||||
|
@ -135,8 +135,7 @@ fn uuid(
|
||||
_ => {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Unsupported UUID version: {}. Supported versions are 1, 3, 4, 5, and 7.",
|
||||
version
|
||||
"Unsupported UUID version: {version}. Supported versions are 1, 3, 4, 5, and 7."
|
||||
),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
@ -190,8 +189,7 @@ fn validate_flags(
|
||||
if v != 4 && v != 7 {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Unsupported UUID version: {}. Supported versions are 1, 3, 4, 5, and 7.",
|
||||
v
|
||||
"Unsupported UUID version: {v}. Supported versions are 1, 3, 4, 5, and 7."
|
||||
),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
@ -202,7 +200,7 @@ fn validate_flags(
|
||||
.is_some()
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
@ -211,7 +209,7 @@ fn validate_flags(
|
||||
.is_some()
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
@ -220,7 +218,7 @@ fn validate_flags(
|
||||
.is_some()
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
@ -81,33 +81,32 @@ fn process(
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
match columns {
|
||||
Some(record) => {
|
||||
let mut create_stmt = format!("CREATE TABLE {} ( ", new_table_name);
|
||||
let mut create_stmt = format!("CREATE TABLE {new_table_name} ( ");
|
||||
for (column_name, column_datatype) in record {
|
||||
match column_datatype.coerce_str()?.as_ref() {
|
||||
"int" => {
|
||||
create_stmt.push_str(&format!("{} INTEGER, ", column_name));
|
||||
create_stmt.push_str(&format!("{column_name} INTEGER, "));
|
||||
}
|
||||
"float" => {
|
||||
create_stmt.push_str(&format!("{} REAL, ", column_name));
|
||||
create_stmt.push_str(&format!("{column_name} REAL, "));
|
||||
}
|
||||
"str" => {
|
||||
create_stmt.push_str(&format!("{} VARCHAR(255), ", column_name));
|
||||
create_stmt.push_str(&format!("{column_name} VARCHAR(255), "));
|
||||
}
|
||||
|
||||
"bool" => {
|
||||
create_stmt.push_str(&format!("{} BOOLEAN, ", column_name));
|
||||
create_stmt.push_str(&format!("{column_name} BOOLEAN, "));
|
||||
}
|
||||
"datetime" => {
|
||||
create_stmt.push_str(&format!(
|
||||
"{} DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), ",
|
||||
column_name
|
||||
"{column_name} DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "
|
||||
));
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "unsupported column data type".into(),
|
||||
input: format!("{:?}", column_datatype),
|
||||
input: format!("{column_datatype:?}"),
|
||||
msg_span: column_datatype.span(),
|
||||
input_span: column_datatype.span(),
|
||||
});
|
||||
|
@ -92,16 +92,16 @@ impl Command for StorDelete {
|
||||
let sql_stmt = match where_clause_opt {
|
||||
None => {
|
||||
// We're deleting an entire table
|
||||
format!("DROP TABLE {}", new_table_name)
|
||||
format!("DROP TABLE {new_table_name}")
|
||||
}
|
||||
Some(where_clause) => {
|
||||
// We're just deleting some rows
|
||||
let mut delete_stmt = format!("DELETE FROM {} ", new_table_name);
|
||||
let mut delete_stmt = format!("DELETE FROM {new_table_name} ");
|
||||
|
||||
// Yup, this is a bit janky, but I'm not sure a better way to do this without having
|
||||
// --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc.
|
||||
// and other sql syntax. So, for now, just type a sql where clause as a string.
|
||||
delete_stmt.push_str(&format!("WHERE {}", where_clause));
|
||||
delete_stmt.push_str(&format!("WHERE {where_clause}"));
|
||||
delete_stmt
|
||||
}
|
||||
};
|
||||
@ -109,7 +109,9 @@ impl Command for StorDelete {
|
||||
// dbg!(&sql_stmt);
|
||||
conn.execute(&sql_stmt, [])
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from delete".into(),
|
||||
error:
|
||||
"Failed to delete using the SQLite connection in memory from delete.rs."
|
||||
.into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
|
@ -164,34 +164,35 @@ fn process(
|
||||
}
|
||||
let new_table_name = table_name.unwrap_or("table".into());
|
||||
|
||||
let mut create_stmt = format!("INSERT INTO {new_table_name} (");
|
||||
let mut column_placeholders: Vec<String> = Vec::new();
|
||||
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
column_placeholders.push(col.to_string());
|
||||
});
|
||||
|
||||
create_stmt.push_str(&column_placeholders.join(", "));
|
||||
|
||||
// Values are set as placeholders.
|
||||
create_stmt.push_str(") VALUES (");
|
||||
let mut value_placeholders: Vec<String> = Vec::new();
|
||||
for (index, _) in record.columns().enumerate() {
|
||||
value_placeholders.push(format!("?{}", index + 1));
|
||||
}
|
||||
create_stmt.push_str(&value_placeholders.join(", "));
|
||||
create_stmt.push(')');
|
||||
|
||||
// dbg!(&create_stmt);
|
||||
|
||||
// Get the params from the passed values
|
||||
let params = values_to_sql(record.values().cloned())?;
|
||||
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
let mut create_stmt = format!("INSERT INTO {} (", new_table_name);
|
||||
let mut column_placeholders: Vec<String> = Vec::new();
|
||||
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
column_placeholders.push(col.to_string());
|
||||
});
|
||||
|
||||
create_stmt.push_str(&column_placeholders.join(", "));
|
||||
|
||||
// Values are set as placeholders.
|
||||
create_stmt.push_str(") VALUES (");
|
||||
let mut value_placeholders: Vec<String> = Vec::new();
|
||||
for (index, _) in record.columns().enumerate() {
|
||||
value_placeholders.push(format!("?{}", index + 1));
|
||||
}
|
||||
create_stmt.push_str(&value_placeholders.join(", "));
|
||||
create_stmt.push(')');
|
||||
|
||||
// dbg!(&create_stmt);
|
||||
|
||||
// Get the params from the passed values
|
||||
let params = values_to_sql(record.values().cloned())?;
|
||||
|
||||
conn.execute(&create_stmt, params_from_iter(params))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from insert".into(),
|
||||
error: "Failed to insert using the SQLite connection in memory from insert.rs."
|
||||
.into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
|
@ -164,7 +164,7 @@ fn process(
|
||||
}
|
||||
let new_table_name = table_name.unwrap_or("table".into());
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
let mut update_stmt = format!("UPDATE {} ", new_table_name);
|
||||
let mut update_stmt = format!("UPDATE {new_table_name} ");
|
||||
|
||||
update_stmt.push_str("SET ");
|
||||
let mut placeholders: Vec<String> = Vec::new();
|
||||
|
@ -435,7 +435,7 @@ static CODE_LIST: LazyLock<Vec<AnsiCode>> = LazyLock::new(|| { vec![
|
||||
AnsiCode { short_name: Some("h"), long_name: "attr_hidden", code: Style::new().hidden().prefix().to_string()},
|
||||
AnsiCode { short_name: Some("s"), long_name: "attr_strike", code: Style::new().strikethrough().prefix().to_string()},
|
||||
|
||||
AnsiCode{ short_name: None, long_name: "reset", code: "\x1b[0m".to_owned()},
|
||||
AnsiCode{ short_name: Some("rst"), long_name: "reset", code: "\x1b[0m".to_owned()},
|
||||
|
||||
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||||
// Another good reference http://ascii-table.com/ansi-escape-sequences.php
|
||||
@ -633,6 +633,11 @@ Operating system commands:
|
||||
example: r#"$"(ansi --escape '3;93;41m')Hello(ansi reset)" # italic bright yellow on red background"#,
|
||||
result: Some(Value::test_string("\u{1b}[3;93;41mHello\u{1b}[0m")),
|
||||
},
|
||||
Example {
|
||||
description: "Use simple hex string",
|
||||
example: r#"$"(ansi '#4169E1')Hello(ansi reset)" # royal blue foreground color"#,
|
||||
result: Some(Value::test_string("\u{1b}[38;2;65;105;225mHello\u{1b}[0m")),
|
||||
},
|
||||
Example {
|
||||
description: "Use structured escape codes",
|
||||
example: r#"let bold_blue_on_red = { # `fg`, `bg`, `attr` are the acceptable keys, all other keys are considered invalid and will throw errors.
|
||||
@ -874,13 +879,13 @@ fn generate_ansi_code_list(
|
||||
record! {
|
||||
"name" => name,
|
||||
"preview" => preview,
|
||||
"short name" => short_name,
|
||||
"short_name" => short_name,
|
||||
"code" => code,
|
||||
}
|
||||
} else {
|
||||
record! {
|
||||
"name" => name,
|
||||
"short name" => short_name,
|
||||
"short_name" => short_name,
|
||||
"code" => code,
|
||||
}
|
||||
};
|
||||
@ -904,7 +909,7 @@ fn build_ansi_hashmap(v: &[AnsiCode]) -> HashMap<&str, &str> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::platform::ansi::ansi_::Ansi;
|
||||
use crate::strings::ansi::ansi_::Ansi;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
@ -915,7 +920,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn no_duplicate_short_names() {
|
||||
use crate::platform::ansi::ansi_::CODE_LIST;
|
||||
use crate::strings::ansi::ansi_::CODE_LIST;
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
@ -931,14 +936,13 @@ mod tests {
|
||||
|
||||
assert!(
|
||||
duplicates.is_empty(),
|
||||
"Duplicate short_names found: {:?}",
|
||||
duplicates
|
||||
"Duplicate short_names found: {duplicates:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicate_long_names() {
|
||||
use crate::platform::ansi::ansi_::CODE_LIST;
|
||||
use crate::strings::ansi::ansi_::CODE_LIST;
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
@ -953,8 +957,7 @@ mod tests {
|
||||
|
||||
assert!(
|
||||
duplicates.is_empty(),
|
||||
"Duplicate long_names found: {:?}",
|
||||
duplicates
|
||||
"Duplicate long_names found: {duplicates:?}"
|
||||
);
|
||||
}
|
||||
}
|
@ -155,9 +155,9 @@ fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
|
||||
&arg.format_value.item
|
||||
};
|
||||
if d.fract() == 0.0 {
|
||||
Value::string(format!("{} {}", d, unit), inner_span)
|
||||
Value::string(format!("{d} {unit}"), inner_span)
|
||||
} else {
|
||||
Value::string(format!("{:.float_precision$} {}", d, unit), inner_span)
|
||||
Value::string(format!("{d:.float_precision$} {unit}"), inner_span)
|
||||
}
|
||||
}
|
||||
Err(e) => Value::error(e, inner_span),
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod ansi;
|
||||
mod base;
|
||||
mod char_;
|
||||
mod detect_columns;
|
||||
@ -8,6 +9,7 @@ mod parse;
|
||||
mod split;
|
||||
mod str_;
|
||||
|
||||
pub use ansi::{Ansi, AnsiLink, AnsiStrip};
|
||||
pub use base::{
|
||||
DecodeBase32, DecodeBase32Hex, DecodeBase64, DecodeHex, EncodeBase32, EncodeBase32Hex,
|
||||
EncodeBase64, EncodeHex,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use fancy_regex::{Captures, Regex};
|
||||
use fancy_regex::{Captures, Regex, RegexBuilder};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ListStream, Signals, engine::StateWorkingSet};
|
||||
use std::collections::VecDeque;
|
||||
@ -31,6 +31,12 @@ impl Command for Parse {
|
||||
(Type::List(Box::new(Type::Any)), Type::table()),
|
||||
])
|
||||
.switch("regex", "use full regex syntax for patterns", Some('r'))
|
||||
.named(
|
||||
"backtrack",
|
||||
SyntaxShape::Int,
|
||||
"set the max backtrack limit for regex",
|
||||
Some('b'),
|
||||
)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
@ -96,6 +102,14 @@ impl Command for Parse {
|
||||
"capture0" => Value::test_string("b"),
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
description: "Parse a string with a manually set fancy-regex backtrack limit",
|
||||
example: "\"hi there\" | parse --backtrack 1500000 \"{foo} {bar}\"",
|
||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||
"foo" => Value::test_string("hi"),
|
||||
"bar" => Value::test_string("there"),
|
||||
})])),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -112,7 +126,10 @@ impl Command for Parse {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let regex: bool = call.has_flag(engine_state, stack, "regex")?;
|
||||
operate(engine_state, pattern, regex, call, input)
|
||||
let backtrack_limit: usize = call
|
||||
.get_flag(engine_state, stack, "backtrack")?
|
||||
.unwrap_or(1_000_000); // 1_000_000 is fancy_regex default
|
||||
operate(engine_state, pattern, regex, backtrack_limit, call, input)
|
||||
}
|
||||
|
||||
fn run_const(
|
||||
@ -123,7 +140,17 @@ impl Command for Parse {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Spanned<String> = call.req_const(working_set, 0)?;
|
||||
let regex: bool = call.has_flag_const(working_set, "regex")?;
|
||||
operate(working_set.permanent(), pattern, regex, call, input)
|
||||
let backtrack_limit: usize = call
|
||||
.get_flag_const(working_set, "backtrack")?
|
||||
.unwrap_or(1_000_000);
|
||||
operate(
|
||||
working_set.permanent(),
|
||||
pattern,
|
||||
regex,
|
||||
backtrack_limit,
|
||||
call,
|
||||
input,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,6 +158,7 @@ fn operate(
|
||||
engine_state: &EngineState,
|
||||
pattern: Spanned<String>,
|
||||
regex: bool,
|
||||
backtrack_limit: usize,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
@ -145,13 +173,16 @@ fn operate(
|
||||
build_regex(&pattern_item, pattern_span)?
|
||||
};
|
||||
|
||||
let regex = Regex::new(&item_to_parse).map_err(|e| ShellError::GenericError {
|
||||
error: "Error with regular expression".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(pattern_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
let regex = RegexBuilder::new(&item_to_parse)
|
||||
.backtrack_limit(backtrack_limit)
|
||||
.build()
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Error with regular expression".into(),
|
||||
msg: e.to_string(),
|
||||
span: Some(pattern_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
|
||||
let columns = regex
|
||||
.capture_names()
|
||||
|
@ -103,7 +103,7 @@ fn run(
|
||||
if first {
|
||||
first = false;
|
||||
} else if let Some(separator) = &separator {
|
||||
write!(buffer, "{}", separator).map_err(&from_io_error)?;
|
||||
write!(buffer, "{separator}").map_err(&from_io_error)?;
|
||||
}
|
||||
|
||||
match value {
|
||||
|
@ -292,7 +292,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for expectation in &cases {
|
||||
println!("{:?}", expectation);
|
||||
println!("{expectation:?}");
|
||||
let expected = expectation.expected;
|
||||
let actual = action(
|
||||
&word,
|
||||
|
@ -106,7 +106,7 @@ impl Command for NuCheck {
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let result = if as_module || path.is_dir() {
|
||||
if as_module || path.is_dir() {
|
||||
parse_file_or_dir_module(
|
||||
path.to_string_lossy().as_bytes(),
|
||||
&mut working_set,
|
||||
@ -120,9 +120,7 @@ impl Command for NuCheck {
|
||||
working_set.files = FileStack::with_file(path.clone());
|
||||
parse_file_script(&path, &mut working_set, is_debug, path_span, call.head)
|
||||
// The working set is not merged, so no need to pop the file from the stack.
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Failed to execute command".into(),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user