Merge branch 'main' into callgrind-benches

This commit is contained in:
Filip Andersson 2024-06-26 16:32:31 +02:00 committed by GitHub
commit ce5b95c63a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
274 changed files with 11282 additions and 3723 deletions

View File

@ -18,6 +18,14 @@ updates:
ignore: ignore:
- dependency-name: "*" - dependency-name: "*"
update-types: ["version-update:semver-patch"] update-types: ["version-update:semver-patch"]
groups:
# Only update polars as a whole as there are many subcrates that need to
# be updated at once. We explicitly depend on some of them, so batch their
# updates to not take up dependabot PR slots with dysfunctional PRs
polars:
patterns:
- "polars"
- "polars-*"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:

View File

@ -19,7 +19,7 @@ jobs:
# Prevent sudden announcement of a new advisory from failing ci: # Prevent sudden announcement of a new advisory from failing ci:
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- uses: rustsec/audit-check@v1.4.1 - uses: rustsec/audit-check@v1.4.1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -33,10 +33,10 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
@ -66,10 +66,10 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Tests - name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
@ -95,10 +95,10 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install Nushell - name: Install Nushell
run: cargo install --path . --locked --no-default-features run: cargo install --path . --locked --no-default-features
@ -146,10 +146,10 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Clippy - name: Clippy
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS

View File

@ -27,7 +27,7 @@ jobs:
# if: github.repository == 'nushell/nightly' # if: github.repository == 'nushell/nightly'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
if: github.repository == 'nushell/nightly' if: github.repository == 'nushell/nightly'
with: with:
ref: main ref: main
@ -112,7 +112,7 @@ jobs:
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
with: with:
ref: main ref: main
fetch-depth: 0 fetch-depth: 0
@ -122,7 +122,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''
@ -161,7 +161,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release # REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo # Create a release only in nushell/nightly repo
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v2.0.5 uses: softprops/action-gh-release@v2.0.6
if: ${{ startsWith(github.repository, 'nushell/nightly') }} if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with: with:
prerelease: true prerelease: true
@ -181,7 +181,7 @@ jobs:
- name: Waiting for Release - name: Waiting for Release
run: sleep 1800 run: sleep 1800
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
with: with:
ref: main ref: main

View File

@ -62,14 +62,14 @@ jobs:
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.7
- name: Update Rust Toolchain Target - name: Update Rust Toolchain Target
run: | run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
cache: false cache: false
@ -91,7 +91,7 @@ jobs:
# REF: https://github.com/marketplace/actions/gh-release # REF: https://github.com/marketplace/actions/gh-release
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v2.0.5 uses: softprops/action-gh-release@v2.0.6
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
draft: true draft: true

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Actions Repository - name: Checkout Actions Repository
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@v1.22.0 uses: crate-ci/typos@v1.22.9

26
CITATION.cff Normal file
View File

@ -0,0 +1,26 @@
cff-version: 1.2.0
title: 'Nushell'
message: >-
If you use this software and wish to cite it,
you can use the metadata from this file.
type: software
authors:
- name: "The Nushell Project Team"
identifiers:
- type: url
value: 'https://github.com/nushell/nushell'
description: Repository
repository-code: 'https://github.com/nushell/nushell'
url: 'https://www.nushell.sh/'
abstract: >-
The goal of the Nushell project is to take the Unix
philosophy of shells, where pipes connect simple commands
together, and bring it to the modern style of development.
Thus, rather than being either a shell, or a programming
language, Nushell connects both by bringing a rich
programming language and a full-featured shell together
into one package.
keywords:
- nushell
- shell
license: MIT

332
Cargo.lock generated
View File

@ -481,17 +481,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8" checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8"
[[package]]
name = "brotli"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor 2.5.1",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "5.0.0" version = "5.0.0"
@ -500,17 +489,7 @@ checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
"brotli-decompressor 4.0.0", "brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
] ]
[[package]] [[package]]
@ -874,7 +853,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"unicode-width", "unicode-width",
] ]
@ -944,6 +923,15 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -1242,6 +1230,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "doctest-file"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
[[package]] [[package]]
name = "downcast-rs" name = "downcast-rs"
version = "1.2.1" version = "1.2.1"
@ -1298,6 +1292,9 @@ name = "either"
version = "1.11.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "eml-parser" name = "eml-parser"
@ -1699,9 +1696,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "git2" name = "git2"
version = "0.18.3" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"libc", "libc",
@ -1797,6 +1794,7 @@ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"allocator-api2", "allocator-api2",
"rayon", "rayon",
"serde",
] ]
[[package]] [[package]]
@ -2072,10 +2070,11 @@ dependencies = [
[[package]] [[package]]
name = "interprocess" name = "interprocess"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4d0250d41da118226e55b3d50ca3f0d9e0a0f6829b92f543ac0054aeea1572" checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3"
dependencies = [ dependencies = [
"doctest-file",
"libc", "libc",
"recvmsg", "recvmsg",
"widestring", "widestring",
@ -2334,9 +2333,9 @@ dependencies = [
[[package]] [[package]]
name = "libgit2-sys" name = "libgit2-sys"
version = "0.16.2+1.7.2" version = "0.17.0+1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2808,7 +2807,7 @@ dependencies = [
[[package]] [[package]]
name = "nu" name = "nu"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"assert_cmd", "assert_cmd",
"crossterm", "crossterm",
@ -2862,7 +2861,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cli" name = "nu-cli"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossterm", "crossterm",
@ -2897,7 +2896,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-base" name = "nu-cmd-base"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"miette", "miette",
@ -2909,7 +2908,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-extra" name = "nu-cmd-extra"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"fancy-regex", "fancy-regex",
"heck 0.5.0", "heck 0.5.0",
@ -2934,7 +2933,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"nu-engine", "nu-engine",
@ -2946,7 +2945,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-cmd-plugin" name = "nu-cmd-plugin"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"nu-engine", "nu-engine",
@ -2957,7 +2956,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-color-config" name = "nu-color-config"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"nu-engine", "nu-engine",
@ -2969,12 +2968,12 @@ dependencies = [
[[package]] [[package]]
name = "nu-command" name = "nu-command"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"alphanumeric-sort", "alphanumeric-sort",
"base64 0.22.1", "base64 0.22.1",
"bracoxide", "bracoxide",
"brotli 5.0.0", "brotli",
"byteorder", "byteorder",
"bytesize", "bytesize",
"calamine", "calamine",
@ -3067,7 +3066,7 @@ dependencies = [
"uu_mv", "uu_mv",
"uu_uname", "uu_uname",
"uu_whoami", "uu_whoami",
"uucore 0.0.25", "uucore",
"uuid", "uuid",
"v_htmlescape", "v_htmlescape",
"wax", "wax",
@ -3076,9 +3075,20 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "nu-derive-value"
version = "0.95.1"
dependencies = [
"convert_case",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "nu-engine" name = "nu-engine"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-glob", "nu-glob",
"nu-path", "nu-path",
@ -3088,7 +3098,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-explore" name = "nu-explore"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"ansi-str", "ansi-str",
"anyhow", "anyhow",
@ -3113,14 +3123,14 @@ dependencies = [
[[package]] [[package]]
name = "nu-glob" name = "nu-glob"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"doc-comment", "doc-comment",
] ]
[[package]] [[package]]
name = "nu-json" name = "nu-json"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"linked-hash-map", "linked-hash-map",
"num-traits", "num-traits",
@ -3130,7 +3140,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-lsp" name = "nu-lsp"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"assert-json-diff", "assert-json-diff",
"crossbeam-channel", "crossbeam-channel",
@ -3151,7 +3161,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-parser" name = "nu-parser"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"bytesize", "bytesize",
"chrono", "chrono",
@ -3167,7 +3177,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-path" name = "nu-path"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"dirs-next", "dirs-next",
"omnipath", "omnipath",
@ -3176,7 +3186,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin" name = "nu-plugin"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"log", "log",
"nix", "nix",
@ -3191,7 +3201,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-core" name = "nu-plugin-core"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"interprocess", "interprocess",
"log", "log",
@ -3205,7 +3215,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-engine" name = "nu-plugin-engine"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"log", "log",
"nu-engine", "nu-engine",
@ -3220,7 +3230,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-protocol" name = "nu-plugin-protocol"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"bincode", "bincode",
"nu-protocol", "nu-protocol",
@ -3232,7 +3242,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-plugin-test-support" name = "nu-plugin-test-support"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"nu-cmd-lang", "nu-cmd-lang",
@ -3250,7 +3260,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-pretty-hex" name = "nu-pretty-hex"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"heapless", "heapless",
"nu-ansi-term", "nu-ansi-term",
@ -3259,17 +3269,19 @@ dependencies = [
[[package]] [[package]]
name = "nu-protocol" name = "nu-protocol"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"brotli 5.0.0", "brotli",
"byte-unit", "byte-unit",
"chrono", "chrono",
"chrono-humanize", "chrono-humanize",
"convert_case",
"fancy-regex", "fancy-regex",
"indexmap", "indexmap",
"lru", "lru",
"miette", "miette",
"nix", "nix",
"nu-derive-value",
"nu-path", "nu-path",
"nu-system", "nu-system",
"nu-test-support", "nu-test-support",
@ -3282,7 +3294,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"tempfile", "tempfile",
"thiserror", "thiserror",
"typetag", "typetag",
@ -3290,7 +3302,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-std" name = "nu-std"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"log", "log",
"miette", "miette",
@ -3301,7 +3313,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-system" name = "nu-system"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"itertools 0.12.1", "itertools 0.12.1",
@ -3319,7 +3331,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-table" name = "nu-table"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"fancy-regex", "fancy-regex",
"nu-ansi-term", "nu-ansi-term",
@ -3333,7 +3345,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-term-grid" name = "nu-term-grid"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-utils", "nu-utils",
"unicode-width", "unicode-width",
@ -3341,7 +3353,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-test-support" name = "nu-test-support"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-glob", "nu-glob",
"nu-path", "nu-path",
@ -3353,7 +3365,7 @@ dependencies = [
[[package]] [[package]]
name = "nu-utils" name = "nu-utils"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"crossterm_winapi", "crossterm_winapi",
"log", "log",
@ -3379,7 +3391,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_example" name = "nu_plugin_example"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-cmd-lang", "nu-cmd-lang",
"nu-plugin", "nu-plugin",
@ -3389,7 +3401,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_formats" name = "nu_plugin_formats"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"eml-parser", "eml-parser",
"ical", "ical",
@ -3402,7 +3414,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_gstat" name = "nu_plugin_gstat"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"git2", "git2",
"nu-plugin", "nu-plugin",
@ -3411,7 +3423,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_inc" name = "nu_plugin_inc"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"nu-plugin", "nu-plugin",
"nu-protocol", "nu-protocol",
@ -3420,7 +3432,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_polars" name = "nu_plugin_polars"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz 0.9.0", "chrono-tz 0.9.0",
@ -3443,7 +3455,7 @@ dependencies = [
"polars-plan", "polars-plan",
"polars-utils", "polars-utils",
"serde", "serde",
"sqlparser 0.45.0", "sqlparser 0.47.0",
"tempfile", "tempfile",
"typetag", "typetag",
"uuid", "uuid",
@ -3451,7 +3463,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_query" name = "nu_plugin_query"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"gjson", "gjson",
"nu-plugin", "nu-plugin",
@ -3463,7 +3475,7 @@ dependencies = [
[[package]] [[package]]
name = "nu_plugin_stress_internals" name = "nu_plugin_stress_internals"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"interprocess", "interprocess",
"serde", "serde",
@ -3589,7 +3601,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "nuon" name = "nuon"
version = "0.94.3" version = "0.95.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"fancy-regex", "fancy-regex",
@ -3770,9 +3782,9 @@ dependencies = [
[[package]] [[package]]
name = "os_pipe" name = "os_pipe"
version = "1.1.5" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -4053,9 +4065,9 @@ dependencies = [
[[package]] [[package]]
name = "polars" name = "polars"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea21b858b16b9c0e17a12db2800d11aa5b4bd182be6b3022eb537bbfc1f2db5" checksum = "e148396dca5496566880fa19374f3f789a29db94e3eb458afac1497b4bac5442"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"polars-arrow", "polars-arrow",
@ -4073,9 +4085,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-arrow" name = "polars-arrow"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "725b09f2b5ef31279b66e27bbab63c58d49d8f6696b66b1f46c7eaab95e80f75" checksum = "1cb5e11cd0752ae022fa6ca3afa50a14b0301b7ce53c0135828fbb0f4fa8303e"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"atoi", "atoi",
@ -4121,9 +4133,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-compute" name = "polars-compute"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a796945b14b14fbb79b91ef0406e6fddca2be636e889f81ea5d6ee7d36efb4fe" checksum = "89fc4578f826234cdecb782952aa9c479dc49373f81694a7b439c70b6f609ba0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"either", "either",
@ -4137,9 +4149,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-core" name = "polars-core"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465f70d3e96b6d0b1a43c358ba451286b8c8bd56696feff020d65702aa33e35c" checksum = "e490c6bace1366a558feea33d1846f749a8ca90bd72a6748752bc65bb4710b2a"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bitflags 2.5.0", "bitflags 2.5.0",
@ -4171,9 +4183,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-error" name = "polars-error"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757" checksum = "08888f58e61599b00f5ea0c2ccdc796b54b9859559cc0d4582733509451fa01a"
dependencies = [ dependencies = [
"avro-schema", "avro-schema",
"polars-arrow-format", "polars-arrow-format",
@ -4183,10 +4195,30 @@ dependencies = [
] ]
[[package]] [[package]]
name = "polars-io" name = "polars-expr"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2c8589e418cbe4a48228d64b2a8a40284a82ec3c98817c0c2bcc0267701338b" checksum = "4173591920fe56ad55af025f92eb0d08421ca85705c326a640c43856094e3484"
dependencies = [
"ahash 0.8.11",
"bitflags 2.5.0",
"once_cell",
"polars-arrow",
"polars-core",
"polars-io",
"polars-ops",
"polars-plan",
"polars-time",
"polars-utils",
"rayon",
"smartstring",
]
[[package]]
name = "polars-io"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5842896aea46d975b425d63f156f412aed3cfde4c257b64fb1f43ceea288074e"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"async-trait", "async-trait",
@ -4225,9 +4257,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-json" name = "polars-json"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81224492a649a12b668480c0cf219d703f432509765d2717e72fe32ad16fc701" checksum = "160cbad0145b93ac6a88639aadfa6f7d7c769d05a8674f9b7e895b398cae9901"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"chrono", "chrono",
@ -4246,9 +4278,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-lazy" name = "polars-lazy"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2632b1af668e2058d5f8f916d8fbde3cac63d03ae29a705f598e41dcfeb7f" checksum = "e805ea2ebbc6b7749b0afb31b7fc5d32b42b57ba29b984549d43d3a16114c4a5"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bitflags 2.5.0", "bitflags 2.5.0",
@ -4256,6 +4288,7 @@ dependencies = [
"once_cell", "once_cell",
"polars-arrow", "polars-arrow",
"polars-core", "polars-core",
"polars-expr",
"polars-io", "polars-io",
"polars-json", "polars-json",
"polars-ops", "polars-ops",
@ -4270,13 +4303,13 @@ dependencies = [
[[package]] [[package]]
name = "polars-ops" name = "polars-ops"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efdbdb4d9a92109bc2e0ce8e17af5ae8ab643bb5b7ee9d1d74f0aeffd1fbc95f" checksum = "7b0aed7e169c81b98457641cf82b251f52239a668916c2e683abd1f38df00d58"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"argminmax", "argminmax",
"base64 0.21.7", "base64 0.22.1",
"bytemuck", "bytemuck",
"chrono", "chrono",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
@ -4306,14 +4339,14 @@ dependencies = [
[[package]] [[package]]
name = "polars-parquet" name = "polars-parquet"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b421d2196f786fdfe162db614c8485f8308fe41575d4de634a39bbe460d1eb6a" checksum = "c70670a9e51cac66d0e77fd20b5cc957dbcf9f2660d410633862bb72f846d5b8"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"async-stream", "async-stream",
"base64 0.21.7", "base64 0.22.1",
"brotli 3.5.0", "brotli",
"ethnum", "ethnum",
"flate2", "flate2",
"futures", "futures",
@ -4332,9 +4365,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-pipe" name = "polars-pipe"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48700f1d5bd56a15451e581f465c09541492750360f18637b196f995470a015c" checksum = "0a40ae1b3c74ee07e2d1f7cbf56c5d6e15969e45d9b6f0903bd2acaf783ba436"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"crossbeam-queue", "crossbeam-queue",
@ -4344,6 +4377,7 @@ dependencies = [
"polars-arrow", "polars-arrow",
"polars-compute", "polars-compute",
"polars-core", "polars-core",
"polars-expr",
"polars-io", "polars-io",
"polars-ops", "polars-ops",
"polars-plan", "polars-plan",
@ -4357,13 +4391,14 @@ dependencies = [
[[package]] [[package]]
name = "polars-plan" name = "polars-plan"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb8e2302e20c44defd5be8cad9c96e75face63c3a5f609aced8c4ec3b3ac97d" checksum = "8daa3541ae7e9af311a4389bc2b21f83349c34c723cc67fa524cdefdaa172d90"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bytemuck", "bytemuck",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
"either",
"hashbrown 0.14.5", "hashbrown 0.14.5",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
@ -4380,15 +4415,15 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"smartstring", "smartstring",
"strum_macros 0.25.3", "strum_macros",
"version_check", "version_check",
] ]
[[package]] [[package]]
name = "polars-row" name = "polars-row"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a515bdc68c2ae3702e3de70d89601f3b71ca8137e282a226dddb53ee4bacfa2e" checksum = "deb285f2f3a65b00dd06bef16bb9f712dbb5478f941dab5cf74f9f016d382e40"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"polars-arrow", "polars-arrow",
@ -4398,11 +4433,12 @@ dependencies = [
[[package]] [[package]]
name = "polars-sql" name = "polars-sql"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4bb7cc1c04c3023d1953b2f1dec50515e8fd8169a5a2bf4967b3b082232db7" checksum = "a724f699d194cb02c25124d3832f7d4d77f387f1a89ee42f6b9e88ec561d4ad9"
dependencies = [ dependencies = [
"hex", "hex",
"once_cell",
"polars-arrow", "polars-arrow",
"polars-core", "polars-core",
"polars-error", "polars-error",
@ -4416,11 +4452,12 @@ dependencies = [
[[package]] [[package]]
name = "polars-time" name = "polars-time"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc18e3ad92eec55db89d88f16c22d436559ba7030cf76f86f6ed7a754b673f1" checksum = "87ebec238d8b6200d9f0c3ce411c8441e950bd5a7df7806b8172d06c1d5a4b97"
dependencies = [ dependencies = [
"atoi", "atoi",
"bytemuck",
"chrono", "chrono",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
"now", "now",
@ -4437,9 +4474,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-utils" name = "polars-utils"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c760b6c698cfe2fbbbd93d6cfb408db14ececfe1d92445dae2229ce1b5b21ae8" checksum = "34e1a907c63abf71e5f21467e2e4ff748896c28196746f631c6c25512ec6102c"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bytemuck", "bytemuck",
@ -4884,7 +4921,7 @@ dependencies = [
"serde_json", "serde_json",
"strip-ansi-escapes", "strip-ansi-escapes",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"thiserror", "thiserror",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
@ -5612,9 +5649,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlparser" name = "sqlparser"
version = "0.45.0" version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7bbffee862a796d67959a89859d6b1046bb5016d63e23835ad0da182777bbe0" checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25"
dependencies = [ dependencies = [
"log", "log",
] ]
@ -5728,20 +5765,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [ dependencies = [
"strum_macros 0.26.2", "strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.60",
] ]
[[package]] [[package]]
@ -6407,93 +6431,77 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "uu_cp" name = "uu_cp"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56" checksum = "c31fc5c95f7668999e129464a29e9080f69ba01ccf7a0ae43ff2cfdb15baa340"
dependencies = [ dependencies = [
"clap", "clap",
"filetime", "filetime",
"indicatif", "indicatif",
"libc", "libc",
"quick-error 2.0.1", "quick-error 2.0.1",
"uucore 0.0.26", "uucore",
"walkdir", "walkdir",
"xattr", "xattr",
] ]
[[package]] [[package]]
name = "uu_mkdir" name = "uu_mkdir"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae" checksum = "496d95e0e3121e4d424ba62019eb84a6f1102213ca8ca16c0a2f8c652c7236c3"
dependencies = [ dependencies = [
"clap", "clap",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_mktemp" name = "uu_mktemp"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57" checksum = "a28a0d9744bdc28ceaf13f70b959bacded91aedfd008402d72fa1e3224158653"
dependencies = [ dependencies = [
"clap", "clap",
"rand", "rand",
"tempfile", "tempfile",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_mv" name = "uu_mv"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022" checksum = "53680908b01c5ac3cc0ee8a376de3e51a36dde2c5a5227a115a3d0977cc4539b"
dependencies = [ dependencies = [
"clap", "clap",
"fs_extra", "fs_extra",
"indicatif", "indicatif",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_uname" name = "uu_uname"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5" checksum = "a7f4125fb4f286313bca8f222abaefe39db54d65179ea788c91ebd3162345f4e"
dependencies = [ dependencies = [
"clap", "clap",
"platform-info", "platform-info",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_whoami" name = "uu_whoami"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4" checksum = "7f7b313901a15cfde2d88f434fcd077903d690f73cc36d1cec20f47906960aec"
dependencies = [ dependencies = [
"clap", "clap",
"libc", "libc",
"uucore 0.0.26", "uucore",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "uucore"
version = "0.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4"
dependencies = [
"clap",
"glob",
"libc",
"nix",
"once_cell",
"os_display",
"uucore_procs",
"wild",
]
[[package]] [[package]]
name = "uucore" name = "uucore"
version = "0.0.26" version = "0.0.26"
@ -6535,9 +6543,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.8.0" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",

View File

@ -11,7 +11,7 @@ license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.77.2" rust-version = "1.77.2"
version = "0.94.3" version = "0.95.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -39,6 +39,7 @@ members = [
"crates/nu-lsp", "crates/nu-lsp",
"crates/nu-pretty-hex", "crates/nu-pretty-hex",
"crates/nu-protocol", "crates/nu-protocol",
"crates/nu-derive-value",
"crates/nu-plugin", "crates/nu-plugin",
"crates/nu-plugin-core", "crates/nu-plugin-core",
"crates/nu-plugin-engine", "crates/nu-plugin-engine",
@ -74,6 +75,7 @@ chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4.34" } chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3" chrono-humanize = "0.2.3"
chrono-tz = "0.8" chrono-tz = "0.8"
convert_case = "0.6"
crossbeam-channel = "0.5.8" crossbeam-channel = "0.5.8"
crossterm = "0.27" crossterm = "0.27"
csv = "1.3" csv = "1.3"
@ -93,7 +95,7 @@ heck = "0.5.0"
human-date-parser = "0.1.1" human-date-parser = "0.1.1"
indexmap = "2.2" indexmap = "2.2"
indicatif = "0.17" indicatif = "0.17"
interprocess = "2.1.0" interprocess = "2.2.0"
is_executable = "1.0" is_executable = "1.0"
itertools = "0.12" itertools = "0.12"
libc = "0.2" libc = "0.2"
@ -118,16 +120,19 @@ num-traits = "0.2"
omnipath = "0.1" omnipath = "0.1"
once_cell = "1.18" once_cell = "1.18"
open = "5.1" open = "5.1"
os_pipe = { version = "1.1", features = ["io_safety"] } os_pipe = { version = "1.2", features = ["io_safety"] }
pathdiff = "0.2" pathdiff = "0.2"
percent-encoding = "2" percent-encoding = "2"
pretty_assertions = "1.4" pretty_assertions = "1.4"
print-positions = "0.6" print-positions = "0.6"
proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = "1.0"
procfs = "0.16.0" procfs = "0.16.0"
pwd = "1.3" pwd = "1.3"
quick-xml = "0.31.0" quick-xml = "0.31.0"
quickcheck = "1.0" quickcheck = "1.0"
quickcheck_macros = "1.0" quickcheck_macros = "1.0"
quote = "1.0"
rand = "0.8" rand = "0.8"
ratatui = "0.26" ratatui = "0.26"
rayon = "1.10" rayon = "1.10"
@ -147,6 +152,7 @@ serde_urlencoded = "0.7.1"
serde_yaml = "0.9" serde_yaml = "0.9"
sha2 = "0.10" sha2 = "0.10"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
syn = "2.0"
sysinfo = "0.30" sysinfo = "0.30"
tabled = { version = "0.14.0", default-features = false } tabled = { version = "0.14.0", default-features = false }
tempfile = "3.10" tempfile = "3.10"
@ -159,14 +165,14 @@ unicode-segmentation = "1.11"
unicode-width = "0.1" unicode-width = "0.1"
ureq = { version = "2.9", default-features = false } ureq = { version = "2.9", default-features = false }
url = "2.2" url = "2.2"
uu_cp = "0.0.25" uu_cp = "0.0.26"
uu_mkdir = "0.0.25" uu_mkdir = "0.0.26"
uu_mktemp = "0.0.25" uu_mktemp = "0.0.26"
uu_mv = "0.0.25" uu_mv = "0.0.26"
uu_whoami = "0.0.25" uu_whoami = "0.0.26"
uu_uname = "0.0.25" uu_uname = "0.0.26"
uucore = "0.0.25" uucore = "0.0.26"
uuid = "1.8.0" uuid = "1.9.1"
v_htmlescape = "0.15.0" v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
which = "6.0.0" which = "6.0.0"
@ -174,27 +180,28 @@ windows = "0.54"
winreg = "0.52" winreg = "0.52"
[dependencies] [dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.94.3" } nu-cli = { path = "./crates/nu-cli", version = "0.95.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.3" } nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.3" } nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.3", optional = true } nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.3" } nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" }
nu-command = { path = "./crates/nu-command", version = "0.94.3" } nu-command = { path = "./crates/nu-command", version = "0.95.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.94.3" } nu-engine = { path = "./crates/nu-engine", version = "0.95.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.94.3" } nu-explore = { path = "./crates/nu-explore", version = "0.95.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.3" } nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.94.3" } nu-parser = { path = "./crates/nu-parser", version = "0.95.1" }
nu-path = { path = "./crates/nu-path", version = "0.94.3" } nu-path = { path = "./crates/nu-path", version = "0.95.1" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.3" } nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.3" } nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
nu-std = { path = "./crates/nu-std", version = "0.94.3" } nu-std = { path = "./crates/nu-std", version = "0.95.1" }
nu-system = { path = "./crates/nu-system", version = "0.94.3" } nu-system = { path = "./crates/nu-system", version = "0.95.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.94.3" } nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true } crossterm = { workspace = true }
ctrlc = { workspace = true } ctrlc = { workspace = true }
dirs-next = { workspace = true }
log = { workspace = true } log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.42", default-features = false, optional = true } mimalloc = { version = "0.1.42", default-features = false, optional = true }
@ -218,9 +225,9 @@ nix = { workspace = true, default-features = false, features = [
] } ] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.3" } nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.3" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.3" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" }
assert_cmd = "2.0" assert_cmd = "2.0"
dirs-next = { workspace = true } dirs-next = { workspace = true }
tango-bench = "0.5" tango-bench = "0.5"
@ -245,7 +252,6 @@ default = ["default-no-clipboard", "system-clipboard"]
# See https://github.com/nushell/nushell/pull/11535 # See https://github.com/nushell/nushell/pull/11535
default-no-clipboard = [ default-no-clipboard = [
"plugin", "plugin",
"which-support",
"trash-support", "trash-support",
"sqlite", "sqlite",
"mimalloc", "mimalloc",
@ -265,7 +271,6 @@ system-clipboard = [
] ]
# Stable (Default) # Stable (Default)
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"] trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
# SQLite commands for nushell # SQLite commands for nushell

View File

@ -52,7 +52,7 @@ To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/ac
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers: Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg?columns=3)](https://repology.org/project/nushell/versions)
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md). For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).
@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell) - [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
- [Dorothy](http://github.com/bevry/dorothy) - [Dorothy](http://github.com/bevry/dorothy)
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell) - [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
- [x-cmd](https://x-cmd.com/mod/nu)
## Contributing ## Contributing

View File

@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
&mut engine, &mut engine,
&mut stack, &mut stack,
PipelineData::empty(), PipelineData::empty(),
None, Default::default(),
false,
) )
.unwrap(); .unwrap();
@ -90,8 +89,7 @@ fn bench_command(
&mut engine, &mut engine,
&mut stack, &mut stack,
PipelineData::empty(), PipelineData::empty(),
None, Default::default(),
false,
) )
.unwrap(), .unwrap(),
); );

View File

@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.94.3" version = "0.95.1"
[lib] [lib]
bench = false bench = false
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-command = { path = "../nu-command", version = "0.94.3" } nu-command = { path = "../nu-command", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.94.3" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
rstest = { workspace = true, default-features = false } rstest = { workspace = true, default-features = false }
tempfile = { workspace = true } tempfile = { workspace = true }
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.94.3" } nu-path = { path = "../nu-path", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.94.3" } nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3", optional = true } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.94.3" } nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-color-config = { path = "../nu-color-config", version = "0.94.3" } nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }

View File

@ -1,7 +1,7 @@
use crate::completions::{matches, CompletionOptions}; use crate::completions::{matches, CompletionOptions};
use nu_ansi_term::Style; use nu_ansi_term::Style;
use nu_engine::env_to_string; use nu_engine::env_to_string;
use nu_path::home_dir; use nu_path::{expand_to_real_path, home_dir};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Span, Span,
@ -185,7 +185,12 @@ pub fn complete_item(
.map(|p| { .map(|p| {
let path = original_cwd.apply(p); let path = original_cwd.apply(p);
let style = ls_colors.as_ref().map(|lsc| { let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(&path, std::fs::symlink_metadata(&path).ok().as_ref()) lsc.style_for_path_with_metadata(
&path,
std::fs::symlink_metadata(expand_to_real_path(&path))
.ok()
.as_ref(),
)
.map(lscolors::Style::to_nu_ansi_term_style) .map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default() .unwrap_or_default()
}); });

View File

@ -344,7 +344,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
name: identity.name().to_owned(), name: identity.name().to_owned(),
filename: identity.filename().to_owned(), filename: identity.filename().to_owned(),
shell: identity.shell().map(|p| p.to_owned()), shell: identity.shell().map(|p| p.to_owned()),
data: PluginRegistryItemData::Valid { commands }, data: PluginRegistryItemData::Valid {
metadata: Default::default(),
commands,
},
}); });
} }

View File

@ -8,15 +8,45 @@ use nu_protocol::{
}; };
use std::sync::Arc; use std::sync::Arc;
#[derive(Default)]
pub struct EvaluateCommandsOpts {
pub table_mode: Option<Value>,
pub error_style: Option<Value>,
pub no_newline: bool,
}
/// Run a command (or commands) given to us by the user /// Run a command (or commands) given to us by the user
pub fn evaluate_commands( pub fn evaluate_commands(
commands: &Spanned<String>, commands: &Spanned<String>,
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
input: PipelineData, input: PipelineData,
table_mode: Option<Value>, opts: EvaluateCommandsOpts,
no_newline: bool,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let EvaluateCommandsOpts {
table_mode,
error_style,
no_newline,
} = opts;
// Handle the configured error style early
if let Some(e_style) = error_style {
match e_style.coerce_str()?.parse() {
Ok(e_style) => {
Arc::make_mut(&mut engine_state.config).error_style = e_style;
}
Err(err) => {
return Err(ShellError::GenericError {
error: "Invalid value for `--error-style`".into(),
msg: err.into(),
span: Some(e_style.span()),
help: None,
inner: vec![],
});
}
}
}
// Translate environment variables from Strings to Values // Translate environment variables from Strings to Values
convert_env_values(engine_state, stack)?; convert_env_values(engine_state, stack)?;

View File

@ -17,7 +17,7 @@ mod validation;
pub use commands::add_cli_context; pub use commands::add_cli_context;
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind}; pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
pub use config_files::eval_config_contents; pub use config_files::eval_config_contents;
pub use eval_cmds::evaluate_commands; pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
pub use eval_file::evaluate_file; pub use eval_file::evaluate_file;
pub use menus::NuHelpCompleter; pub use menus::NuHelpCompleter;
pub use nu_cmd_base::util::get_init_cwd; pub use nu_cmd_base::util::get_init_cwd;

View File

@ -1,6 +1,5 @@
use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crate::{menus::NuMenuCompleter, NuHelpCompleter};
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use log::trace;
use nu_ansi_term::Style; use nu_ansi_term::Style;
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
use nu_engine::eval_block; use nu_engine::eval_block;
@ -76,15 +75,15 @@ const DEFAULT_HELP_MENU: &str = r#"
// Adds all menus to line editor // Adds all menus to line editor
pub(crate) fn add_menus( pub(crate) fn add_menus(
mut line_editor: Reedline, mut line_editor: Reedline,
engine_state: Arc<EngineState>, engine_state_ref: Arc<EngineState>,
stack: &Stack, stack: &Stack,
config: &Config, config: &Config,
) -> Result<Reedline, ShellError> { ) -> Result<Reedline, ShellError> {
trace!("add_menus: config: {:#?}", &config); //log::trace!("add_menus: config: {:#?}", &config);
line_editor = line_editor.clear_menus(); line_editor = line_editor.clear_menus();
for menu in &config.menus { for menu in &config.menus {
line_editor = add_menu(line_editor, menu, engine_state.clone(), stack, config)? line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)?
} }
// Checking if the default menus have been added from the config file // Checking if the default menus have been added from the config file
@ -94,13 +93,16 @@ pub(crate) fn add_menus(
("help_menu", DEFAULT_HELP_MENU), ("help_menu", DEFAULT_HELP_MENU),
]; ];
let mut engine_state = (*engine_state_ref).clone();
let mut menu_eval_results = vec![];
for (name, definition) in default_menus { for (name, definition) in default_menus {
if !config if !config
.menus .menus
.iter() .iter()
.any(|menu| menu.name.to_expanded_string("", config) == name) .any(|menu| menu.name.to_expanded_string("", config) == name)
{ {
let (block, _) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state); let mut working_set = StateWorkingSet::new(&engine_state);
let output = parse( let output = parse(
&mut working_set, &mut working_set,
@ -112,15 +114,31 @@ pub(crate) fn add_menus(
(output, working_set.render()) (output, working_set.render())
}; };
engine_state.merge_delta(delta)?;
let mut temp_stack = Stack::new().capture(); let mut temp_stack = Stack::new().capture();
let input = PipelineData::Empty; let input = PipelineData::Empty;
let res = eval_block::<WithoutDebug>(&engine_state, &mut temp_stack, &block, input)?; menu_eval_results.push(eval_block::<WithoutDebug>(
&engine_state,
&mut temp_stack,
&block,
input,
)?);
}
}
let new_engine_state_ref = Arc::new(engine_state);
for res in menu_eval_results.into_iter() {
if let PipelineData::Value(value, None) = res { if let PipelineData::Value(value, None) = res {
for menu in create_menus(&value)? { for menu in create_menus(&value)? {
line_editor = line_editor = add_menu(
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?; line_editor,
} &menu,
new_engine_state_ref.clone(),
stack,
config,
)?;
} }
} }
} }

View File

@ -138,6 +138,7 @@ impl Highlighter for NuHighlighter {
FlatShape::Filepath => add_colored_token(&shape.1, next_token), FlatShape::Filepath => add_colored_token(&shape.1, next_token),
FlatShape::Directory => add_colored_token(&shape.1, next_token), FlatShape::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::GlobPattern => add_colored_token(&shape.1, next_token),
FlatShape::Variable(_) | FlatShape::VarDecl(_) => { FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
add_colored_token(&shape.1, next_token) add_colored_token(&shape.1, next_token)
@ -452,7 +453,8 @@ fn find_matching_block_end_in_expr(
} }
} }
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| { Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
exprs.iter().find_map(|expr| {
find_matching_block_end_in_expr( find_matching_block_end_in_expr(
line, line,
working_set, working_set,
@ -460,7 +462,8 @@ fn find_matching_block_end_in_expr(
global_span_offset, global_span_offset,
global_cursor_offset, global_cursor_offset,
) )
}), })
}
Expr::List(list) => { Expr::List(list) => {
if expr_last == global_cursor_offset { if expr_last == global_cursor_offset {

View File

@ -763,11 +763,13 @@ fn variables_completions() {
// Test completions for $nu // Test completions for $nu
let suggestions = completer.complete("$nu.", 4); let suggestions = completer.complete("$nu.", 4);
assert_eq!(15, suggestions.len()); assert_eq!(18, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"cache-dir".into(),
"config-path".into(), "config-path".into(),
"current-exe".into(), "current-exe".into(),
"data-dir".into(),
"default-config-dir".into(), "default-config-dir".into(),
"env-path".into(), "env-path".into(),
"history-enabled".into(), "history-enabled".into(),
@ -781,6 +783,7 @@ fn variables_completions() {
"plugin-path".into(), "plugin-path".into(),
"startup-time".into(), "startup-time".into(),
"temp-path".into(), "temp-path".into(),
"vendor-autoload-dir".into(),
]; ];
// Match results // Match results

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-base" name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.94.3" version = "0.95.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.94.3" } nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.94.3" } nu-path = { path = "../nu-path", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
indexmap = { workspace = true } indexmap = { workspace = true }
miette = { workspace = true } miette = { workspace = true }

View File

@ -194,7 +194,7 @@ pub fn eval_hook(
let Some(follow) = val.get("code") else { let Some(follow) = val.get("code") else {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: "code".into(), col_name: "code".into(),
span, span: Some(span),
src_span: span, src_span: span,
}); });
}; };

View File

@ -20,13 +20,14 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
type MakeRangeError = fn(&str, Span) -> ShellError; type MakeRangeError = fn(&str, Span) -> ShellError;
/// Returns a inclusive pair of boundary in given `range`.
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> { pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
match range { match range {
Range::IntRange(range) => { Range::IntRange(range) => {
let start = range.start().try_into().unwrap_or(0); let start = range.start().try_into().unwrap_or(0);
let end = match range.end() { let end = match range.end() {
Bound::Included(v) => (v + 1) as isize, Bound::Included(v) => v as isize,
Bound::Excluded(v) => v as isize, Bound::Excluded(v) => (v - 1) as isize,
Bound::Unbounded => isize::MAX, Bound::Unbounded => isize::MAX,
}; };
Ok((start, end)) Ok((start, end))

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-extra" name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.94.3" version = "0.95.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,13 +13,13 @@ version = "0.94.3"
bench = false bench = false
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-json = { version = "0.94.3", path = "../nu-json" } nu-json = { version = "0.95.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.94.3" } nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-pretty-hex = { version = "0.94.3", path = "../nu-pretty-hex" } nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.94.3" } nu-utils = { path = "../nu-utils", version = "0.95.1" }
# Potential dependencies for extras # Potential dependencies for extras
heck = { workspace = true } heck = { workspace = true }
@ -32,11 +32,7 @@ serde_urlencoded = { workspace = true }
v_htmlescape = { workspace = true } v_htmlescape = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
[features]
extra = ["default"]
default = []
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-command = { path = "../nu-command", version = "0.94.3" } nu-command = { path = "../nu-command", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.94.3" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" }

View File

@ -1,5 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_expression}; use nu_engine::command_prelude::*;
use nu_parser::parse_expression;
use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream}; use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream};
#[derive(Clone)] #[derive(Clone)]
@ -57,14 +56,7 @@ impl Command for FormatPattern {
string_span.start + 1, string_span.start + 1,
)?; )?;
format( format(input_val, &ops, engine_state, call.head)
input_val,
&ops,
engine_state,
&mut working_set,
stack,
call.head,
)
} }
} }
} }
@ -100,8 +92,6 @@ enum FormatOperation {
FixedText(String), FixedText(String),
// raw input is something like {column1.column2} // raw input is something like {column1.column2}
ValueFromColumn(String, Span), ValueFromColumn(String, Span),
// raw input is something like {$it.column1.column2} or {$var}.
ValueNeedEval(String, Span),
} }
/// Given a pattern that is fed into the Format command, we can process it and subdivide it /// Given a pattern that is fed into the Format command, we can process it and subdivide it
@ -110,7 +100,6 @@ enum FormatOperation {
/// there without any further processing. /// there without any further processing.
/// FormatOperation::ValueFromColumn contains the name of a column whose values will be /// FormatOperation::ValueFromColumn contains the name of a column whose values will be
/// formatted according to the input pattern. /// formatted according to the input pattern.
/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form:
/// "$it.column1.column2" or "$variable" /// "$it.column1.column2" or "$variable"
fn extract_formatting_operations( fn extract_formatting_operations(
input: String, input: String,
@ -161,10 +150,17 @@ fn extract_formatting_operations(
if !column_name.is_empty() { if !column_name.is_empty() {
if column_need_eval { if column_need_eval {
output.push(FormatOperation::ValueNeedEval( return Err(ShellError::GenericError {
column_name.clone(), error: "Removed functionality".into(),
Span::new(span_start + column_span_start, span_start + column_span_end), msg: "The ability to use variables ($it) in `format pattern` has been removed."
)); .into(),
span: Some(error_span),
help: Some(
"You can use other formatting options, such as string interpolation."
.into(),
),
inner: vec![],
});
} else { } else {
output.push(FormatOperation::ValueFromColumn( output.push(FormatOperation::ValueFromColumn(
column_name.clone(), column_name.clone(),
@ -185,8 +181,6 @@ fn format(
input_data: Value, input_data: Value,
format_operations: &[FormatOperation], format_operations: &[FormatOperation],
engine_state: &EngineState, engine_state: &EngineState,
working_set: &mut StateWorkingSet,
stack: &mut Stack,
head_span: Span, head_span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let data_as_value = input_data; let data_as_value = input_data;
@ -194,13 +188,7 @@ fn format(
// We can only handle a Record or a List of Records // We can only handle a Record or a List of Records
match data_as_value { match data_as_value {
Value::Record { .. } => { Value::Record { .. } => {
match format_record( match format_record(format_operations, &data_as_value, engine_state) {
format_operations,
&data_as_value,
engine_state,
working_set,
stack,
) {
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
Err(value) => Err(value), Err(value) => Err(value),
} }
@ -211,13 +199,7 @@ fn format(
for val in vals.iter() { for val in vals.iter() {
match val { match val {
Value::Record { .. } => { Value::Record { .. } => {
match format_record( match format_record(format_operations, val, engine_state) {
format_operations,
val,
engine_state,
working_set,
stack,
) {
Ok(value) => { Ok(value) => {
list.push(Value::string(value, head_span)); list.push(Value::string(value, head_span));
} }
@ -256,12 +238,9 @@ fn format_record(
format_operations: &[FormatOperation], format_operations: &[FormatOperation],
data_as_value: &Value, data_as_value: &Value,
engine_state: &EngineState, engine_state: &EngineState,
working_set: &mut StateWorkingSet,
stack: &mut Stack,
) -> Result<String, ShellError> { ) -> Result<String, ShellError> {
let config = engine_state.get_config(); let config = engine_state.get_config();
let mut output = String::new(); let mut output = String::new();
let eval_expression = get_eval_expression(engine_state);
for op in format_operations { for op in format_operations {
match op { match op {
@ -283,23 +262,6 @@ fn format_record(
Err(se) => return Err(se), Err(se) => return Err(se),
} }
} }
FormatOperation::ValueNeedEval(_col_name, span) => {
let exp = parse_expression(working_set, &[*span]);
match working_set.parse_errors.first() {
None => {
let parsed_result = eval_expression(engine_state, stack, &exp);
if let Ok(val) = parsed_result {
output.push_str(&val.to_abbreviated_string(config))
}
}
Some(err) => {
return Err(ShellError::TypeMismatch {
err_message: format!("expression is invalid, detail message: {err:?}"),
span: *span,
})
}
}
}
} }
} }
Ok(output) Ok(output)

View File

@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.94.3" version = "0.95.1"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.94.3" } nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.94.3" } nu-utils = { path = "../nu-utils", version = "0.95.1" }
itertools = { workspace = true } itertools = { workspace = true }
shadow-rs = { version = "0.28", default-features = false } shadow-rs = { version = "0.28", default-features = false }
@ -25,7 +25,6 @@ shadow-rs = { version = "0.28", default-features = false }
[features] [features]
mimalloc = [] mimalloc = []
which-support = []
trash-support = [] trash-support = []
sqlite = [] sqlite = []
static-link-openssl = [] static-link-openssl = []

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct Break; pub struct Break;
@ -18,6 +19,15 @@ impl Command for Break {
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct Continue; pub struct Continue;
@ -18,6 +19,14 @@ impl Command for Continue {
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,

View File

@ -60,10 +60,15 @@ impl Command for Def {
example: r#"def --env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#, example: r#"def --env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#,
result: Some(Value::test_string("BAZ")), result: Some(Value::test_string("BAZ")),
}, },
Example {
description: "cd affects the environment, so '--env' is required to change directory from within a command",
example: r#"def --env gohome [] { cd ~ }; gohome; $env.PWD == ('~' | path expand)"#,
result: Some(Value::test_string("true")),
},
Example { Example {
description: "Define a custom wrapper for an external command", description: "Define a custom wrapper for an external command",
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#, example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
result: Some(Value::test_list(vec![Value::test_string("spam")])), result: Some(Value::test_string("spam\tspam")),
}, },
] ]
} }

View File

@ -229,14 +229,24 @@ impl Command for Do {
result: None, result: None,
}, },
Example { Example {
description: "Run the closure, with a positional parameter", description: "Run the closure with a positional, type-checked parameter",
example: r#"do {|x| 100 + $x } 77"#, example: r#"do {|x:int| 100 + $x } 77"#,
result: Some(Value::test_int(177)), result: Some(Value::test_int(177)),
}, },
Example { Example {
description: "Run the closure, with input", description: "Run the closure with pipeline input",
example: r#"77 | do {|x| 100 + $in }"#, example: r#"77 | do { 100 + $in }"#,
result: None, // TODO: returns 177 result: Some(Value::test_int(177)),
},
Example {
description: "Run the closure with a default parameter value",
example: r#"77 | do {|x=100| $x + $in }"#,
result: Some(Value::test_int(177)),
},
Example {
description: "Run the closure with two positional parameters",
example: r#"do {|x,y| $x + $y } 77 100"#,
result: Some(Value::test_int(177)),
}, },
Example { Example {
description: "Run the closure and keep changes to the environment", description: "Run the closure and keep changes to the environment",

View File

@ -1,5 +1,6 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
use nu_protocol::engine::CommandType; use nu_protocol::engine::CommandType;
use nu_protocol::ParseWarning;
#[derive(Clone)] #[derive(Clone)]
pub struct For; pub struct For;
@ -30,7 +31,7 @@ impl Command for For {
.required("block", SyntaxShape::Block, "The block to run.") .required("block", SyntaxShape::Block, "The block to run.")
.switch( .switch(
"numbered", "numbered",
"return a numbered item ($it.index and $it.item)", "DEPRECATED: return a numbered item ($it.index and $it.item)",
Some('n'), Some('n'),
) )
.creates_scope() .creates_scope()
@ -78,6 +79,20 @@ impl Command for For {
let value = eval_expression(engine_state, stack, keyword_expr)?; let value = eval_expression(engine_state, stack, keyword_expr)?;
let numbered = call.has_flag(engine_state, stack, "numbered")?; let numbered = call.has_flag(engine_state, stack, "numbered")?;
if numbered {
nu_protocol::report_error_new(
engine_state,
&ParseWarning::DeprecatedWarning {
old_command: "--numbered/-n".into(),
new_suggestion: "use `enumerate`".into(),
span: call
.get_named_arg("numbered")
.expect("`get_named_arg` found `--numbered` but still failed")
.span,
url: "See `help for` examples".into(),
},
);
}
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
@ -198,8 +213,7 @@ impl Command for For {
}, },
Example { Example {
description: "Number each item and print a message", description: "Number each item and print a message",
example: example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
result: None, result: None,
}, },
] ]

View File

@ -2,7 +2,7 @@ use nu_engine::{
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input, command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
}; };
use nu_protocol::{ use nu_protocol::{
engine::StateWorkingSet, engine::{CommandType, StateWorkingSet},
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input}, eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
}; };
@ -41,6 +41,15 @@ impl Command for If {
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn is_const(&self) -> bool { fn is_const(&self) -> bool {
true true
} }
@ -122,6 +131,10 @@ impl Command for If {
} }
} }
fn search_terms(&self) -> Vec<&str> {
vec!["else", "conditional"]
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block}; use nu_engine::{command_prelude::*, get_eval_block};
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct Loop; pub struct Loop;
@ -20,6 +21,15 @@ impl Command for Loop {
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -1,7 +1,7 @@
use nu_engine::{ use nu_engine::{
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input, command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
}; };
use nu_protocol::engine::Matcher; use nu_protocol::engine::{CommandType, Matcher};
#[derive(Clone)] #[derive(Clone)]
pub struct Match; pub struct Match;
@ -27,6 +27,15 @@ impl Command for Match {
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -1,5 +1,4 @@
use nu_engine::{command_prelude::*, get_full_help}; use nu_engine::{command_prelude::*, get_full_help};
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct Scope; pub struct Scope;
@ -20,10 +19,6 @@ impl Command for Scope {
"Commands for getting info about what is in scope." "Commands for getting info about what is in scope."
} }
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -1,5 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn}; use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
use nu_protocol::engine::Closure; use nu_protocol::engine::{Closure, CommandType};
#[derive(Clone)] #[derive(Clone)]
pub struct Try; pub struct Try;
@ -10,7 +10,7 @@ impl Command for Try {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Try to run a block, if it fails optionally run a catch block." "Try to run a block, if it fails optionally run a catch closure."
} }
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
@ -18,7 +18,7 @@ impl Command for Try {
.input_output_types(vec![(Type::Any, Type::Any)]) .input_output_types(vec![(Type::Any, Type::Any)])
.required("try_block", SyntaxShape::Block, "Block to run.") .required("try_block", SyntaxShape::Block, "Block to run.")
.optional( .optional(
"catch_block", "catch_closure",
SyntaxShape::Keyword( SyntaxShape::Keyword(
b"catch".to_vec(), b"catch".to_vec(),
Box::new(SyntaxShape::OneOf(vec![ Box::new(SyntaxShape::OneOf(vec![
@ -26,11 +26,20 @@ impl Command for Try {
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
])), ])),
), ),
"Block to run if try block fails.", "Closure to run if try block fails.",
) )
.category(Category::Core) .category(Category::Core)
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -88,6 +97,11 @@ impl Command for Try {
example: "try { asdfasdf } catch { 'missing' }", example: "try { asdfasdf } catch { 'missing' }",
result: Some(Value::test_string("missing")), result: Some(Value::test_string("missing")),
}, },
Example {
description: "Try to run a missing command and report the message",
example: "try { asdfasdf } catch { |err| $err.msg }",
result: None,
},
] ]
} }
} }

View File

@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
Value::string(features_enabled().join(", "), span), Value::string(features_enabled().join(", "), span),
); );
// Get a list of plugin names // Get a list of plugin names and versions if present
let installed_plugins = engine_state let installed_plugins = engine_state
.plugins() .plugins()
.iter() .iter()
.map(|x| x.identity().name()) .map(|x| {
let name = x.identity().name();
if let Some(version) = x.metadata().and_then(|m| m.version) {
format!("{name} {version}")
} else {
name.into()
}
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
record.push( record.push(
@ -160,11 +167,6 @@ fn features_enabled() -> Vec<String> {
// NOTE: There should be another way to know features on. // NOTE: There should be another way to know features on.
#[cfg(feature = "which-support")]
{
names.push("which".to_string());
}
#[cfg(feature = "trash-support")] #[cfg(feature = "trash-support")]
{ {
names.push("trash".to_string()); names.push("trash".to_string());

View File

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct While; pub struct While;
@ -29,6 +30,15 @@ impl Command for While {
vec!["loop"] vec!["loop"]
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -5,15 +5,15 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-plugin" name = "nu-cmd-plugin"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
version = "0.94.3" version = "0.95.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.94.3" } nu-path = { path = "../nu-path", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] } nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
itertools = { workspace = true } itertools = { workspace = true }

View File

@ -118,11 +118,12 @@ apparent the next time `nu` is next launched with that plugin registry file.
}, },
)); ));
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?; let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
let metadata = interface.get_metadata()?;
let commands = interface.get_signature()?; let commands = interface.get_signature()?;
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| { modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
// Update the file with the received signatures // Update the file with the received metadata and signatures
let item = PluginRegistryItem::new(plugin.identity(), commands); let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
contents.upsert_plugin(item); contents.upsert_plugin(item);
Ok(()) Ok(())
})?; })?;

View File

@ -16,6 +16,7 @@ impl Command for PluginList {
Type::Table( Type::Table(
[ [
("name".into(), Type::String), ("name".into(), Type::String),
("version".into(), Type::String),
("is_running".into(), Type::Bool), ("is_running".into(), Type::Bool),
("pid".into(), Type::Int), ("pid".into(), Type::Int),
("filename".into(), Type::String), ("filename".into(), Type::String),
@ -43,6 +44,7 @@ impl Command for PluginList {
description: "List installed plugins.", description: "List installed plugins.",
result: Some(Value::test_list(vec![Value::test_record(record! { result: Some(Value::test_list(vec![Value::test_record(record! {
"name" => Value::test_string("inc"), "name" => Value::test_string("inc"),
"version" => Value::test_string(env!("CARGO_PKG_VERSION")),
"is_running" => Value::test_bool(true), "is_running" => Value::test_bool(true),
"pid" => Value::test_int(106480), "pid" => Value::test_int(106480),
"filename" => if cfg!(windows) { "filename" => if cfg!(windows) {
@ -98,8 +100,15 @@ impl Command for PluginList {
.map(|s| Value::string(s.to_string_lossy(), head)) .map(|s| Value::string(s.to_string_lossy(), head))
.unwrap_or(Value::nothing(head)); .unwrap_or(Value::nothing(head));
let metadata = plugin.metadata();
let version = metadata
.and_then(|m| m.version)
.map(|s| Value::string(s, head))
.unwrap_or(Value::nothing(head));
let record = record! { let record = record! {
"name" => Value::string(plugin.identity().name(), head), "name" => Value::string(plugin.identity().name(), head),
"version" => version,
"is_running" => Value::bool(plugin.is_running(), head), "is_running" => Value::bool(plugin.is_running(), head),
"pid" => pid, "pid" => pid,
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head), "filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),

View File

@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-color-config" name = "nu-color-config"
version = "0.94.3" version = "0.95.1"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-json = { path = "../nu-json", version = "0.94.3" } nu-json = { path = "../nu-json", version = "0.95.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.94.3" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" }

View File

@ -20,6 +20,7 @@ pub fn default_shape_color(shape: &str) -> Style {
"shape_flag" => Style::new().fg(Color::Blue).bold(), "shape_flag" => Style::new().fg(Color::Blue).bold(),
"shape_float" => Style::new().fg(Color::Purple).bold(), "shape_float" => Style::new().fg(Color::Purple).bold(),
"shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(), "shape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(),
"shape_glob_interpolation" => Style::new().fg(Color::Cyan).bold(),
"shape_globpattern" => Style::new().fg(Color::Cyan).bold(), "shape_globpattern" => Style::new().fg(Color::Cyan).bold(),
"shape_int" => Style::new().fg(Color::Purple).bold(), "shape_int" => Style::new().fg(Color::Purple).bold(),
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(), "shape_internalcall" => Style::new().fg(Color::Cyan).bold(),

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.94.3" version = "0.95.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,21 +13,21 @@ version = "0.94.3"
bench = false bench = false
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
nu-color-config = { path = "../nu-color-config", version = "0.94.3" } nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
nu-engine = { path = "../nu-engine", version = "0.94.3" } nu-engine = { path = "../nu-engine", version = "0.95.1" }
nu-glob = { path = "../nu-glob", version = "0.94.3" } nu-glob = { path = "../nu-glob", version = "0.95.1" }
nu-json = { path = "../nu-json", version = "0.94.3" } nu-json = { path = "../nu-json", version = "0.95.1" }
nu-parser = { path = "../nu-parser", version = "0.94.3" } nu-parser = { path = "../nu-parser", version = "0.95.1" }
nu-path = { path = "../nu-path", version = "0.94.3" } nu-path = { path = "../nu-path", version = "0.95.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
nu-protocol = { path = "../nu-protocol", version = "0.94.3" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
nu-system = { path = "../nu-system", version = "0.94.3" } nu-system = { path = "../nu-system", version = "0.95.1" }
nu-table = { path = "../nu-table", version = "0.94.3" } nu-table = { path = "../nu-table", version = "0.95.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.94.3" } nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" }
nu-utils = { path = "../nu-utils", version = "0.94.3" } nu-utils = { path = "../nu-utils", version = "0.95.1" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.94.3" } nuon = { path = "../nuon", version = "0.95.1" }
alphanumeric-sort = { workspace = true } alphanumeric-sort = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
@ -134,11 +134,10 @@ workspace = true
plugin = ["nu-parser/plugin"] plugin = ["nu-parser/plugin"]
sqlite = ["rusqlite"] sqlite = ["rusqlite"]
trash-support = ["trash"] trash-support = ["trash"]
which-support = []
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
nu-test-support = { path = "../nu-test-support", version = "0.94.3" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
dirs-next = { workspace = true } dirs-next = { workspace = true }
mockito = { workspace = true, default-features = false } mockito = { workspace = true, default-features = false }

View File

@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let range = &args.indexes; let range = &args.indexes;
match input { match input {
Value::Binary { val, .. } => { Value::Binary { val, .. } => {
use std::cmp::{self, Ordering};
let len = val.len() as isize; let len = val.len() as isize;
let start = if range.0 < 0 { range.0 + len } else { range.0 }; let start = if range.0 < 0 { range.0 + len } else { range.0 };
let end = if range.1 < 0 { range.1 + len } else { range.1 };
let end = if range.1 < 0 { if start > end {
cmp::max(range.1 + len, 0)
} else {
range.1
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::binary(vec![], head),
Ordering::Greater => Value::error(
ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: head,
},
head,
),
Ordering::Less => Value::binary(
if end == isize::MAX {
val.iter()
.skip(start as usize)
.copied()
.collect::<Vec<u8>>()
} else {
val.iter()
.skip(start as usize)
.take((end - start) as usize)
.copied()
.collect()
},
head,
),
}
} else {
Value::binary(vec![], head) Value::binary(vec![], head)
} else {
let val_iter = val.iter().skip(start as usize);
Value::binary(
if end == isize::MAX {
val_iter.copied().collect::<Vec<u8>>()
} else {
val_iter.take((end - start + 1) as usize).copied().collect()
},
head,
)
} }
} }
Value::Error { .. } => input.clone(), Value::Error { .. } => input.clone(),
other => Value::error( other => Value::error(

View File

@ -194,7 +194,7 @@ fn run_histogram(
if inputs.is_empty() { if inputs.is_empty() {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.clone(), col_name: col_name.clone(),
span: head_span, span: Some(head_span),
src_span: list_span, src_span: list_span,
}); });
} }

View File

@ -154,7 +154,7 @@ fn record_to_path_member(
let Some(value) = record.get("value") else { let Some(value) = record.get("value") else {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: "value".into(), col_name: "value".into(),
span: val_span, span: Some(val_span),
src_span: span, src_span: span,
}); });
}; };

View File

@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Value::Filesize { .. } => input.clone(), Value::Filesize { .. } => input.clone(),
Value::Int { val, .. } => Value::filesize(*val, value_span), Value::Int { val, .. } => Value::filesize(*val, value_span),
Value::Float { val, .. } => Value::filesize(*val as i64, value_span), Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
Value::String { val, .. } => match int_from_string(val, value_span) { Value::String { val, .. } => match i64_from_string(val, value_span) {
Ok(val) => Value::filesize(val, value_span), Ok(val) => Value::filesize(val, value_span),
Err(error) => Value::error(error, value_span), Err(error) => Value::error(error, value_span),
}, },
@ -139,7 +139,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
} }
} }
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> { fn i64_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
// Get the Locale so we know what the thousands separator is // Get the Locale so we know what the thousands separator is
let locale = get_system_locale(); let locale = get_system_locale();
@ -151,24 +151,35 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
// Hadle negative file size // Hadle negative file size
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') { if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
match stripped_negative_string.parse::<bytesize::ByteSize>() { match stripped_negative_string.parse::<bytesize::ByteSize>() {
Ok(n) => Ok(-(n.as_u64() as i64)), Ok(n) => i64_from_byte_size(n, true, span),
Err(_) => Err(string_convert_error(span)), Err(_) => Err(string_convert_error(span)),
} }
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') { } else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
match stripped_positive_string.parse::<bytesize::ByteSize>() { match stripped_positive_string.parse::<bytesize::ByteSize>() {
Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => { Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => {
Ok(n.as_u64() as i64) i64_from_byte_size(n, false, span)
} }
_ => Err(string_convert_error(span)), _ => Err(string_convert_error(span)),
} }
} else { } else {
match clean_string.parse::<bytesize::ByteSize>() { match clean_string.parse::<bytesize::ByteSize>() {
Ok(n) => Ok(n.as_u64() as i64), Ok(n) => i64_from_byte_size(n, false, span),
Err(_) => Err(string_convert_error(span)), Err(_) => Err(string_convert_error(span)),
} }
} }
} }
fn i64_from_byte_size(
byte_size: bytesize::ByteSize,
is_negative: bool,
span: Span,
) -> Result<i64, ShellError> {
match i64::try_from(byte_size.as_u64()) {
Ok(n) => Ok(if is_negative { -n } else { n }),
Err(_) => Err(string_convert_error(span)),
}
}
fn string_convert_error(span: Span) -> ShellError { fn string_convert_error(span: Span) -> ShellError {
ShellError::CantConvert { ShellError::CantConvert {
to_type: "filesize".into(), to_type: "filesize".into(),

View File

@ -127,7 +127,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
SysTemp, SysTemp,
SysUsers, SysUsers,
UName, UName,
Which,
}; };
// Help // Help
@ -172,9 +172,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
))] ))]
bind_command! { Ps }; bind_command! { Ps };
#[cfg(feature = "which-support")]
bind_command! { Which };
// Strings // Strings
bind_command! { bind_command! {
Char, Char,

View File

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, redirect_env}; use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
use nu_protocol::engine::CommandType;
#[derive(Clone)] #[derive(Clone)]
pub struct ExportEnv; pub struct ExportEnv;
@ -23,6 +24,15 @@ impl Command for ExportEnv {
"Run a block and preserve its environment in a current scope." "Run a block and preserve its environment in a current scope."
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -2,6 +2,7 @@ use nu_engine::{
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return, command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
redirect_env, redirect_env,
}; };
use nu_protocol::engine::CommandType;
use std::path::PathBuf; use std::path::PathBuf;
/// Source a file for environment variables. /// Source a file for environment variables.
@ -28,6 +29,15 @@ impl Command for SourceEnv {
"Source the environment from a source file into the current environment." "Source the environment from a source file into the current environment."
} }
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -135,6 +135,11 @@ impl Command for Cd {
example: r#"cd -"#, example: r#"cd -"#,
result: None, result: None,
}, },
Example {
description: "Changing directory with a custom command requires 'def --env'",
example: r#"def --env gohome [] { cd ~ }"#,
result: None,
},
] ]
} }
} }

View File

@ -35,12 +35,12 @@ impl Command for Touch {
) )
.switch( .switch(
"modified", "modified",
"change the modification time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used", "change the modification time of the file or directory. If no reference file/directory is given, the current time is used",
Some('m'), Some('m'),
) )
.switch( .switch(
"access", "access",
"change the access time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used", "change the access time of the file or directory. If no reference file/directory is given, the current time is used",
Some('a'), Some('a'),
) )
.switch( .switch(
@ -189,11 +189,6 @@ impl Command for Touch {
example: r#"touch -m -r fixture.json d e"#, example: r#"touch -m -r fixture.json d e"#,
result: None, result: None,
}, },
Example {
description: r#"Changes the last accessed time of "fixture.json" to a date"#,
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
result: None,
},
] ]
} }
} }

View File

@ -130,7 +130,7 @@ pub fn split(
Some(group_key) => Ok(group_key.coerce_string()?), Some(group_key) => Ok(group_key.coerce_string()?),
None => Err(ShellError::CantFindColumn { None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(), col_name: column_name.item.to_string(),
span: column_name.span, span: Some(column_name.span),
src_span: row.span(), src_span: row.span(),
}), }),
} }

View File

@ -123,7 +123,7 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) { if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: nonexistent, col_name: nonexistent,
span, span: Some(span),
src_span: val_span, src_span: val_span,
}); });
} }

View File

@ -1,5 +1,5 @@
use nu_engine::{command_prelude::*, ClosureEval}; use nu_engine::{command_prelude::*, ClosureEval};
use nu_protocol::engine::Closure; use nu_protocol::engine::{Closure, CommandType};
#[derive(Clone)] #[derive(Clone)]
pub struct Where; pub struct Where;
@ -19,6 +19,10 @@ tables, known as "row conditions". On the other hand, reading the condition from
not supported."# not supported."#
} }
fn command_type(&self) -> CommandType {
CommandType::Keyword
}
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("where") Signature::build("where")
.input_output_types(vec![ .input_output_types(vec![

View File

@ -1,4 +1,4 @@
use chrono::SecondsFormat; use chrono::{DateTime, Datelike, FixedOffset, Timelike};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ast::PathMember; use nu_protocol::ast::PathMember;
@ -49,9 +49,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
Value::Int { val, .. } => toml::Value::Integer(*val), Value::Int { val, .. } => toml::Value::Integer(*val),
Value::Filesize { val, .. } => toml::Value::Integer(*val), Value::Filesize { val, .. } => toml::Value::Integer(*val),
Value::Duration { val, .. } => toml::Value::String(val.to_string()), Value::Duration { val, .. } => toml::Value::String(val.to_string()),
Value::Date { val, .. } => { Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
toml::Value::String(val.to_rfc3339_opts(SecondsFormat::AutoSi, false))
}
Value::Range { .. } => toml::Value::String("<Range>".to_string()), Value::Range { .. } => toml::Value::String("<Range>".to_string()),
Value::Float { val, .. } => toml::Value::Float(*val), Value::Float { val, .. } => toml::Value::Float(*val),
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()), Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
@ -157,6 +155,43 @@ fn to_toml(
} }
} }
/// Convert chrono datetime into a toml::Value datetime. The latter uses its
/// own ad-hoc datetime types, which makes this somewhat convoluted.
fn to_toml_datetime(datetime: &DateTime<FixedOffset>) -> toml::value::Datetime {
let date = toml::value::Date {
// TODO: figure out what to do with BC dates, because the toml
// crate doesn't support them. Same for large years, which
// don't fit in u16.
year: datetime.year_ce().1 as u16,
// Panic: this is safe, because chrono guarantees that the month
// value will be between 1 and 12 and the day will be between 1
// and 31
month: datetime.month() as u8,
day: datetime.day() as u8,
};
let time = toml::value::Time {
// Panic: same as before, chorono guarantees that all of the following 3
// methods return values less than 65'000
hour: datetime.hour() as u8,
minute: datetime.minute() as u8,
second: datetime.second() as u8,
nanosecond: datetime.nanosecond(),
};
let offset = toml::value::Offset::Custom {
// Panic: minute timezone offset fits into i16 (that's more than
// 1000 hours)
minutes: (-datetime.timezone().utc_minus_local() / 60) as i16,
};
toml::value::Datetime {
date: Some(date),
time: Some(time),
offset: Some(offset),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -181,7 +216,20 @@ mod tests {
Span::test_data(), Span::test_data(),
); );
let reference_date = toml::Value::String(String::from("1980-10-12T10:12:44+02:00")); let reference_date = toml::Value::Datetime(toml::value::Datetime {
date: Some(toml::value::Date {
year: 1980,
month: 10,
day: 12,
}),
time: Some(toml::value::Time {
hour: 10,
minute: 12,
second: 44,
nanosecond: 0,
}),
offset: Some(toml::value::Offset::Custom { minutes: 120 }),
});
let result = helper(&engine_state, &test_date); let result = helper(&engine_state, &test_date);

View File

@ -1,6 +1,7 @@
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use nu_color_config::StyleComputer; use nu_color_config::StyleComputer;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ast::{Expr, Expression};
use std::collections::VecDeque; use std::collections::VecDeque;
@ -14,6 +15,7 @@ struct Arguments {
month_names: bool, month_names: bool,
full_year: Option<Spanned<i64>>, full_year: Option<Spanned<i64>>,
week_start: Option<Spanned<String>>, week_start: Option<Spanned<String>>,
as_table: bool,
} }
impl Command for Cal { impl Command for Cal {
@ -26,6 +28,7 @@ impl Command for Cal {
.switch("year", "Display the year column", Some('y')) .switch("year", "Display the year column", Some('y'))
.switch("quarter", "Display the quarter column", Some('q')) .switch("quarter", "Display the quarter column", Some('q'))
.switch("month", "Display the month column", Some('m')) .switch("month", "Display the month column", Some('m'))
.switch("as-table", "output as a table", Some('t'))
.named( .named(
"full-year", "full-year",
SyntaxShape::Int, SyntaxShape::Int,
@ -43,7 +46,10 @@ impl Command for Cal {
"Display the month names instead of integers", "Display the month names instead of integers",
None, None,
) )
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::Nothing, Type::String),
])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples .allow_variants_without_examples(true) // TODO: supply exhaustive examples
.category(Category::Generators) .category(Category::Generators)
} }
@ -75,10 +81,15 @@ impl Command for Cal {
result: None, result: None,
}, },
Example { Example {
description: "This month's calendar with the week starting on monday", description: "This month's calendar with the week starting on Monday",
example: "cal --week-start mo", example: "cal --week-start mo",
result: None, result: None,
}, },
Example {
description: "How many 'Friday the Thirteenths' occurred in 2015?",
example: "cal --as-table --full-year 2015 | where fr == 13 | length",
result: None,
},
] ]
} }
} }
@ -101,6 +112,7 @@ pub fn cal(
quarter: call.has_flag(engine_state, stack, "quarter")?, quarter: call.has_flag(engine_state, stack, "quarter")?,
full_year: call.get_flag(engine_state, stack, "full-year")?, full_year: call.get_flag(engine_state, stack, "full-year")?,
week_start: call.get_flag(engine_state, stack, "week-start")?, week_start: call.get_flag(engine_state, stack, "week-start")?,
as_table: call.has_flag(engine_state, stack, "as-table")?,
}; };
let style_computer = &StyleComputer::from_config(engine_state, stack); let style_computer = &StyleComputer::from_config(engine_state, stack);
@ -131,7 +143,27 @@ pub fn cal(
style_computer, style_computer,
)?; )?;
Ok(Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data()) let mut table_no_index = Call::new(Span::unknown());
table_no_index.add_named((
Spanned {
item: "index".to_string(),
span: Span::unknown(),
},
None,
Some(Expression::new_unknown(
Expr::Bool(false),
Span::unknown(),
Type::Bool,
)),
));
let cal_table_output =
Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data();
if !arguments.as_table {
crate::Table.run(engine_state, stack, &table_no_index, cal_table_output)
} else {
Ok(cal_table_output)
}
} }
fn get_invalid_year_shell_error(head: Span) -> ShellError { fn get_invalid_year_shell_error(head: Span) -> ShellError {

View File

@ -12,13 +12,7 @@ impl Command for Generate {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("generate") Signature::build("generate")
.input_output_types(vec![ .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
(Type::Nothing, Type::List(Box::new(Type::Any))),
(
Type::List(Box::new(Type::Any)),
Type::List(Box::new(Type::Any)),
),
])
.required("initial", SyntaxShape::Any, "Initial value.") .required("initial", SyntaxShape::Any, "Initial value.")
.required( .required(
"closure", "closure",
@ -63,23 +57,10 @@ used as the next argument to the closure, otherwise generation stops.
)), )),
}, },
Example { Example {
example: "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10", example:
description: "Generate a stream of fibonacci numbers", "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
result: Some(Value::list( description: "Generate a continuous stream of Fibonacci numbers",
vec![ result: None,
Value::test_int(0),
Value::test_int(1),
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_int(5),
Value::test_int(8),
Value::test_int(13),
Value::test_int(21),
Value::test_int(34),
],
Span::test_data(),
)),
}, },
] ]
} }

View File

@ -122,7 +122,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
let usage = sig.usage; let usage = sig.usage;
let search_terms = sig.search_terms; let search_terms = sig.search_terms;
let command_type = format!("{:?}", decl.command_type()).to_ascii_lowercase(); let command_type = decl.command_type().to_string();
// Build table of parameters // Build table of parameters
let param_table = { let param_table = {

View File

@ -1,7 +1,10 @@
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet; use nu_protocol::engine::StateWorkingSet;
use std::path::{Path, PathBuf}; use std::{
io,
path::{Path, PathBuf},
};
struct Arguments { struct Arguments {
pwd: PathBuf, pwd: PathBuf,
@ -36,7 +39,7 @@ impl Command for SubCommand {
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
r#"This checks the file system to confirm the path's object type. r#"This checks the file system to confirm the path's object type.
If nothing is found, an empty string will be returned."# If the path does not exist, null will be returned."#
} }
fn is_const(&self) -> bool { fn is_const(&self) -> bool {
@ -104,9 +107,11 @@ If nothing is found, an empty string will be returned."#
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value { fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
let path = nu_path::expand_path_with(path, &args.pwd, true); let path = nu_path::expand_path_with(path, &args.pwd, true);
let meta = path.symlink_metadata(); match path.symlink_metadata() {
let ty = meta.as_ref().map(get_file_type).unwrap_or(""); Ok(metadata) => Value::string(get_file_type(&metadata), span),
Value::string(ty, span) Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span),
Err(err) => Value::error(err.into_spanned(span).into(), span),
}
} }
fn get_file_type(md: &std::fs::Metadata) -> &str { fn get_file_type(md: &std::fs::Metadata) -> &str {

View File

@ -81,7 +81,7 @@ pub fn sort(
if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) { if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: nonexistent, col_name: nonexistent,
span, span: Some(span),
src_span: val_span, src_span: val_span,
}); });
} }

View File

@ -12,14 +12,17 @@ impl Command for StorInsert {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("stor insert") Signature::build("stor insert")
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::record(), Type::table()),
])
.required_named( .required_named(
"table-name", "table-name",
SyntaxShape::String, SyntaxShape::String,
"name of the table you want to insert into", "name of the table you want to insert into",
Some('t'), Some('t'),
) )
.required_named( .named(
"data-record", "data-record",
SyntaxShape::Record(vec![]), SyntaxShape::Record(vec![]),
"a record of column names and column values to insert into the specified table", "a record of column names and column values to insert into the specified table",
@ -42,7 +45,13 @@ impl Command for StorInsert {
description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs", description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs",
example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}", example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}",
result: None, result: None,
}] },
Example {
description: "Insert data through pipeline input as a record of column-name and column-value pairs",
example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb",
result: None,
},
]
} }
fn run( fn run(
@ -50,25 +59,79 @@ impl Command for StorInsert {
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?; let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?;
let columns: Option<Record> = call.get_flag(engine_state, stack, "data-record")?; let data_record: Option<Record> = call.get_flag(engine_state, stack, "data-record")?;
// let config = engine_state.get_config(); // let config = engine_state.get_config();
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
// Check if the record is being passed as input or using the data record parameter
let columns = handle(span, data_record, input)?;
process(table_name, span, &db, columns)?; process(table_name, span, &db, columns)?;
Ok(Value::custom(db, span).into_pipeline_data()) Ok(Value::custom(db, span).into_pipeline_data())
} }
} }
fn handle(
span: Span,
data_record: Option<Record>,
input: PipelineData,
) -> Result<Record, ShellError> {
match input {
PipelineData::Empty => data_record.ok_or_else(|| ShellError::MissingParameter {
param_name: "requires a record".into(),
span,
}),
PipelineData::Value(value, ..) => {
// Since input is being used, check if the data record parameter is used too
if data_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
match value {
Value::Record { val, .. } => Ok(val.into_owned()),
val => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
}),
}
}
_ => {
if data_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: "".into(),
dst_span: span,
src_span: span,
})
}
}
}
fn process( fn process(
table_name: Option<String>, table_name: Option<String>,
span: Span, span: Span,
db: &SQLiteDatabase, db: &SQLiteDatabase,
columns: Option<Record>, record: Record,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
if table_name.is_none() { if table_name.is_none() {
return Err(ShellError::MissingParameter { return Err(ShellError::MissingParameter {
@ -77,9 +140,8 @@ fn process(
}); });
} }
let new_table_name = table_name.unwrap_or("table".into()); let new_table_name = table_name.unwrap_or("table".into());
if let Ok(conn) = db.open_connection() { if let Ok(conn) = db.open_connection() {
match columns {
Some(record) => {
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
let cols = record.columns(); let cols = record.columns();
cols.for_each(|col| { cols.for_each(|col| {
@ -116,15 +178,7 @@ fn process(
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
}
None => {
return Err(ShellError::MissingParameter {
param_name: "requires at least one column".into(),
span,
});
}
}; };
}
// dbg!(db.clone()); // dbg!(db.clone());
Ok(()) Ok(())
} }
@ -176,7 +230,7 @@ mod test {
), ),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -201,7 +255,7 @@ mod test {
Value::test_string("String With Spaces".to_string()), Value::test_string("String With Spaces".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -226,7 +280,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
// SQLite uses dynamic typing, making any length acceptable for a varchar column // SQLite uses dynamic typing, making any length acceptable for a varchar column
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -251,7 +305,7 @@ mod test {
Value::test_string("ThisIsTheWrongType".to_string()), Value::test_string("ThisIsTheWrongType".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
// SQLite uses dynamic typing, making any type acceptable for a column // SQLite uses dynamic typing, making any type acceptable for a column
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -276,7 +330,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -293,7 +347,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_err()); assert!(result.is_err());
} }

View File

@ -11,14 +11,17 @@ impl Command for StorUpdate {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("stor update") Signature::build("stor update")
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::record(), Type::table()),
])
.required_named( .required_named(
"table-name", "table-name",
SyntaxShape::String, SyntaxShape::String,
"name of the table you want to insert into", "name of the table you want to insert into",
Some('t'), Some('t'),
) )
.required_named( .named(
"update-record", "update-record",
SyntaxShape::Record(vec![]), SyntaxShape::Record(vec![]),
"a record of column names and column values to update in the specified table", "a record of column names and column values to update in the specified table",
@ -54,6 +57,11 @@ impl Command for StorUpdate {
example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"", example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"",
result: None, result: None,
}, },
Example {
description: "Update the in-memory sqlite database through pipeline input",
example: "{str1: nushell datetime1: 2020-04-17} | stor update --table-name nudb",
result: None,
},
] ]
} }
@ -62,17 +70,84 @@ impl Command for StorUpdate {
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?; let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?;
let columns: Option<Record> = call.get_flag(engine_state, stack, "update-record")?; let update_record: Option<Record> = call.get_flag(engine_state, stack, "update-record")?;
let where_clause_opt: Option<Spanned<String>> = let where_clause_opt: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "where-clause")?; call.get_flag(engine_state, stack, "where-clause")?;
// Open the in-mem database // Open the in-mem database
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
// Check if the record is being passed as input or using the update record parameter
let columns = handle(span, update_record, input)?;
process(table_name, span, &db, columns, where_clause_opt)?;
Ok(Value::custom(db, span).into_pipeline_data())
}
}
fn handle(
span: Span,
update_record: Option<Record>,
input: PipelineData,
) -> Result<Record, ShellError> {
match input {
PipelineData::Empty => update_record.ok_or_else(|| ShellError::MissingParameter {
param_name: "requires a record".into(),
span,
}),
PipelineData::Value(value, ..) => {
// Since input is being used, check if the data record parameter is used too
if update_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--update-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
match value {
Value::Record { val, .. } => Ok(val.into_owned()),
val => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
}),
}
}
_ => {
if update_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--update-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: "".into(),
dst_span: span,
src_span: span,
})
}
}
}
fn process(
table_name: Option<String>,
span: Span,
db: &SQLiteDatabase,
record: Record,
where_clause_opt: Option<Spanned<String>>,
) -> Result<(), ShellError> {
if table_name.is_none() { if table_name.is_none() {
return Err(ShellError::MissingParameter { return Err(ShellError::MissingParameter {
param_name: "requires at table name".into(), param_name: "requires at table name".into(),
@ -81,8 +156,6 @@ impl Command for StorUpdate {
} }
let new_table_name = table_name.unwrap_or("table".into()); let new_table_name = table_name.unwrap_or("table".into());
if let Ok(conn) = db.open_connection() { if let Ok(conn) = db.open_connection() {
match columns {
Some(record) => {
let mut update_stmt = format!("UPDATE {} ", new_table_name); let mut update_stmt = format!("UPDATE {} ", new_table_name);
update_stmt.push_str("SET "); update_stmt.push_str("SET ");
@ -134,17 +207,8 @@ impl Command for StorUpdate {
inner: vec![], inner: vec![],
})?; })?;
} }
None => {
return Err(ShellError::MissingParameter {
param_name: "requires at least one column".into(),
span: call.head,
});
}
};
}
// dbg!(db.clone()); // dbg!(db.clone());
Ok(Value::custom(db, span).into_pipeline_data()) Ok(())
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,6 +1,6 @@
use indexmap::{indexmap, IndexMap}; use indexmap::{indexmap, IndexMap};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};

View File

@ -45,20 +45,6 @@ impl Command for DetectColumns {
vec!["split", "tabular"] vec!["split", "tabular"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "guess")? {
guess_width(engine_state, stack, call, input)
} else {
detect_columns(engine_state, stack, call, input)
}
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -109,33 +95,87 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn guess_width( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> {
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?;
let noheader = call.has_flag(engine_state, stack, "no-headers")?;
let range: Option<Range> = call.get_flag(engine_state, stack, "combine-columns")?;
let args = Arguments {
noheader,
num_rows_to_skip,
range,
};
if call.has_flag(engine_state, stack, "guess")? {
guess_width(engine_state, call, input, args)
} else {
detect_columns(engine_state, call, input, args)
}
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let num_rows_to_skip: Option<usize> = call.get_flag_const(working_set, "skip")?;
let noheader = call.has_flag_const(working_set, "no-headers")?;
let range: Option<Range> = call.get_flag_const(working_set, "combine-columns")?;
let args = Arguments {
noheader,
num_rows_to_skip,
range,
};
if call.has_flag_const(working_set, "guess")? {
guess_width(working_set.permanent(), call, input, args)
} else {
detect_columns(working_set.permanent(), call, input, args)
}
}
}
struct Arguments {
num_rows_to_skip: Option<usize>,
noheader: bool,
range: Option<Range>,
}
fn guess_width(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
use super::guess_width::GuessWidth; use super::guess_width::GuessWidth;
let input_span = input.span().unwrap_or(call.head); let input_span = input.span().unwrap_or(call.head);
let mut input = input.collect_string("", engine_state.get_config())?; let mut input = input.collect_string("", engine_state.get_config())?;
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?; if let Some(rows) = args.num_rows_to_skip {
if let Some(rows) = num_rows_to_skip {
input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); input = input.lines().skip(rows).map(|x| x.to_string()).join("\n");
} }
let mut guess_width = GuessWidth::new_reader(Box::new(Cursor::new(input))); let mut guess_width = GuessWidth::new_reader(Box::new(Cursor::new(input)));
let noheader = call.has_flag(engine_state, stack, "no-headers")?;
let result = guess_width.read_all(); let result = guess_width.read_all();
if result.is_empty() { if result.is_empty() {
return Ok(Value::nothing(input_span).into_pipeline_data()); return Ok(Value::nothing(input_span).into_pipeline_data());
} }
let range: Option<Range> = call.get_flag(engine_state, stack, "combine-columns")?; if !args.noheader {
if !noheader {
let columns = result[0].clone(); let columns = result[0].clone();
Ok(result Ok(result
.into_iter() .into_iter()
@ -152,7 +192,7 @@ fn guess_width(
let record = let record =
Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span);
match record { match record {
Ok(r) => match &range { Ok(r) => match &args.range {
Some(range) => merge_record(r, range, input_span), Some(range) => merge_record(r, range, input_span),
None => Value::record(r, input_span), None => Value::record(r, input_span),
}, },
@ -177,7 +217,7 @@ fn guess_width(
let record = let record =
Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span);
match record { match record {
Ok(r) => match &range { Ok(r) => match &args.range {
Some(range) => merge_record(r, range, input_span), Some(range) => merge_record(r, range, input_span),
None => Value::record(r, input_span), None => Value::record(r, input_span),
}, },
@ -190,21 +230,18 @@ fn guess_width(
fn detect_columns( fn detect_columns(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head; let name_span = call.head;
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?;
let noheader = call.has_flag(engine_state, stack, "no-headers")?;
let range: Option<Range> = call.get_flag(engine_state, stack, "combine-columns")?;
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config(); let config = engine_state.get_config();
let input = input.collect_string("", config)?; let input = input.collect_string("", config)?;
let input: Vec<_> = input let input: Vec<_> = input
.lines() .lines()
.skip(num_rows_to_skip.unwrap_or_default()) .skip(args.num_rows_to_skip.unwrap_or_default())
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect(); .collect();
@ -214,13 +251,14 @@ fn detect_columns(
if let Some(orig_headers) = headers { if let Some(orig_headers) = headers {
let mut headers = find_columns(&orig_headers); let mut headers = find_columns(&orig_headers);
if noheader { if args.noheader {
for header in headers.iter_mut().enumerate() { for header in headers.iter_mut().enumerate() {
header.1.item = format!("column{}", header.0); header.1.item = format!("column{}", header.0);
} }
} }
Ok(noheader Ok(args
.noheader
.then_some(orig_headers) .then_some(orig_headers)
.into_iter() .into_iter()
.chain(input) .chain(input)
@ -273,7 +311,7 @@ fn detect_columns(
} }
} }
match &range { match &args.range {
Some(range) => merge_record(record, range, name_span), Some(range) => merge_record(record, range, name_span),
None => Value::record(record, name_span), None => Value::record(record, name_span),
} }

View File

@ -7,10 +7,9 @@ use base64::{
Engine, Engine,
}; };
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{EngineState, Stack}, engine::EngineState,
PipelineData, ShellError, Span, Spanned, Value, PipelineData, ShellError, Span, Spanned, Value,
}; };
@ -42,22 +41,24 @@ impl CmdArgument for Arguments {
} }
} }
pub(super) struct Base64CommandArguments {
pub(super) character_set: Option<Spanned<String>>,
pub(super) action_type: ActionType,
pub(super) binary: bool,
}
pub fn operate( pub fn operate(
action_type: ActionType,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
cell_paths: Vec<CellPath>,
args: Base64CommandArguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
// Default the character set to standard if the argument is not specified. // Default the character set to standard if the argument is not specified.
let character_set = match character_set { let character_set = match args.character_set {
Some(inner_tag) => inner_tag, Some(inner_tag) => inner_tag,
None => Spanned { None => Spanned {
item: "standard".to_string(), item: "standard".to_string(),
@ -68,9 +69,9 @@ pub fn operate(
let args = Arguments { let args = Arguments {
encoding_config: Base64Config { encoding_config: Base64Config {
character_set, character_set,
action_type, action_type: args.action_type,
}, },
binary, binary: args.binary,
cell_paths, cell_paths,
}; };

View File

@ -46,6 +46,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -53,8 +57,27 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head;
let encoding: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?; let encoding: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
run(call, input, encoding)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let encoding: Option<Spanned<String>> = call.opt_const(working_set, 0)?;
run(call, input, encoding)
}
}
fn run(
call: &Call,
input: PipelineData,
encoding: Option<Spanned<String>>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
match input { match input {
PipelineData::ByteStream(stream, ..) => { PipelineData::ByteStream(stream, ..) => {
@ -97,7 +120,6 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
}), }),
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -1,4 +1,4 @@
use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
@ -66,6 +66,10 @@ impl Command for DecodeBase64 {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -73,7 +77,34 @@ impl Command for DecodeBase64 {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(ActionType::Decode, engine_state, stack, call, input) let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = Base64CommandArguments {
action_type: ActionType::Decode,
binary,
character_set,
};
operate(engine_state, call, input, cell_paths, args)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let character_set: Option<Spanned<String>> =
call.get_flag_const(working_set, "character-set")?;
let binary = call.has_flag_const(working_set, "binary")?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
let args = Base64CommandArguments {
action_type: ActionType::Decode,
binary,
character_set,
};
operate(working_set.permanent(), call, input, cell_paths, args)
} }
} }

View File

@ -69,6 +69,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -76,9 +80,30 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head;
let encoding: Spanned<String> = call.req(engine_state, stack, 0)?; let encoding: Spanned<String> = call.req(engine_state, stack, 0)?;
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?;
run(call, input, encoding, ignore_errors)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let encoding: Spanned<String> = call.req_const(working_set, 0)?;
let ignore_errors = call.has_flag_const(working_set, "ignore-errors")?;
run(call, input, encoding, ignore_errors)
}
}
fn run(
call: &Call,
input: PipelineData,
encoding: Spanned<String>,
ignore_errors: bool,
) -> Result<PipelineData, ShellError> {
let head = call.head;
match input { match input {
PipelineData::ByteStream(stream, ..) => { PipelineData::ByteStream(stream, ..) => {
@ -113,7 +138,6 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
}), }),
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -1,4 +1,4 @@
use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
@ -70,6 +70,10 @@ impl Command for EncodeBase64 {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -77,7 +81,34 @@ impl Command for EncodeBase64 {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(ActionType::Encode, engine_state, stack, call, input) let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = Base64CommandArguments {
action_type: ActionType::Encode,
binary,
character_set,
};
operate(engine_state, call, input, cell_paths, args)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let character_set: Option<Spanned<String>> =
call.get_flag_const(working_set, "character-set")?;
let binary = call.has_flag_const(working_set, "binary")?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
let args = Base64CommandArguments {
action_type: ActionType::Encode,
binary,
character_set,
};
operate(working_set.permanent(), call, input, cell_paths, args)
} }
} }

View File

@ -27,7 +27,7 @@ impl Command for FormatDate {
SyntaxShape::String, SyntaxShape::String,
"The desired format date.", "The desired format date.",
) )
.category(Category::Date) .category(Category::Strings)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -38,36 +38,6 @@ impl Command for FormatDate {
vec!["fmt", "strftime"] vec!["fmt", "strftime"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
if call.has_flag(engine_state, stack, "list")? {
return Ok(PipelineData::Value(
generate_strftime_list(head, false),
None,
));
}
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| match &format {
Some(format) => format_helper(value, format.item.as_str(), format.span, head),
None => format_helper_rfc2822(value, head),
},
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -104,6 +74,61 @@ impl Command for FormatDate {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let list = call.has_flag(engine_state, stack, "list")?;
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
run(engine_state, call, input, list, format)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let list = call.has_flag_const(working_set, "list")?;
let format = call.opt_const::<Spanned<String>>(working_set, 0)?;
run(working_set.permanent(), call, input, list, format)
}
}
fn run(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
list: bool,
format: Option<Spanned<String>>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
if list {
return Ok(PipelineData::Value(
generate_strftime_list(head, false),
None,
));
}
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(
move |value| match &format {
Some(format) => format_helper(value, format.item.as_str(), format.span, head),
None => format_helper_rfc2822(value, head),
},
engine_state.ctrlc.clone(),
)
} }
fn format_from<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value fn format_from<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value

View File

@ -53,6 +53,10 @@ impl Command for FormatDuration {
vec!["convert", "display", "pattern", "human readable"] vec!["convert", "display", "pattern", "human readable"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -81,6 +85,33 @@ impl Command for FormatDuration {
) )
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let format_value = call
.req_const::<Value>(working_set, 0)?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let float_precision = working_set.permanent().config.float_precision as usize;
let arg = Arguments {
format_value,
float_precision,
cell_paths,
};
operate(
format_value_impl,
arg,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,6 +1,6 @@
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::format_filesize; use nu_protocol::{engine::StateWorkingSet, format_filesize};
struct Arguments { struct Arguments {
format_value: String, format_value: String,
@ -50,6 +50,10 @@ impl Command for FormatFilesize {
vec!["convert", "display", "pattern", "human readable"] vec!["convert", "display", "pattern", "human readable"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -76,6 +80,31 @@ impl Command for FormatFilesize {
) )
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let format_value = call
.req_const::<Value>(working_set, 0)?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let arg = Arguments {
format_value,
cell_paths,
};
operate(
format_value_impl,
arg,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,6 +1,6 @@
use fancy_regex::{Captures, Regex}; use fancy_regex::{Captures, Regex};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ListStream; use nu_protocol::{engine::StateWorkingSet, ListStream};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
@ -99,6 +99,10 @@ impl Command for Parse {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -106,19 +110,31 @@ impl Command for Parse {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) 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)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> 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)
} }
} }
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, pattern: Spanned<String>,
regex: bool,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let regex: bool = call.has_flag(engine_state, stack, "regex")?;
let pattern_item = pattern.item; let pattern_item = pattern.item;
let pattern_span = pattern.span; let pattern_span = pattern.span;

View File

@ -1,5 +1,6 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)] #[derive(Clone)]
@ -88,6 +89,10 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -95,19 +100,28 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
split_chars(engine_state, stack, call, input) let graphemes = grapheme_flags(engine_state, stack, call)?;
split_chars(engine_state, call, input, graphemes)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let graphemes = grapheme_flags_const(working_set, call)?;
split_chars(working_set.permanent(), call, input, graphemes)
} }
} }
fn split_chars( fn split_chars(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
graphemes: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
let graphemes = grapheme_flags(engine_state, stack, call)?;
input.map( input.map(
move |x| split_chars_helper(&x, span, graphemes), move |x| split_chars_helper(&x, span, graphemes),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),

View File

@ -43,16 +43,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_column(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -103,35 +93,83 @@ impl Command for SubCommand {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn split_column( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(engine_state, stack, 0)?; let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?; let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?;
let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?;
let has_regex = call.has_flag(engine_state, stack, "regex")?;
let regex = if call.has_flag(engine_state, stack, "regex")? { let args = Arguments {
Regex::new(&separator.item) separator,
rest,
collapse_empty,
has_regex,
};
split_column(engine_state, call, input, args)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Spanned<String> = call.req_const(working_set, 0)?;
let rest: Vec<Spanned<String>> = call.rest_const(working_set, 1)?;
let collapse_empty = call.has_flag_const(working_set, "collapse-empty")?;
let has_regex = call.has_flag_const(working_set, "regex")?;
let args = Arguments {
separator,
rest,
collapse_empty,
has_regex,
};
split_column(working_set.permanent(), call, input, args)
}
}
struct Arguments {
separator: Spanned<String>,
rest: Vec<Spanned<String>>,
collapse_empty: bool,
has_regex: bool,
}
fn split_column(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let regex = if args.has_regex {
Regex::new(&args.separator.item)
} else { } else {
let escaped = regex::escape(&separator.item); let escaped = regex::escape(&args.separator.item);
Regex::new(&escaped) Regex::new(&escaped)
} }
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "Error with regular expression".into(), error: "Error with regular expression".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(separator.span), span: Some(args.separator.span),
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
input.flat_map( input.flat_map(
move |x| split_column_helper(&x, &regex, &rest, collapse_empty, name_span), move |x| split_column_helper(&x, &regex, &args.rest, args.collapse_empty, name_span),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -36,16 +36,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_list(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -145,6 +135,33 @@ impl Command for SubCommand {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let has_regex = call.has_flag(engine_state, stack, "regex")?;
let separator: Value = call.req(engine_state, stack, 0)?;
split_list(engine_state, call, input, has_regex, separator)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let has_regex = call.has_flag_const(working_set, "regex")?;
let separator: Value = call.req_const(working_set, 0)?;
split_list(working_set.permanent(), call, input, has_regex, separator)
}
} }
enum Matcher { enum Matcher {
@ -188,15 +205,15 @@ impl Matcher {
fn split_list( fn split_list(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
has_regex: bool,
separator: Value,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let separator: Value = call.req(engine_state, stack, 0)?;
let mut temp_list = Vec::new(); let mut temp_list = Vec::new();
let mut returned_list = Vec::new(); let mut returned_list = Vec::new();
let matcher = Matcher::new(call.has_flag(engine_state, stack, "regex")?, separator)?; let matcher = Matcher::new(has_regex, separator)?;
for val in input { for val in input {
if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) {
break; break;

View File

@ -43,16 +43,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_row(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -109,32 +99,77 @@ impl Command for SubCommand {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn split_row( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(engine_state, stack, 0)?; let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let regex = if call.has_flag(engine_state, stack, "regex")? { let max_split: Option<usize> = call.get_flag(engine_state, stack, "number")?;
Regex::new(&separator.item) let has_regex = call.has_flag(engine_state, stack, "regex")?;
let args = Arguments {
separator,
max_split,
has_regex,
};
split_row(engine_state, call, input, args)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Spanned<String> = call.req_const(working_set, 0)?;
let max_split: Option<usize> = call.get_flag_const(working_set, "number")?;
let has_regex = call.has_flag_const(working_set, "regex")?;
let args = Arguments {
separator,
max_split,
has_regex,
};
split_row(working_set.permanent(), call, input, args)
}
}
struct Arguments {
has_regex: bool,
separator: Spanned<String>,
max_split: Option<usize>,
}
fn split_row(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let regex = if args.has_regex {
Regex::new(&args.separator.item)
} else { } else {
let escaped = regex::escape(&separator.item); let escaped = regex::escape(&args.separator.item);
Regex::new(&escaped) Regex::new(&escaped)
} }
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "Error with regular expression".into(), error: "Error with regular expression".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(separator.span), span: Some(args.separator.span),
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
let max_split: Option<usize> = call.get_flag(engine_state, stack, "number")?;
input.flat_map( input.flat_map(
move |x| split_row_helper(&x, &regex, max_split, name_span), move |x| split_row_helper(&x, &regex, args.max_split, name_span),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -1,4 +1,4 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use fancy_regex::Regex; use fancy_regex::Regex;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
@ -96,6 +96,10 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -103,40 +107,76 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
split_words(engine_state, stack, call, input) let word_length: Option<usize> = call.get_flag(engine_state, stack, "min-word-length")?;
let has_grapheme = call.has_flag(engine_state, stack, "grapheme-clusters")?;
let has_utf8 = call.has_flag(engine_state, stack, "utf-8-bytes")?;
let graphemes = grapheme_flags(engine_state, stack, call)?;
let args = Arguments {
word_length,
has_grapheme,
has_utf8,
graphemes,
};
split_words(engine_state, call, input, args)
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let word_length: Option<usize> = call.get_flag_const(working_set, "min-word-length")?;
let has_grapheme = call.has_flag_const(working_set, "grapheme-clusters")?;
let has_utf8 = call.has_flag_const(working_set, "utf-8-bytes")?;
let graphemes = grapheme_flags_const(working_set, call)?;
let args = Arguments {
word_length,
has_grapheme,
has_utf8,
graphemes,
};
split_words(working_set.permanent(), call, input, args)
}
}
struct Arguments {
word_length: Option<usize>,
has_grapheme: bool,
has_utf8: bool,
graphemes: bool,
} }
fn split_words( fn split_words(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
// let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?; // let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?;
// let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?; // let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?;
// let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?; // let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?;
let word_length: Option<usize> = call.get_flag(engine_state, stack, "min-word-length")?;
if word_length.is_none() { if args.word_length.is_none() {
if call.has_flag(engine_state, stack, "grapheme-clusters")? { if args.has_grapheme {
return Err(ShellError::IncompatibleParametersSingle { return Err(ShellError::IncompatibleParametersSingle {
msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(), msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(),
span, span,
}); });
} }
if call.has_flag(engine_state, stack, "utf-8-bytes")? { if args.has_utf8 {
return Err(ShellError::IncompatibleParametersSingle { return Err(ShellError::IncompatibleParametersSingle {
msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(), msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(),
span, span,
}); });
} }
} }
let graphemes = grapheme_flags(engine_state, stack, call)?;
input.map( input.map(
move |x| split_words_helper(&x, word_length, span, graphemes), move |x| split_words_helper(&x, args.word_length, span, args.graphemes),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["convert", "style", "caps", "upper"] vec!["convert", "style", "caps", "upper"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
operate(engine_state, call, input, column_paths)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -72,12 +87,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["lower case", "lowercase"] vec!["lower case", "lowercase"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
operate(engine_state, call, input, column_paths)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -80,12 +95,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["uppercase", "upper case"] vec!["uppercase", "upper case"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
operate(engine_state, call, input, column_paths)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -57,12 +72,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -10,7 +10,6 @@ struct Arguments {
substring: String, substring: String,
cell_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
case_insensitive: bool, case_insensitive: bool,
not_contain: bool,
} }
impl CmdArgument for Arguments { impl CmdArgument for Arguments {
@ -40,7 +39,6 @@ impl Command for SubCommand {
"For a data structure input, check strings at the given cell paths, and replace with result.", "For a data structure input, check strings at the given cell paths, and replace with result.",
) )
.switch("ignore-case", "search is case insensitive", Some('i')) .switch("ignore-case", "search is case insensitive", Some('i'))
.switch("not", "DEPRECATED OPTION: does not contain", Some('n'))
.category(Category::Strings) .category(Category::Strings)
} }
@ -52,6 +50,10 @@ impl Command for SubCommand {
vec!["substring", "match", "find", "search"] vec!["substring", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -59,9 +61,25 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "not")? { let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: call.req::<String>(engine_state, stack, 0)?,
cell_paths,
case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.has_flag_const(working_set, "not")? {
nu_protocol::report_error_new( nu_protocol::report_error_new(
engine_state, working_set.permanent(),
&ShellError::GenericError { &ShellError::GenericError {
error: "Deprecated option".into(), error: "Deprecated option".into(),
msg: "`str contains --not {string}` is deprecated and will be removed in 0.95." msg: "`str contains --not {string}` is deprecated and will be removed in 0.95."
@ -73,15 +91,20 @@ impl Command for SubCommand {
); );
} }
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments { let args = Arguments {
substring: call.req::<String>(engine_state, stack, 0)?, substring: call.req_const::<String>(working_set, 0)?,
cell_paths, cell_paths,
case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
not_contain: call.has_flag(engine_state, stack, "not")?,
}; };
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -142,7 +165,6 @@ fn action(
input: &Value, input: &Value,
Arguments { Arguments {
case_insensitive, case_insensitive,
not_contain,
substring, substring,
.. ..
}: &Arguments, }: &Arguments,
@ -150,23 +172,11 @@ fn action(
) -> Value { ) -> Value {
match input { match input {
Value::String { val, .. } => Value::bool( Value::String { val, .. } => Value::bool(
match case_insensitive { if *case_insensitive {
true => {
if *not_contain {
!val.to_folded_case()
.contains(substring.to_folded_case().as_str())
} else {
val.to_folded_case() val.to_folded_case()
.contains(substring.to_folded_case().as_str()) .contains(substring.to_folded_case().as_str())
}
}
false => {
if *not_contain {
!val.contains(substring)
} else { } else {
val.contains(substring) val.contains(substring)
}
}
}, },
head, head,
), ),

View File

@ -1,6 +1,6 @@
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::levenshtein_distance; use nu_protocol::{engine::StateWorkingSet, levenshtein_distance};
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -49,6 +49,10 @@ impl Command for SubCommand {
vec!["edit", "levenshtein"] vec!["edit", "levenshtein"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -66,6 +70,28 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let compare_string: String = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
compare_string,
cell_paths,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "get the edit distance between two strings", description: "get the edit distance between two strings",

View File

@ -50,6 +50,10 @@ impl Command for SubCommand {
vec!["suffix", "match", "find", "search"] vec!["suffix", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -67,6 +71,28 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: call.req_const::<String>(working_set, 0)?,
cell_paths,
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -179,18 +179,42 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> {
let is_path = call.has_flag(engine_state, stack, "path")?;
run(call, input, is_path, engine_state)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let is_path = call.has_flag_const(working_set, "path")?;
run(call, input, is_path, working_set.permanent())
}
}
fn run(
call: &Call,
input: PipelineData,
is_path: bool,
engine_state: &EngineState,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
if matches!(input, PipelineData::Empty) { if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: span }); return Err(ShellError::PipelineEmpty { dst_span: span });
} }
let is_path = call.has_flag(engine_state, stack, "path")?;
input.map( input.map(
move |v| { move |v| {
let value_span = v.span(); let value_span = v.span();
@ -212,7 +236,6 @@ impl Command for SubCommand {
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }
}
fn str_expand(contents: &str, span: Span, value_span: Span) -> Value { fn str_expand(contents: &str, span: Span, value_span: Span) -> Value {
use bracoxide::{ use bracoxide::{

View File

@ -1,10 +1,10 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::{ use nu_cmd_base::{
input_handler::{operate, CmdArgument}, input_handler::{operate, CmdArgument},
util, util,
}; };
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::Range; use nu_protocol::{engine::StateWorkingSet, Range};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
struct Arguments { struct Arguments {
@ -72,6 +72,10 @@ impl Command for SubCommand {
vec!["match", "find", "search"] vec!["match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -92,6 +96,31 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let substring: Spanned<String> = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: substring.item,
range: call.get_flag_const(working_set, "range")?,
end: call.has_flag_const(working_set, "end")?,
cell_paths,
graphemes: grapheme_flags_const(working_set, call)?,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use std::io::Write; use std::io::Write;
#[derive(Clone)] #[derive(Clone)]
@ -32,6 +33,10 @@ impl Command for StrJoin {
vec!["collect", "concatenate"] vec!["collect", "concatenate"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -40,7 +45,41 @@ impl Command for StrJoin {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt(engine_state, stack, 0)?; let separator: Option<String> = call.opt(engine_state, stack, 0)?;
run(engine_state, call, input, separator)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt_const(working_set, 0)?;
run(working_set.permanent(), call, input, separator)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a string from input",
example: "['nu', 'shell'] | str join",
result: Some(Value::test_string("nushell")),
},
Example {
description: "Create a string from input with a separator",
example: "['nu', 'shell'] | str join '-'",
result: Some(Value::test_string("nu-shell")),
},
]
}
}
fn run(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
separator: Option<String>,
) -> Result<PipelineData, ShellError> {
let config = engine_state.config.clone(); let config = engine_state.config.clone();
let span = call.head; let span = call.head;
@ -77,22 +116,6 @@ impl Command for StrJoin {
Ok(PipelineData::ByteStream(output, metadata)) Ok(PipelineData::ByteStream(output, metadata))
} }
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a string from input",
example: "['nu', 'shell'] | str join",
result: Some(Value::test_string("nushell")),
},
Example {
description: "Create a string from input with a separator",
example: "['nu', 'shell'] | str join '-'",
result: Some(Value::test_string("nu-shell")),
},
]
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,7 +1,7 @@
use crate::{grapheme_flags, grapheme_flags_const}; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
struct Arguments { struct Arguments {

View File

@ -73,6 +73,10 @@ impl Command for SubCommand {
vec!["search", "shift", "switch", "regex"] vec!["search", "shift", "switch", "regex"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -101,6 +105,39 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let find: Spanned<String> = call.req_const(working_set, 0)?;
let replace: Spanned<String> = call.req_const(working_set, 1)?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 2)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let literal_replace = call.has_flag_const(working_set, "no-expand")?;
let no_regex = !call.has_flag_const(working_set, "regex")?
&& !call.has_flag_const(working_set, "multiline")?;
let multiline = call.has_flag_const(working_set, "multiline")?;
let args = Arguments {
all: call.has_flag_const(working_set, "all")?,
find,
replace,
cell_paths,
literal_replace,
no_regex,
multiline,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -37,6 +37,10 @@ impl Command for SubCommand {
vec!["convert", "inverse", "flip"] vec!["convert", "inverse", "flip"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -49,6 +53,23 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -51,6 +51,10 @@ impl Command for SubCommand {
vec!["prefix", "match", "find", "search"] vec!["prefix", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -69,6 +73,29 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let substring: Spanned<String> = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: substring.item,
cell_paths,
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,5 +1,6 @@
use fancy_regex::Regex; use fancy_regex::Regex;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{fmt, str}; use std::{fmt, str};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -29,6 +30,10 @@ impl Command for SubCommand {
vec!["count", "word", "character", "unicode", "wc"] vec!["count", "word", "character", "unicode", "wc"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -39,6 +44,15 @@ impl Command for SubCommand {
stats(engine_state, call, input) stats(engine_state, call, input)
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
stats(working_set.permanent(), call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,11 +1,10 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::{ use nu_cmd_base::{
input_handler::{operate, CmdArgument}, input_handler::{operate, CmdArgument},
util, util,
}; };
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::Range; use nu_protocol::{engine::StateWorkingSet, Range};
use std::cmp::Ordering;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)] #[derive(Clone)]
@ -77,6 +76,10 @@ impl Command for SubCommand {
vec!["slice"] vec!["slice"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -103,6 +106,37 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let range: Range = call.req_const(working_set, 0)?;
let indexes = match util::process_range(&range) {
Ok(idxs) => idxs.into(),
Err(processing_error) => {
return Err(processing_error("could not perform substring", call.head))
}
};
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
indexes,
cell_paths,
graphemes: grapheme_flags_const(working_set, call)?,
};
operate(
action,
args,
input,
call.head,
working_set.permanent().ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -116,6 +150,11 @@ impl Command for SubCommand {
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5", example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
result: Some(Value::test_string("ふが")), result: Some(Value::test_string("ふが")),
}, },
Example {
description: "sub string by negative index",
example: " 'good nushell' | str substring 5..-2",
result: Some(Value::test_string("nushel")),
},
] ]
} }
} }
@ -132,22 +171,15 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
options.0 options.0
}; };
let end: isize = if options.1 < 0 { let end: isize = if options.1 < 0 {
std::cmp::max(len + options.1, 0) options.1 + len
} else { } else {
options.1 options.1
}; };
if start < len && end >= 0 { if start > end {
match start.cmp(&end) { Value::string("", head)
Ordering::Equal => Value::string("", head), } else {
Ordering::Greater => Value::error( Value::string(
ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: head,
},
head,
),
Ordering::Less => Value::string(
{ {
if end == isize::MAX { if end == isize::MAX {
if args.graphemes { if args.graphemes {
@ -164,24 +196,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
} else if args.graphemes { } else if args.graphemes {
s.graphemes(true) s.graphemes(true)
.skip(start as usize) .skip(start as usize)
.take((end - start) as usize) .take((end - start + 1) as usize)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join("") .join("")
} else { } else {
String::from_utf8_lossy( String::from_utf8_lossy(
&s.bytes() &s.bytes()
.skip(start as usize) .skip(start as usize)
.take((end - start) as usize) .take((end - start + 1) as usize)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.to_string() .to_string()
} }
}, },
head, head,
), )
}
} else {
Value::string("", head)
} }
} }
// Propagate errors by explicitly matching them before the final case. // Propagate errors by explicitly matching them before the final case.
@ -208,6 +237,7 @@ mod tests {
test_examples(SubCommand {}) test_examples(SubCommand {})
} }
#[derive(Debug)]
struct Expectation<'a> { struct Expectation<'a> {
options: (isize, isize), options: (isize, isize),
expected: &'a str, expected: &'a str,
@ -231,18 +261,19 @@ mod tests {
let word = Value::test_string("andres"); let word = Value::test_string("andres");
let cases = vec![ let cases = vec![
expectation("a", (0, 1)), expectation("a", (0, 0)),
expectation("an", (0, 2)), expectation("an", (0, 1)),
expectation("and", (0, 3)), expectation("and", (0, 2)),
expectation("andr", (0, 4)), expectation("andr", (0, 3)),
expectation("andre", (0, 5)), expectation("andre", (0, 4)),
expectation("andres", (0, 5)),
expectation("andres", (0, 6)), expectation("andres", (0, 6)),
expectation("", (0, -6)), expectation("a", (0, -6)),
expectation("a", (0, -5)), expectation("an", (0, -5)),
expectation("an", (0, -4)), expectation("and", (0, -4)),
expectation("and", (0, -3)), expectation("andr", (0, -3)),
expectation("andr", (0, -2)), expectation("andre", (0, -2)),
expectation("andre", (0, -1)), expectation("andres", (0, -1)),
// str substring [ -4 , _ ] // str substring [ -4 , _ ]
// str substring -4 , // str substring -4 ,
expectation("dres", (-4, isize::MAX)), expectation("dres", (-4, isize::MAX)),
@ -257,6 +288,7 @@ mod tests {
]; ];
for expectation in &cases { for expectation in &cases {
println!("{:?}", expectation);
let expected = expectation.expected; let expected = expectation.expected;
let actual = action( let actual = action(
&word, &word,

View File

@ -71,6 +71,10 @@ impl Command for SubCommand {
vec!["whitespace", "strip", "lstrip", "rstrip"] vec!["whitespace", "strip", "lstrip", "rstrip"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -79,44 +83,37 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let character = call.get_flag::<Spanned<String>>(engine_state, stack, "char")?; let character = call.get_flag::<Spanned<String>>(engine_state, stack, "char")?;
let to_trim = match character.as_ref() {
Some(v) => {
if v.item.chars().count() > 1 {
return Err(ShellError::GenericError {
error: "Trim only works with single character".into(),
msg: "needs single character".into(),
span: Some(v.span),
help: None,
inner: vec![],
});
}
v.item.chars().next()
}
None => None,
};
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let mode = match cell_paths {
None => ActionMode::Global,
Some(_) => ActionMode::Local,
};
let left = call.has_flag(engine_state, stack, "left")?; let left = call.has_flag(engine_state, stack, "left")?;
let right = call.has_flag(engine_state, stack, "right")?; let right = call.has_flag(engine_state, stack, "right")?;
let trim_side = match (left, right) { run(
(true, true) => TrimSide::Both, character,
(true, false) => TrimSide::Left,
(false, true) => TrimSide::Right,
(false, false) => TrimSide::Both,
};
let args = Arguments {
to_trim,
trim_side,
cell_paths, cell_paths,
mode, (left, right),
}; call,
operate(action, args, input, call.head, engine_state.ctrlc.clone()) input,
engine_state,
)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let character = call.get_flag_const::<Spanned<String>>(working_set, "char")?;
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
let left = call.has_flag_const(working_set, "left")?;
let right = call.has_flag_const(working_set, "right")?;
run(
character,
cell_paths,
(left, right),
call,
input,
working_set.permanent(),
)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -150,6 +147,52 @@ impl Command for SubCommand {
} }
} }
fn run(
character: Option<Spanned<String>>,
cell_paths: Vec<CellPath>,
(left, right): (bool, bool),
call: &Call,
input: PipelineData,
engine_state: &EngineState,
) -> Result<PipelineData, ShellError> {
let to_trim = match character.as_ref() {
Some(v) => {
if v.item.chars().count() > 1 {
return Err(ShellError::GenericError {
error: "Trim only works with single character".into(),
msg: "needs single character".into(),
span: Some(v.span),
help: None,
inner: vec![],
});
}
v.item.chars().next()
}
None => None,
};
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let mode = match cell_paths {
None => ActionMode::Global,
Some(_) => ActionMode::Local,
};
let trim_side = match (left, right) {
(true, true) => TrimSide::Both,
(true, false) => TrimSide::Left,
(false, true) => TrimSide::Right,
(false, false) => TrimSide::Both,
};
let args = Arguments {
to_trim,
trim_side,
cell_paths,
mode,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ActionMode { pub enum ActionMode {
Local, Local,

View File

@ -1,15 +1,15 @@
use nu_cmd_base::hook::eval_hook; use nu_cmd_base::hook::eval_hook;
use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression};
use nu_path::{dots::expand_ndots, expand_tilde};
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Expression}, ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest,
did_you_mean,
process::ChildProcess,
ByteStream, NuGlob, OutDest,
}; };
use nu_system::ForegroundChild; use nu_system::ForegroundChild;
use nu_utils::IgnoreCaseExt; use nu_utils::IgnoreCaseExt;
use pathdiff::diff_paths;
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::{OsStr, OsString},
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Stdio, process::Stdio,
@ -32,8 +32,16 @@ impl Command for External {
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name()) Signature::build(self.name())
.input_output_types(vec![(Type::Any, Type::Any)]) .input_output_types(vec![(Type::Any, Type::Any)])
.required("command", SyntaxShape::String, "External command to run.") .required(
.rest("args", SyntaxShape::Any, "Arguments for external command.") "command",
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
"External command to run.",
)
.rest(
"args",
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]),
"Arguments for external command.",
)
.category(Category::System) .category(Category::System)
} }
@ -46,42 +54,33 @@ impl Command for External {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let cwd = engine_state.cwd(Some(stack))?; let cwd = engine_state.cwd(Some(stack))?;
// Evaluate the command name in the same way the arguments are evaluated. Since this isn't let name: Value = call.req(engine_state, stack, 0)?;
// a spread, it should return a one-element vec.
let name_expr = call let name_str: Cow<str> = match &name {
.positional_nth(0) Value::Glob { val, .. } => Cow::Borrowed(val),
.ok_or_else(|| ShellError::MissingParameter { Value::String { val, .. } => Cow::Borrowed(val),
param_name: "command".into(), _ => Cow::Owned(name.clone().coerce_into_string()?),
span: call.head, };
})?;
let name = eval_argument(engine_state, stack, name_expr, false)? let expanded_name = match &name {
.pop() // Expand tilde and ndots on the name if it's a bare string / glob (#13000)
.expect("eval_argument returned zero-element vec") Value::Glob { no_expand, .. } if !*no_expand => {
.into_spanned(name_expr.span); expand_ndots_safe(expand_tilde(&*name_str))
}
_ => Path::new(&*name_str).to_owned(),
};
// Find the absolute path to the executable. On Windows, set the // Find the absolute path to the executable. On Windows, set the
// executable to "cmd.exe" if it's is a CMD internal command. If the // executable to "cmd.exe" if it's is a CMD internal command. If the
// command is not found, display a helpful error message. // command is not found, display a helpful error message.
let executable = if cfg!(windows) && is_cmd_internal_command(&name.item) { let executable = if cfg!(windows) && is_cmd_internal_command(&name_str) {
PathBuf::from("cmd.exe") PathBuf::from("cmd.exe")
} else { } else {
// Expand tilde on the name if it's a bare string (#13000)
let expanded_name = if is_bare_string(name_expr) {
expand_tilde(&name.item)
} else {
name.item.clone()
};
// Determine the PATH to be used and then use `which` to find it - though this has no // Determine the PATH to be used and then use `which` to find it - though this has no
// effect if it's an absolute path already // effect if it's an absolute path already
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let Some(executable) = which(&expanded_name, &paths, &cwd) else { let Some(executable) = which(expanded_name, &paths, &cwd) else {
return Err(command_not_found( return Err(command_not_found(&name_str, call.head, engine_state, stack));
&name.item,
call.head,
engine_state,
stack,
));
}; };
executable executable
}; };
@ -100,15 +99,15 @@ impl Command for External {
// Configure args. // Configure args.
let args = eval_arguments_from_call(engine_state, stack, call)?; let args = eval_arguments_from_call(engine_state, stack, call)?;
#[cfg(windows)] #[cfg(windows)]
if is_cmd_internal_command(&name.item) { if is_cmd_internal_command(&name_str) {
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
// The /D flag disables execution of AutoRun commands from registry. // The /D flag disables execution of AutoRun commands from registry.
// The /C flag followed by a command name instructs CMD to execute // The /C flag followed by a command name instructs CMD to execute
// that command and quit. // that command and quit.
command.args(["/D", "/C", &name.item]); command.args(["/D", "/C", &name_str]);
for arg in &args { for arg in &args {
command.raw_arg(escape_cmd_argument(arg)?.as_ref()); command.raw_arg(escape_cmd_argument(arg)?);
} }
} else { } else {
command.args(args.into_iter().map(|s| s.item)); command.args(args.into_iter().map(|s| s.item));
@ -216,74 +215,54 @@ impl Command for External {
} }
} }
/// Removes surrounding quotes from a string. Doesn't remove quotes from raw
/// strings. Returns the original string if it doesn't have matching quotes.
fn remove_quotes(s: &str) -> &str {
let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"');
let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\'');
let quoted_by_backticks = s.len() >= 2 && s.starts_with('`') && s.ends_with('`');
if quoted_by_double_quotes || quoted_by_single_quotes || quoted_by_backticks {
&s[1..s.len() - 1]
} else {
s
}
}
/// Evaluate all arguments from a call, performing expansions when necessary. /// Evaluate all arguments from a call, performing expansions when necessary.
pub fn eval_arguments_from_call( pub fn eval_arguments_from_call(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
) -> Result<Vec<Spanned<String>>, ShellError> { ) -> Result<Vec<Spanned<OsString>>, ShellError> {
let ctrlc = &engine_state.ctrlc; let ctrlc = &engine_state.ctrlc;
let cwd = engine_state.cwd(Some(stack))?; let cwd = engine_state.cwd(Some(stack))?;
let mut args: Vec<Spanned<String>> = vec![]; let mut args: Vec<Spanned<OsString>> = vec![];
for (expr, spread) in call.rest_iter(1) { for (expr, spread) in call.rest_iter(1) {
if is_bare_string(expr) {
// If `expr` is a bare string, perform tilde-expansion,
// glob-expansion, and inner-quotes-removal, in that order.
for arg in eval_argument(engine_state, stack, expr, spread)? { for arg in eval_argument(engine_state, stack, expr, spread)? {
let tilde_expanded = expand_tilde(&arg); match arg {
for glob_expanded in expand_glob(&tilde_expanded, &cwd, expr.span, ctrlc)? { // Expand globs passed to run-external
let inner_quotes_removed = remove_inner_quotes(&glob_expanded); Value::Glob { val, no_expand, .. } if !no_expand => args.extend(
args.push(inner_quotes_removed.into_owned().into_spanned(expr.span)); expand_glob(&val, &cwd, expr.span, ctrlc)?
.into_iter()
.map(|s| s.into_spanned(expr.span)),
),
other => {
args.push(OsString::from(coerce_into_string(other)?).into_spanned(expr.span))
} }
} }
} else {
for arg in eval_argument(engine_state, stack, expr, spread)? {
args.push(arg.into_spanned(expr.span));
}
} }
} }
Ok(args) Ok(args)
} }
/// Evaluates an expression, coercing the values to strings. /// Custom `coerce_into_string()`, including globs, since those are often args to `run-external`
/// /// as well
/// Note: The parser currently has a special hack that retains surrounding fn coerce_into_string(val: Value) -> Result<String, ShellError> {
/// quotes for string literals in `Expression`, so that we can decide whether match val {
/// the expression is considered a bare string. The hack doesn't affect string Value::Glob { val, .. } => Ok(val),
/// literals within lists or records. This function will remove the quotes _ => val.coerce_into_string(),
/// before evaluating the expression. }
}
/// Evaluate an argument, returning more than one value if it was a list to be spread.
fn eval_argument( fn eval_argument(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
expr: &Expression, expr: &Expression,
spread: bool, spread: bool,
) -> Result<Vec<String>, ShellError> { ) -> Result<Vec<Value>, ShellError> {
// Remove quotes from string literals.
let mut expr = expr.clone();
if let Expr::String(s) = &expr.expr {
expr.expr = Expr::String(remove_quotes(s).into());
}
let eval = get_eval_expression(engine_state); let eval = get_eval_expression(engine_state);
match eval(engine_state, stack, &expr)? { match eval(engine_state, stack, expr)? {
Value::List { vals, .. } => { Value::List { vals, .. } => {
if spread { if spread {
vals.into_iter() Ok(vals)
.map(|val| val.coerce_into_string())
.collect()
} else { } else {
Err(ShellError::CannotPassListToExternal { Err(ShellError::CannotPassListToExternal {
arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(), arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(),
@ -295,31 +274,12 @@ fn eval_argument(
if spread { if spread {
Err(ShellError::CannotSpreadAsList { span: expr.span }) Err(ShellError::CannotSpreadAsList { span: expr.span })
} else { } else {
Ok(vec![value.coerce_into_string()?]) Ok(vec![value])
} }
} }
} }
} }
/// Returns whether an expression is considered a bare string.
///
/// Bare strings are defined as string literals that are either unquoted or
/// quoted by backticks. Raw strings or string interpolations don't count.
fn is_bare_string(expr: &Expression) -> bool {
let Expr::String(s) = &expr.expr else {
return false;
};
let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"');
let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\'');
!quoted_by_double_quotes && !quoted_by_single_quotes
}
/// Performs tilde expansion on `arg`. Returns the original string if `arg`
/// doesn't start with tilde.
fn expand_tilde(arg: &str) -> String {
nu_path::expand_tilde(arg).to_string_lossy().to_string()
}
/// Performs glob expansion on `arg`. If the expansion found no matches or the pattern /// Performs glob expansion on `arg`. If the expansion found no matches or the pattern
/// is not a valid glob, then this returns the original string as the expansion result. /// is not a valid glob, then this returns the original string as the expansion result.
/// ///
@ -330,91 +290,65 @@ fn expand_glob(
cwd: &Path, cwd: &Path,
span: Span, span: Span,
interrupt: &Option<Arc<AtomicBool>>, interrupt: &Option<Arc<AtomicBool>>,
) -> Result<Vec<String>, ShellError> { ) -> Result<Vec<OsString>, ShellError> {
const GLOB_CHARS: &[char] = &['*', '?', '[']; const GLOB_CHARS: &[char] = &['*', '?', '['];
// Don't expand something that doesn't include the GLOB_CHARS // For an argument that doesn't include the GLOB_CHARS, just do the `expand_tilde`
// and `expand_ndots` expansion
if !arg.contains(GLOB_CHARS) { if !arg.contains(GLOB_CHARS) {
return Ok(vec![arg.into()]); let path = expand_ndots_safe(expand_tilde(arg));
return Ok(vec![path.into()]);
} }
// We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct // We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct
// dir // dir
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span); let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
let Ok((_prefix, paths)) = nu_engine::glob_from(&glob, cwd, span, None) else { if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
// If an error occurred, return the original input let mut result: Vec<OsString> = vec![];
return Ok(vec![arg.into()]);
};
// If the first component of the original `arg` string path was '.', that should be preserved for m in matches {
let relative_to_dot = Path::new(arg).starts_with("."); if nu_utils::ctrl_c::was_pressed(interrupt) {
return Err(ShellError::InterruptedByUser { span: Some(span) });
}
if let Ok(arg) = m {
let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd);
result.push(arg.into());
} else {
result.push(arg.into());
}
}
let paths = paths // FIXME: do we want to special-case this further? We might accidentally expand when they don't
// Skip over glob failures. These are usually just inaccessible paths. // intend to
.flat_map(|path_result| match path_result { if result.is_empty() {
Ok(path) => Some(path), result.push(arg.into());
Err(err) => {
// But internally log them just in case we need to debug this.
log::warn!("Error in run_external::expand_glob(): {}", err);
None
} }
})
// Make the paths relative to the cwd
.map(|path| {
path.strip_prefix(cwd)
.map(|path| path.to_owned())
.unwrap_or(path)
})
// Add './' to relative paths if the original pattern had it
.map(|path| {
if relative_to_dot && path.is_relative() {
Path::new(".").join(path)
} else {
path
}
})
// Convert the paths returned to UTF-8 strings.
//
// FIXME: this fails to return the correct results for non-UTF-8 paths, but we don't support
// those in Nushell yet.
.map(|path| path.to_string_lossy().into_owned())
// Abandon if ctrl-c is pressed
.map(|path| {
if !nu_utils::ctrl_c::was_pressed(interrupt) {
Ok(path)
} else {
Err(ShellError::InterruptedByUser { span: Some(span) })
}
})
.collect::<Result<Vec<String>, ShellError>>()?;
if !paths.is_empty() { Ok(result)
Ok(paths)
} else { } else {
// If we failed to match, return the original input
Ok(vec![arg.into()]) Ok(vec![arg.into()])
} }
} }
/// Transforms `--option="value"` into `--option=value`. `value` can be quoted fn resolve_globbed_path_to_cwd_relative(
/// with double quotes, single quotes, or backticks. Only removes the outermost path: PathBuf,
/// pair of quotes after the equal sign. prefix: Option<&PathBuf>,
fn remove_inner_quotes(arg: &str) -> Cow<'_, str> { cwd: &Path,
// Check that `arg` is a long option. ) -> PathBuf {
if !arg.starts_with("--") { if let Some(prefix) = prefix {
return Cow::Borrowed(arg); if let Ok(remainder) = path.strip_prefix(prefix) {
} let new_prefix = if let Some(pfx) = diff_paths(prefix, cwd) {
// Split `arg` on the first `=`. pfx
let Some((option, value)) = arg.split_once('=') else { } else {
return Cow::Borrowed(arg); prefix.to_path_buf()
}; };
// Check that `option` doesn't contain quotes. new_prefix.join(remainder)
if option.contains('"') || option.contains('\'') || option.contains('`') { } else {
return Cow::Borrowed(arg); path
}
} else {
path
} }
// Remove the outermost pair of quotes from `value`.
let value = remove_quotes(value);
Cow::Owned(format!("{option}={value}"))
} }
/// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is /// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is
@ -585,7 +519,7 @@ pub fn command_not_found(
/// Note: the `which.rs` crate always uses PATHEXT from the environment. As /// Note: the `which.rs` crate always uses PATHEXT from the environment. As
/// such, changing PATHEXT within Nushell doesn't work without updating the /// such, changing PATHEXT within Nushell doesn't work without updating the
/// actual environment of the Nushell process. /// actual environment of the Nushell process.
pub fn which(name: &str, paths: &str, cwd: &Path) -> Option<PathBuf> { pub fn which(name: impl AsRef<OsStr>, paths: &str, cwd: &Path) -> Option<PathBuf> {
#[cfg(windows)] #[cfg(windows)]
let paths = format!("{};{}", cwd.display(), paths); let paths = format!("{};{}", cwd.display(), paths);
which::which_in(name, Some(paths), cwd).ok() which::which_in(name, Some(paths), cwd).ok()
@ -601,17 +535,18 @@ fn is_cmd_internal_command(name: &str) -> bool {
} }
/// Returns true if a string contains CMD special characters. /// Returns true if a string contains CMD special characters.
#[cfg(windows)] fn has_cmd_special_character(s: impl AsRef<[u8]>) -> bool {
fn has_cmd_special_character(s: &str) -> bool { s.as_ref()
const SPECIAL_CHARS: &[char] = &['<', '>', '&', '|', '^']; .iter()
SPECIAL_CHARS.iter().any(|c| s.contains(*c)) .any(|b| matches!(b, b'<' | b'>' | b'&' | b'|' | b'^'))
} }
/// Escape an argument for CMD internal commands. The result can be safely passed to `raw_arg()`. /// Escape an argument for CMD internal commands. The result can be safely passed to `raw_arg()`.
#[cfg(windows)] #[cfg_attr(not(windows), allow(dead_code))]
fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError> { fn escape_cmd_argument(arg: &Spanned<OsString>) -> Result<Cow<'_, OsStr>, ShellError> {
let Spanned { item: arg, span } = arg; let Spanned { item: arg, span } = arg;
if arg.contains(['\r', '\n', '%']) { let bytes = arg.as_encoded_bytes();
if bytes.iter().any(|b| matches!(b, b'\r' | b'\n' | b'%')) {
// \r and \n trunacte the rest of the arguments and % can expand environment variables // \r and \n trunacte the rest of the arguments and % can expand environment variables
Err(ShellError::ExternalCommand { Err(ShellError::ExternalCommand {
label: label:
@ -620,12 +555,12 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
help: "some characters currently cannot be securely escaped".into(), help: "some characters currently cannot be securely escaped".into(),
span: *span, span: *span,
}) })
} else if arg.contains('"') { } else if bytes.contains(&b'"') {
// If `arg` is already quoted by double quotes, confirm there's no // If `arg` is already quoted by double quotes, confirm there's no
// embedded double quotes, then leave it as is. // embedded double quotes, then leave it as is.
if arg.chars().filter(|c| *c == '"').count() == 2 if bytes.iter().filter(|b| **b == b'"').count() == 2
&& arg.starts_with('"') && bytes.starts_with(b"\"")
&& arg.ends_with('"') && bytes.ends_with(b"\"")
{ {
Ok(Cow::Borrowed(arg)) Ok(Cow::Borrowed(arg))
} else { } else {
@ -636,75 +571,39 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
span: *span, span: *span,
}) })
} }
} else if arg.contains(' ') || has_cmd_special_character(arg) { } else if bytes.contains(&b' ') || has_cmd_special_character(bytes) {
// If `arg` contains space or special characters, quote the entire argument by double quotes. // If `arg` contains space or special characters, quote the entire argument by double quotes.
Ok(Cow::Owned(format!("\"{arg}\""))) let mut new_str = OsString::new();
new_str.push("\"");
new_str.push(arg);
new_str.push("\"");
Ok(Cow::Owned(new_str))
} else { } else {
// FIXME?: what if `arg.is_empty()`? // FIXME?: what if `arg.is_empty()`?
Ok(Cow::Borrowed(arg)) Ok(Cow::Borrowed(arg))
} }
} }
/// Expand ndots, but only if it looks like it probably contains them, because there is some lossy
/// path normalization that happens.
fn expand_ndots_safe(path: impl AsRef<Path>) -> PathBuf {
let string = path.as_ref().to_string_lossy();
// Use ndots if it contains at least `...`, since that's the minimum trigger point, and don't
// use it if it contains ://, because that looks like a URL scheme and the path normalization
// will mess with that.
if string.contains("...") && !string.contains("://") {
expand_ndots(path)
} else {
path.as_ref().to_owned()
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use nu_protocol::ast::ListItem;
use nu_test_support::{fs::Stub, playground::Playground}; use nu_test_support::{fs::Stub, playground::Playground};
#[test]
fn test_remove_quotes() {
assert_eq!(remove_quotes(r#""#), r#""#);
assert_eq!(remove_quotes(r#"'"#), r#"'"#);
assert_eq!(remove_quotes(r#"''"#), r#""#);
assert_eq!(remove_quotes(r#""foo""#), r#"foo"#);
assert_eq!(remove_quotes(r#"`foo '"' bar`"#), r#"foo '"' bar"#);
assert_eq!(remove_quotes(r#"'foo' bar"#), r#"'foo' bar"#);
assert_eq!(remove_quotes(r#"r#'foo'#"#), r#"r#'foo'#"#);
}
#[test]
fn test_eval_argument() {
fn expression(expr: Expr) -> Expression {
Expression::new_unknown(expr, Span::unknown(), Type::Any)
}
fn eval(expr: Expr, spread: bool) -> Result<Vec<String>, ShellError> {
let engine_state = EngineState::new();
let mut stack = Stack::new();
eval_argument(&engine_state, &mut stack, &expression(expr), spread)
}
let actual = eval(Expr::String("".into()), false).unwrap();
let expected = &[""];
assert_eq!(actual, expected);
let actual = eval(Expr::String("'foo'".into()), false).unwrap();
let expected = &["foo"];
assert_eq!(actual, expected);
let actual = eval(Expr::RawString("'foo'".into()), false).unwrap();
let expected = &["'foo'"];
assert_eq!(actual, expected);
let actual = eval(Expr::List(vec![]), true).unwrap();
let expected: &[&str] = &[];
assert_eq!(actual, expected);
let actual = eval(
Expr::List(vec![
ListItem::Item(expression(Expr::String("'foo'".into()))),
ListItem::Item(expression(Expr::String("bar".into()))),
]),
true,
)
.unwrap();
let expected = &["'foo'", "bar"];
assert_eq!(actual, expected);
eval(Expr::String("".into()), true).unwrap_err();
eval(Expr::List(vec![]), false).unwrap_err();
}
#[test] #[test]
fn test_expand_glob() { fn test_expand_glob() {
Playground::setup("test_expand_glob", |dirs, play| { Playground::setup("test_expand_glob", |dirs, play| {
@ -717,10 +616,6 @@ mod test {
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap();
let expected = vec![
Path::new(".").join("a.txt").to_string_lossy().into_owned(),
Path::new(".").join("b.txt").to_string_lossy().into_owned(),
];
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap();
@ -738,28 +633,14 @@ mod test {
let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap();
let expected = &["[*.txt"]; let expected = &["[*.txt"];
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &None).unwrap();
let home = dirs_next::home_dir().expect("failed to get home dir");
let expected: Vec<OsString> = vec![home.join("foo.txt").into()];
assert_eq!(actual, expected);
}) })
} }
#[test]
fn test_remove_inner_quotes() {
let actual = remove_inner_quotes(r#"--option=value"#);
let expected = r#"--option=value"#;
assert_eq!(actual, expected);
let actual = remove_inner_quotes(r#"--option="value""#);
let expected = r#"--option=value"#;
assert_eq!(actual, expected);
let actual = remove_inner_quotes(r#"--option='value'"#);
let expected = r#"--option=value"#;
assert_eq!(actual, expected);
let actual = remove_inner_quotes(r#"--option "value""#);
let expected = r#"--option "value""#;
assert_eq!(actual, expected);
}
#[test] #[test]
fn test_write_pipeline_data() { fn test_write_pipeline_data() {
let engine_state = EngineState::new(); let engine_state = EngineState::new();

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL};
#[derive(Clone)] #[derive(Clone)]
pub struct SysCpu; pub struct SysCpu;
@ -26,7 +28,7 @@ impl Command for SysCpu {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::cpu(call.head).into_pipeline_data()) Ok(cpu(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +39,42 @@ impl Command for SysCpu {
}] }]
} }
} }
fn cpu(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
let cpus = sys
.cpus()
.iter()
.map(|cpu| {
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
let load_avg = System::load_average();
let load_avg = format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
);
let record = record! {
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"cpu_usage" => Value::float(rounded_usage.into(), span),
"load_average" => Value::string(load_avg, span),
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
};
Value::record(record, span)
})
.collect();
Value::list(cpus, span)
}

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::Disks;
#[derive(Clone)] #[derive(Clone)]
pub struct SysDisks; pub struct SysDisks;
@ -26,7 +28,7 @@ impl Command for SysDisks {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::disks(call.head).into_pipeline_data()) Ok(disks(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +39,27 @@ impl Command for SysDisks {
}] }]
} }
} }
fn disks(span: Span) -> Value {
let disks = Disks::new_with_refreshed_list()
.iter()
.map(|disk| {
let device = trim_cstyle_null(disk.name().to_string_lossy());
let typ = trim_cstyle_null(disk.file_system().to_string_lossy());
let record = record! {
"device" => Value::string(device, span),
"type" => Value::string(typ, span),
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
"total" => Value::filesize(disk.total_space() as i64, span),
"free" => Value::filesize(disk.available_space() as i64, span),
"removable" => Value::bool(disk.is_removable(), span),
"kind" => Value::string(disk.kind().to_string(), span),
};
Value::record(record, span)
})
.collect();
Value::list(disks, span)
}

View File

@ -1,4 +1,7 @@
use super::trim_cstyle_null;
use chrono::{DateTime, FixedOffset, Local};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::System;
#[derive(Clone)] #[derive(Clone)]
pub struct SysHost; pub struct SysHost;
@ -26,8 +29,7 @@ impl Command for SysHost {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let host = super::host(call.head); Ok(host(call.head).into_pipeline_data())
Ok(Value::record(host, call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -38,3 +40,53 @@ impl Command for SysHost {
}] }]
} }
} }
fn host(span: Span) -> Value {
let mut record = Record::new();
if let Some(name) = System::name() {
record.push("name", Value::string(trim_cstyle_null(name), span));
}
if let Some(version) = System::os_version() {
record.push("os_version", Value::string(trim_cstyle_null(version), span));
}
if let Some(long_version) = System::long_os_version() {
record.push(
"long_os_version",
Value::string(trim_cstyle_null(long_version), span),
);
}
if let Some(version) = System::kernel_version() {
record.push(
"kernel_version",
Value::string(trim_cstyle_null(version), span),
);
}
if let Some(hostname) = System::host_name() {
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
}
let uptime = System::uptime()
.saturating_mul(1_000_000_000)
.try_into()
.unwrap_or(i64::MAX);
record.push("uptime", Value::duration(uptime, span));
let boot_time = boot_time()
.map(|time| Value::date(time, span))
.unwrap_or(Value::nothing(span));
record.push("boot_time", boot_time);
Value::record(record, span)
}
fn boot_time() -> Option<DateTime<FixedOffset>> {
// Broken systems can apparently return really high values.
// See: https://github.com/nushell/nushell/issues/10155
// First, try to convert u64 to i64, and then try to create a `DateTime`.
let secs = System::boot_time().try_into().ok()?;
let time = DateTime::from_timestamp(secs, 0)?;
Some(time.with_timezone(&Local).fixed_offset())
}

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::System;
#[derive(Clone)] #[derive(Clone)]
pub struct SysMem; pub struct SysMem;
@ -26,7 +27,7 @@ impl Command for SysMem {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::mem(call.head).into_pipeline_data()) Ok(mem(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +38,20 @@ impl Command for SysMem {
}] }]
} }
} }
fn mem(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_memory();
let record = record! {
"total" => Value::filesize(sys.total_memory() as i64, span),
"free" => Value::filesize(sys.free_memory() as i64, span),
"used" => Value::filesize(sys.used_memory() as i64, span),
"available" => Value::filesize(sys.available_memory() as i64, span),
"swap total" => Value::filesize(sys.total_swap() as i64, span),
"swap free" => Value::filesize(sys.free_swap() as i64, span),
"swap used" => Value::filesize(sys.used_swap() as i64, span),
};
Value::record(record, span)
}

Some files were not shown because too many files have changed in this diff Show More