Merge main into sort-custom
This commit is contained in:
commit
f940edbee2
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
# Prevent sudden announcement of a new advisory from failing ci:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: rustsec/audit-check@v1.4.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
@ -66,7 +66,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
@ -95,7 +95,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
@ -146,7 +146,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
|
20
.github/workflows/nightly-build.yml
vendored
20
.github/workflows/nightly-build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
# if: github.repository == 'nushell/nightly'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
ref: main
|
||||
|
@ -36,10 +36,10 @@ jobs:
|
|||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.11
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.93.0
|
||||
version: 0.95.0
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
|
@ -112,7 +112,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
@ -128,9 +128,9 @@ jobs:
|
|||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.11
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
with:
|
||||
version: 0.93.0
|
||||
version: 0.95.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
|
@ -161,7 +161,7 @@ jobs:
|
|||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
# Create a release only in nushell/nightly repo
|
||||
- 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') }}
|
||||
with:
|
||||
prerelease: true
|
||||
|
@ -181,14 +181,14 @@ jobs:
|
|||
- name: Waiting for Release
|
||||
run: sleep 1800
|
||||
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.11
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
with:
|
||||
version: 0.93.0
|
||||
version: 0.95.0
|
||||
|
||||
# Keep the last a few releases
|
||||
- name: Delete Older Releases
|
||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -62,7 +62,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Update Rust Toolchain Target
|
||||
run: |
|
||||
|
@ -76,9 +76,9 @@ jobs:
|
|||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.11
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
with:
|
||||
version: 0.93.0
|
||||
version: 0.95.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
|
@ -91,7 +91,7 @@ jobs:
|
|||
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
- 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/') }}
|
||||
with:
|
||||
draft: true
|
||||
|
|
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.22.7
|
||||
uses: crate-ci/typos@v1.22.9
|
||||
|
|
366
Cargo.lock
generated
366
Cargo.lock
generated
|
@ -1117,6 +1117,36 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl"
|
||||
version = "0.4.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6"
|
||||
dependencies = [
|
||||
"curl-sys",
|
||||
"libc",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"socket2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl-sys"
|
||||
version = "0.4.73+curl-8.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "450ab250ecf17227c39afb9a2dd9261dc0035cb80f2612472fc0c4aac2dcb84d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
|
@ -1148,6 +1178,12 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.11.0"
|
||||
|
@ -1227,6 +1263,12 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "doctest-file"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
|
@ -1324,6 +1366,16 @@ dependencies = [
|
|||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
|
@ -1334,6 +1386,19 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
@ -1848,12 +1913,26 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
|||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"markup5ever 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
|
@ -1900,6 +1979,12 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.28"
|
||||
|
@ -1989,12 +2074,6 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
|
@ -2026,10 +2105,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4d0250d41da118226e55b3d50ca3f0d9e0a0f6829b92f543ac0054aeea1572"
|
||||
checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3"
|
||||
dependencies = [
|
||||
"doctest-file",
|
||||
"libc",
|
||||
"recvmsg",
|
||||
"widestring",
|
||||
|
@ -2516,6 +2596,32 @@ dependencies = [
|
|||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.11.2",
|
||||
"phf_codegen 0.11.2",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever_rcdom"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18"
|
||||
dependencies = [
|
||||
"html5ever 0.27.0",
|
||||
"markup5ever 0.12.1",
|
||||
"tendril",
|
||||
"xml5ever",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
@ -2762,7 +2868,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"crossterm",
|
||||
|
@ -2815,7 +2921,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
|
@ -2850,7 +2956,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"miette",
|
||||
|
@ -2862,7 +2968,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"heck 0.5.0",
|
||||
|
@ -2887,7 +2993,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2899,7 +3005,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-cmd-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"nu-engine",
|
||||
|
@ -2910,7 +3016,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
|
@ -2922,7 +3028,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
|
@ -2937,6 +3043,7 @@ dependencies = [
|
|||
"chrono-tz 0.8.6",
|
||||
"crossterm",
|
||||
"csv",
|
||||
"deunicode",
|
||||
"dialoguer",
|
||||
"digest",
|
||||
"dirs-next",
|
||||
|
@ -3031,7 +3138,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-derive-value"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro-error",
|
||||
|
@ -3042,7 +3149,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
|
@ -3052,7 +3159,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"anyhow",
|
||||
|
@ -3077,14 +3184,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"num-traits",
|
||||
|
@ -3094,7 +3201,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-lsp"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"crossbeam-channel",
|
||||
|
@ -3115,7 +3222,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
|
@ -3131,7 +3238,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"omnipath",
|
||||
|
@ -3140,7 +3247,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
|
@ -3155,7 +3262,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-core"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"log",
|
||||
|
@ -3169,7 +3276,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nu-engine",
|
||||
|
@ -3184,7 +3291,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nu-protocol",
|
||||
|
@ -3196,7 +3303,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-cmd-lang",
|
||||
|
@ -3214,7 +3321,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
|
@ -3223,7 +3330,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"byte-unit",
|
||||
|
@ -3256,7 +3363,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
|
@ -3267,7 +3374,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.12.1",
|
||||
|
@ -3285,7 +3392,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"nu-ansi-term",
|
||||
|
@ -3299,7 +3406,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
|
@ -3307,7 +3414,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
|
@ -3319,7 +3426,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
|
@ -3345,7 +3452,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-cmd-lang",
|
||||
"nu-plugin",
|
||||
|
@ -3355,7 +3462,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"eml-parser",
|
||||
"ical",
|
||||
|
@ -3368,7 +3475,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
|
@ -3377,7 +3484,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
|
@ -3386,12 +3493,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_polars"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz 0.9.0",
|
||||
"env_logger 0.11.3",
|
||||
"fancy-regex",
|
||||
"indexmap",
|
||||
"log",
|
||||
"mimalloc",
|
||||
"nu-cmd-lang",
|
||||
"nu-command",
|
||||
|
@ -3401,6 +3510,7 @@ dependencies = [
|
|||
"nu-plugin",
|
||||
"nu-plugin-test-support",
|
||||
"nu-protocol",
|
||||
"nu-utils",
|
||||
"num",
|
||||
"polars",
|
||||
"polars-arrow",
|
||||
|
@ -3409,7 +3519,7 @@ dependencies = [
|
|||
"polars-plan",
|
||||
"polars-utils",
|
||||
"serde",
|
||||
"sqlparser 0.47.0",
|
||||
"sqlparser",
|
||||
"tempfile",
|
||||
"typetag",
|
||||
"uuid",
|
||||
|
@ -3417,19 +3527,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"scraper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sxd-document",
|
||||
"sxd-xpath",
|
||||
"webpage",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_stress_internals"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"interprocess",
|
||||
"serde",
|
||||
|
@ -3555,7 +3668,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "nuon"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
|
@ -4019,9 +4132,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e148396dca5496566880fa19374f3f789a29db94e3eb458afac1497b4bac5442"
|
||||
checksum = "ce49e10a756f68eb99c102c6b2a0cbc0c583a0fa7263536ad0913d94be878d2d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"polars-arrow",
|
||||
|
@ -4039,9 +4152,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-arrow"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cb5e11cd0752ae022fa6ca3afa50a14b0301b7ce53c0135828fbb0f4fa8303e"
|
||||
checksum = "b436f83f62e864f0d91871e26528f2c5552c7cf07c8d77547f1b8e3fde22bd27"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"atoi",
|
||||
|
@ -4087,9 +4200,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-compute"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89fc4578f826234cdecb782952aa9c479dc49373f81694a7b439c70b6f609ba0"
|
||||
checksum = "f6758f834f07e622a2f859bebb542b2b7f8879b8704dbb2b2bbab460ddcdca4b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"either",
|
||||
|
@ -4103,9 +4216,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-core"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e490c6bace1366a558feea33d1846f749a8ca90bd72a6748752bc65bb4710b2a"
|
||||
checksum = "7ed262e9bdda15a12a9bfcfc9200bec5253335633dbd86cf5b94fda0194244b3"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.5.0",
|
||||
|
@ -4137,9 +4250,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-error"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08888f58e61599b00f5ea0c2ccdc796b54b9859559cc0d4582733509451fa01a"
|
||||
checksum = "53e1707a17475ba5e74c349154b415e3148a1a275e395965427971b5e53ad621"
|
||||
dependencies = [
|
||||
"avro-schema",
|
||||
"polars-arrow-format",
|
||||
|
@ -4150,9 +4263,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-expr"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4173591920fe56ad55af025f92eb0d08421ca85705c326a640c43856094e3484"
|
||||
checksum = "31a9688d5842e7a7fbad88e67a174778794a91d97d3bba1b3c09dd1656fee3b2"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.5.0",
|
||||
|
@ -4170,9 +4283,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-io"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5842896aea46d975b425d63f156f412aed3cfde4c257b64fb1f43ceea288074e"
|
||||
checksum = "18798dacd94fb9263f65f63f0feab0908675422646d6f7fc37043b85ff6dca35"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"async-trait",
|
||||
|
@ -4211,9 +4324,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-json"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "160cbad0145b93ac6a88639aadfa6f7d7c769d05a8674f9b7e895b398cae9901"
|
||||
checksum = "044ea319f667efbf8007c4c38171c2956e0e7f9b078eb66e31e82f80d1e14b51"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"chrono",
|
||||
|
@ -4232,19 +4345,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-lazy"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e805ea2ebbc6b7749b0afb31b7fc5d32b42b57ba29b984549d43d3a16114c4a5"
|
||||
checksum = "74a11994c2211f2e99d9ac31776fd7c2c0607d5fe62d5b5db9e396f7d663f3d5"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.5.0",
|
||||
"glob",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"polars-arrow",
|
||||
"polars-core",
|
||||
"polars-expr",
|
||||
"polars-io",
|
||||
"polars-json",
|
||||
"polars-mem-engine",
|
||||
"polars-ops",
|
||||
"polars-pipe",
|
||||
"polars-plan",
|
||||
|
@ -4256,10 +4371,29 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-ops"
|
||||
version = "0.40.0"
|
||||
name = "polars-mem-engine"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0aed7e169c81b98457641cf82b251f52239a668916c2e683abd1f38df00d58"
|
||||
checksum = "5acd5fde6fadaddfcae3227ec5b64121007928f8e68870c80653438e20c1c587"
|
||||
dependencies = [
|
||||
"polars-arrow",
|
||||
"polars-core",
|
||||
"polars-error",
|
||||
"polars-expr",
|
||||
"polars-io",
|
||||
"polars-json",
|
||||
"polars-ops",
|
||||
"polars-plan",
|
||||
"polars-time",
|
||||
"polars-utils",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-ops"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4170c59e974727941edfb722f6d430ed623be9e7f30581ee00832c907f1b9fd"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"argminmax",
|
||||
|
@ -4293,9 +4427,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-parquet"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c70670a9e51cac66d0e77fd20b5cc957dbcf9f2660d410633862bb72f846d5b8"
|
||||
checksum = "c684638c36c60c691d707d414249fe8af4a19a35a39d418464b140fe23732e5d"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"async-stream",
|
||||
|
@ -4308,9 +4442,11 @@ dependencies = [
|
|||
"num-traits",
|
||||
"parquet-format-safe",
|
||||
"polars-arrow",
|
||||
"polars-compute",
|
||||
"polars-error",
|
||||
"polars-utils",
|
||||
"seq-macro",
|
||||
"serde",
|
||||
"simdutf8",
|
||||
"snap",
|
||||
"streaming-decompression",
|
||||
|
@ -4319,9 +4455,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-pipe"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a40ae1b3c74ee07e2d1f7cbf56c5d6e15969e45d9b6f0903bd2acaf783ba436"
|
||||
checksum = "832af9fbebc4c074d95fb19e1ef9e1bf37c343641238c2476febff296a7028ea"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-queue",
|
||||
|
@ -4345,9 +4481,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-plan"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daa3541ae7e9af311a4389bc2b21f83349c34c723cc67fa524cdefdaa172d90"
|
||||
checksum = "801390ea815c05c9cf8337f3148090c9c10c9595a839fa0706b77cc2405b4466"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
|
@ -4375,9 +4511,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-row"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deb285f2f3a65b00dd06bef16bb9f712dbb5478f941dab5cf74f9f016d382e40"
|
||||
checksum = "dee955e91b605fc91db4d0a8ea02609d3a09ff79256d905214a2a6f758cd6f7b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"polars-arrow",
|
||||
|
@ -4387,9 +4523,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-sql"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a724f699d194cb02c25124d3832f7d4d77f387f1a89ee42f6b9e88ec561d4ad9"
|
||||
checksum = "d89c00a4b399501d5bd478e8e8022b9391047fe8570324ecba20c4e4833c0e87"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"once_cell",
|
||||
|
@ -4397,18 +4533,20 @@ dependencies = [
|
|||
"polars-core",
|
||||
"polars-error",
|
||||
"polars-lazy",
|
||||
"polars-ops",
|
||||
"polars-plan",
|
||||
"polars-time",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlparser 0.39.0",
|
||||
"sqlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-time"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ebec238d8b6200d9f0c3ce411c8441e950bd5a7df7806b8172d06c1d5a4b97"
|
||||
checksum = "9689b3aff99d64befe300495528bdc44c36d2656c3a8b242a790d4f43df027fc"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"bytemuck",
|
||||
|
@ -4428,9 +4566,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-utils"
|
||||
version = "0.40.0"
|
||||
version = "0.41.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e1a907c63abf71e5f21467e2e4ff748896c28196746f631c6c25512ec6102c"
|
||||
checksum = "12081e346983a91e26f395597e1d53dea1b4ecd694653aee1cc402d2fae01f04"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
|
@ -4666,7 +4804,7 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"env_logger 0.8.4",
|
||||
"log",
|
||||
"rand",
|
||||
]
|
||||
|
@ -4745,21 +4883,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.26.2"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
|
||||
checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"itertools 0.12.1",
|
||||
"lru",
|
||||
"paste",
|
||||
"stability",
|
||||
"strum",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
|
@ -5218,7 +5356,7 @@ dependencies = [
|
|||
"ahash 0.8.11",
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
"html5ever",
|
||||
"html5ever 0.26.0",
|
||||
"once_cell",
|
||||
"selectors",
|
||||
"tendril",
|
||||
|
@ -5434,9 +5572,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.28.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d75516bdaee8f640543ad1f6e292448c23ce57143f812c3736ab4b0874383df"
|
||||
checksum = "0a600f795d0894cda22235b44eea4b85c2a35b405f65523645ac8e35b306817a"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
|
@ -5581,15 +5719,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.47.0"
|
||||
|
@ -6307,6 +6436,16 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226"
|
||||
dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.12"
|
||||
|
@ -6486,9 +6625,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60"
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
|
@ -6731,6 +6870,20 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpage"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac"
|
||||
dependencies = [
|
||||
"curl",
|
||||
"html5ever 0.27.0",
|
||||
"markup5ever_rcdom",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "6.0.1"
|
||||
|
@ -7143,6 +7296,17 @@ dependencies = [
|
|||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml5ever"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.10"
|
||||
|
|
47
Cargo.toml
47
Cargo.toml
|
@ -11,7 +11,7 @@ license = "MIT"
|
|||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
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
|
||||
|
||||
|
@ -80,6 +80,7 @@ crossbeam-channel = "0.5.8"
|
|||
crossterm = "0.27"
|
||||
csv = "1.3"
|
||||
ctrlc = "3.4"
|
||||
deunicode = "1.6.0"
|
||||
dialoguer = { default-features = false, version = "0.11" }
|
||||
digest = { default-features = false, version = "0.10" }
|
||||
dirs-next = "2.0"
|
||||
|
@ -95,7 +96,7 @@ heck = "0.5.0"
|
|||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.2"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.1.0"
|
||||
interprocess = "2.2.0"
|
||||
is_executable = "1.0"
|
||||
itertools = "0.12"
|
||||
libc = "0.2"
|
||||
|
@ -172,7 +173,7 @@ uu_mv = "0.0.26"
|
|||
uu_whoami = "0.0.26"
|
||||
uu_uname = "0.0.26"
|
||||
uucore = "0.0.26"
|
||||
uuid = "1.8.0"
|
||||
uuid = "1.9.1"
|
||||
v_htmlescape = "0.15.0"
|
||||
wax = "0.6"
|
||||
which = "6.0.0"
|
||||
|
@ -180,22 +181,22 @@ windows = "0.54"
|
|||
winreg = "0.52"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.3" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.3", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.3" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.94.3" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.94.3" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.94.3" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.3" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.3" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.94.3" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.94.3" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.94.3" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.94.3" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.95.1" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.95.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.95.1" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.95.1" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.95.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.95.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.95.1" }
|
||||
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -225,9 +226,9 @@ nix = { workspace = true, default-features = false, features = [
|
|||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.3" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", 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.95.1" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" }
|
||||
assert_cmd = "2.0"
|
||||
dirs-next = { workspace = true }
|
||||
tango-bench = "0.5"
|
||||
|
@ -310,4 +311,4 @@ bench = false
|
|||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
harness = false
|
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
|
@ -46,4 +46,4 @@ which = { workspace = true }
|
|||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
|||
report_error_new, HistoryFileFormat, PipelineData,
|
||||
};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_utils::utils::perf;
|
||||
use nu_utils::perf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -53,13 +53,10 @@ pub fn read_plugin_file(
|
|||
// Reading signatures from plugin registry file
|
||||
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
||||
perf(
|
||||
perf!(
|
||||
"add plugin file to engine_state",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
|
@ -137,13 +134,10 @@ pub fn read_plugin_file(
|
|||
}
|
||||
};
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
&format!("read plugin file {}", plugin_path.display()),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
|
@ -156,13 +150,10 @@ pub fn read_plugin_file(
|
|||
return;
|
||||
}
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
&format!("load plugin file {}", plugin_path.display()),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +335,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||
name: identity.name().to_owned(),
|
||||
filename: identity.filename().to_owned(),
|
||||
shell: identity.shell().map(|p| p.to_owned()),
|
||||
data: PluginRegistryItemData::Valid { commands },
|
||||
data: PluginRegistryItemData::Valid {
|
||||
metadata: Default::default(),
|
||||
commands,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -378,13 +372,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||
);
|
||||
}
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"migrate old plugin file",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
true
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use nu_protocol::{
|
|||
};
|
||||
use nu_utils::{
|
||||
filesystem::{have_permission, PermissionResult},
|
||||
utils::perf,
|
||||
perf,
|
||||
};
|
||||
use reedline::{
|
||||
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
||||
|
@ -89,14 +89,7 @@ pub fn evaluate_repl(
|
|||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
||||
report_error_new(engine_state, &e);
|
||||
}
|
||||
perf(
|
||||
"translate env vars",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("translate env vars", start_time, use_color);
|
||||
|
||||
// seed env vars
|
||||
unique_stack.add_env_var(
|
||||
|
@ -225,28 +218,14 @@ fn get_line_editor(
|
|||
|
||||
// Now that reedline is created, get the history session id and store it in engine_state
|
||||
store_history_id_in_engine(engine_state, &line_editor);
|
||||
perf(
|
||||
"setup reedline",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("setup reedline", start_time, use_color);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
||||
|
||||
perf(
|
||||
"setup history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("setup history", start_time, use_color);
|
||||
}
|
||||
Ok(line_editor)
|
||||
}
|
||||
|
@ -289,28 +268,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
perf(
|
||||
"merge env",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("merge env", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
perf(
|
||||
"reset ctrlc",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("reset ctrlc", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Right before we start our prompt and take input from the user,
|
||||
|
@ -320,14 +285,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
"pre-prompt hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("pre-prompt hook", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Next, check all the environment variables they ask for
|
||||
|
@ -336,14 +294,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
perf(
|
||||
"env-change hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("env-change hook", start_time, use_color);
|
||||
|
||||
let engine_reference = Arc::new(engine_state.clone());
|
||||
let config = engine_state.get_config();
|
||||
|
@ -355,14 +306,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||
};
|
||||
perf(
|
||||
"get config/cursor config",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("get config/cursor config", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// at this line we have cloned the state for the completer and the transient prompt
|
||||
|
@ -394,14 +338,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
.with_ansi_colors(config.use_ansi_coloring)
|
||||
.with_cursor_config(cursor_config);
|
||||
|
||||
perf(
|
||||
"reedline builder",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("reedline builder", start_time, use_color);
|
||||
|
||||
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
||||
|
||||
|
@ -416,14 +353,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
line_editor.disable_hints()
|
||||
};
|
||||
|
||||
perf(
|
||||
"reedline coloring/style_computer",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("reedline coloring/style_computer", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
trace!("adding menus");
|
||||
|
@ -433,14 +363,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
Reedline::create()
|
||||
});
|
||||
|
||||
perf(
|
||||
"reedline adding menus",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("reedline adding menus", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let buffer_editor = get_editor(engine_state, &stack_arc, Span::unknown());
|
||||
|
@ -457,14 +380,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
line_editor
|
||||
};
|
||||
|
||||
perf(
|
||||
"reedline buffer_editor",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("reedline buffer_editor", start_time, use_color);
|
||||
|
||||
if let Some(history) = engine_state.history_config() {
|
||||
start_time = std::time::Instant::now();
|
||||
|
@ -474,28 +390,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
}
|
||||
}
|
||||
|
||||
perf(
|
||||
"sync_history",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("sync_history", start_time, use_color);
|
||||
}
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = setup_keybindings(engine_state, line_editor);
|
||||
|
||||
perf(
|
||||
"keybindings",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("keybindings", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
let config = &engine_state.get_config().clone();
|
||||
|
@ -512,14 +414,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
nu_prompt,
|
||||
);
|
||||
|
||||
perf(
|
||||
"update_prompt",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("update_prompt", start_time, use_color);
|
||||
|
||||
*entry_num += 1;
|
||||
|
||||
|
@ -546,14 +441,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
// so we should avoid it or making stack cheaper to clone.
|
||||
let mut stack = Arc::unwrap_or_clone(stack_arc);
|
||||
|
||||
perf(
|
||||
"line_editor setup",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("line_editor setup", start_time, use_color);
|
||||
|
||||
let line_editor_input_time = std::time::Instant::now();
|
||||
match input {
|
||||
|
@ -590,14 +478,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
}
|
||||
}
|
||||
|
||||
perf(
|
||||
"pre_execution_hook",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("pre_execution_hook", start_time, use_color);
|
||||
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.cursor_pos = line_editor.current_insertion_point();
|
||||
|
@ -612,26 +493,20 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
|
||||
run_ansi_sequence(VSCODE_PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"pre_execute_marker (633;C) ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
} else if shell_integration_osc133 {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"pre_execute_marker (133;C) ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
} else if shell_integration_osc133 {
|
||||
|
@ -639,13 +514,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
|
||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"pre_execute_marker (133;C) ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -769,22 +641,16 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
perf!(
|
||||
"processing line editor input",
|
||||
line_editor_input_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"time between prompts in line editor loop",
|
||||
loop_start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
|
||||
(true, stack, line_editor)
|
||||
|
@ -1061,14 +927,7 @@ fn run_shell_integration_osc2(
|
|||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
|
||||
|
||||
perf(
|
||||
"set title with command osc2",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
perf!("set title with command osc2", start_time, use_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1093,13 +952,10 @@ fn run_shell_integration_osc7(
|
|||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"communicate path to terminal with osc7",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1116,13 +972,10 @@ fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, u
|
|||
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"communicate path to terminal with osc9;9",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1142,13 +995,10 @@ fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, u
|
|||
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"communicate path to terminal with osc633;P",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1371,13 +1221,10 @@ fn run_finaliziation_ansi_sequence(
|
|||
shell_integration_osc133,
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"post_execute_marker (633;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
} else if shell_integration_osc133 {
|
||||
let start_time = Instant::now();
|
||||
|
@ -1389,13 +1236,10 @@ fn run_finaliziation_ansi_sequence(
|
|||
shell_integration_osc133,
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
} else if shell_integration_osc133 {
|
||||
|
@ -1408,13 +1252,10 @@ fn run_finaliziation_ansi_sequence(
|
|||
shell_integration_osc133,
|
||||
));
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
use_color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ impl Highlighter for NuHighlighter {
|
|||
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
|
||||
add_colored_token(&shape.1, next_token)
|
||||
|
@ -452,15 +453,17 @@ fn find_matching_block_end_in_expr(
|
|||
}
|
||||
}
|
||||
|
||||
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}),
|
||||
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
|
||||
exprs.iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
Expr::List(list) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
|
|
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
|||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use nu_utils::utils::perf;
|
||||
use nu_utils::perf;
|
||||
use std::path::Path;
|
||||
|
||||
// This will collect environment variables from std::env and adds them to a stack.
|
||||
|
@ -228,13 +228,10 @@ pub fn eval_source(
|
|||
let _ = enable_vt_processing();
|
||||
}
|
||||
|
||||
perf(
|
||||
perf!(
|
||||
&format!("eval_source {}", &fname),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
|
||||
exit_code
|
||||
|
|
|
@ -763,7 +763,7 @@ fn variables_completions() {
|
|||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(17, suggestions.len());
|
||||
assert_eq!(18, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"cache-dir".into(),
|
||||
|
@ -783,6 +783,7 @@ fn variables_completions() {
|
|||
"plugin-path".into(),
|
||||
"startup-time".into(),
|
||||
"temp-path".into(),
|
||||
"vendor-autoload-dir".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
|
|
|
@ -5,17 +5,17 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "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
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "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
|
||||
|
||||
|
@ -13,13 +13,13 @@ version = "0.94.3"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-json = { version = "0.94.3", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-pretty-hex = { version = "0.94.3", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-json = { version = "0.95.1", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
|
@ -33,6 +33,6 @@ v_htmlescape = { workspace = true }
|
|||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -6,26 +6,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.28", default-features = false }
|
||||
shadow-rs = { version = "0.29", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.28", default-features = false }
|
||||
shadow-rs = { version = "0.29", default-features = false }
|
||||
|
||||
[features]
|
||||
mimalloc = []
|
||||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
system-clipboard = []
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Break;
|
||||
|
@ -18,6 +19,15 @@ impl Command for Break {
|
|||
.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(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Continue;
|
||||
|
@ -18,6 +19,14 @@ impl Command for Continue {
|
|||
.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(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
|
|
@ -23,11 +23,7 @@ impl Command for Do {
|
|||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("do")
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Closure(None), SyntaxShape::Any]),
|
||||
"The closure to run.",
|
||||
)
|
||||
.required("closure", SyntaxShape::Closure(None), "The closure to run.")
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
|
@ -229,14 +225,24 @@ impl Command for Do {
|
|||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with a positional parameter",
|
||||
example: r#"do {|x| 100 + $x } 77"#,
|
||||
description: "Run the closure with a positional, type-checked parameter",
|
||||
example: r#"do {|x:int| 100 + $x } 77"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with input",
|
||||
example: r#"77 | do {|x| 100 + $in }"#,
|
||||
result: None, // TODO: returns 177
|
||||
description: "Run the closure with pipeline input",
|
||||
example: r#"77 | do { 100 + $in }"#,
|
||||
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 {
|
||||
description: "Run the closure and keep changes to the environment",
|
||||
|
|
|
@ -2,7 +2,7 @@ use nu_engine::{
|
|||
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::StateWorkingSet,
|
||||
engine::{CommandType, StateWorkingSet},
|
||||
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
|
||||
};
|
||||
|
||||
|
@ -41,6 +41,15 @@ impl Command for If {
|
|||
.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 {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Loop;
|
||||
|
@ -20,6 +21,15 @@ impl Command for Loop {
|
|||
.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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use nu_engine::{
|
||||
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)]
|
||||
pub struct Match;
|
||||
|
@ -27,6 +27,15 @@ impl Command for Match {
|
|||
.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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Scope;
|
||||
|
@ -20,10 +19,6 @@ impl Command for Scope {
|
|||
"Commands for getting info about what is in scope."
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
|
||||
use nu_protocol::engine::Closure;
|
||||
use nu_protocol::engine::{Closure, CommandType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Try;
|
||||
|
@ -31,6 +31,15 @@ impl Command for Try {
|
|||
.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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
|||
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
|
||||
.plugins()
|
||||
.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<_>>();
|
||||
|
||||
record.push(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct While;
|
||||
|
@ -29,6 +30,15 @@ impl Command for While {
|
|||
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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -5,16 +5,16 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "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
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
|
||||
|
||||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
[dev-dependencies]
|
|
@ -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 metadata = interface.get_metadata()?;
|
||||
let commands = interface.get_signature()?;
|
||||
|
||||
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
||||
// Update the file with the received signatures
|
||||
let item = PluginRegistryItem::new(plugin.identity(), commands);
|
||||
// Update the file with the received metadata and signatures
|
||||
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
|
||||
contents.upsert_plugin(item);
|
||||
Ok(())
|
||||
})?;
|
||||
|
|
|
@ -16,6 +16,7 @@ impl Command for PluginList {
|
|||
Type::Table(
|
||||
[
|
||||
("name".into(), Type::String),
|
||||
("version".into(), Type::String),
|
||||
("is_running".into(), Type::Bool),
|
||||
("pid".into(), Type::Int),
|
||||
("filename".into(), Type::String),
|
||||
|
@ -43,6 +44,7 @@ impl Command for PluginList {
|
|||
description: "List installed plugins.",
|
||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||
"name" => Value::test_string("inc"),
|
||||
"version" => Value::test_string(env!("CARGO_PKG_VERSION")),
|
||||
"is_running" => Value::test_bool(true),
|
||||
"pid" => Value::test_int(106480),
|
||||
"filename" => if cfg!(windows) {
|
||||
|
@ -98,8 +100,15 @@ impl Command for PluginList {
|
|||
.map(|s| Value::string(s.to_string_lossy(), 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! {
|
||||
"name" => Value::string(plugin.identity().name(), head),
|
||||
"version" => version,
|
||||
"is_running" => Value::bool(plugin.is_running(), head),
|
||||
"pid" => pid,
|
||||
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
||||
|
|
|
@ -31,11 +31,20 @@ pub(crate) fn modify_plugin_file(
|
|||
})?
|
||||
};
|
||||
|
||||
let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span);
|
||||
|
||||
// Try to read the plugin file if it exists
|
||||
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
|
||||
PluginRegistryFile::read_from(
|
||||
File::open(&plugin_registry_file_path).err_span(span)?,
|
||||
Some(span),
|
||||
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
||||
msg: format!(
|
||||
"failed to read `{}`: {}",
|
||||
plugin_registry_file_path.display(),
|
||||
err
|
||||
),
|
||||
span: file_span,
|
||||
})?,
|
||||
Some(file_span),
|
||||
)?
|
||||
} else {
|
||||
PluginRegistryFile::default()
|
||||
|
@ -46,7 +55,14 @@ pub(crate) fn modify_plugin_file(
|
|||
|
||||
// Save the modified file on success
|
||||
contents.write_to(
|
||||
File::create(&plugin_registry_file_path).err_span(span)?,
|
||||
File::create(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
|
||||
msg: format!(
|
||||
"failed to create `{}`: {}",
|
||||
plugin_registry_file_path.display(),
|
||||
err
|
||||
),
|
||||
span: file_span,
|
||||
})?,
|
||||
Some(span),
|
||||
)?;
|
||||
|
||||
|
|
|
@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
|
@ -20,6 +20,7 @@ pub fn default_shape_color(shape: &str) -> Style {
|
|||
"shape_flag" => Style::new().fg(Color::Blue).bold(),
|
||||
"shape_float" => Style::new().fg(Color::Purple).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_int" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "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
|
||||
|
||||
|
@ -13,21 +13,21 @@ version = "0.94.3"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.3" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.95.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.95.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nuon = { path = "../nuon", version = "0.94.3" }
|
||||
nuon = { path = "../nuon", version = "0.95.1" }
|
||||
|
||||
alphanumeric-sort = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
@ -42,6 +42,7 @@ chrono-humanize = { workspace = true }
|
|||
chrono-tz = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
csv = { workspace = true }
|
||||
deunicode = { workspace = true }
|
||||
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
|
||||
digest = { workspace = true, default-features = false }
|
||||
dtparse = { workspace = true }
|
||||
|
@ -136,8 +137,8 @@ sqlite = ["rusqlite"]
|
|||
trash-support = ["trash"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
|
||||
dirs-next = { workspace = true }
|
||||
mockito = { workspace = true, default-features = false }
|
||||
|
@ -145,4 +146,4 @@ quickcheck = { workspace = true }
|
|||
quickcheck_macros = { workspace = true }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tempfile = { workspace = true }
|
|
@ -189,6 +189,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
Str,
|
||||
StrCapitalize,
|
||||
StrContains,
|
||||
StrDeunicode,
|
||||
StrDistance,
|
||||
StrDowncase,
|
||||
StrEndswith,
|
||||
|
|
10
crates/nu-command/src/env/export_env.rs
vendored
10
crates/nu-command/src/env/export_env.rs
vendored
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportEnv;
|
||||
|
@ -23,6 +24,15 @@ impl Command for ExportEnv {
|
|||
"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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
10
crates/nu-command/src/env/source_env.rs
vendored
10
crates/nu-command/src/env/source_env.rs
vendored
|
@ -2,6 +2,7 @@ use nu_engine::{
|
|||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
||||
redirect_env,
|
||||
};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 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."
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -175,20 +175,32 @@ impl Command for Ls {
|
|||
},
|
||||
Example {
|
||||
description: "List files and directories whose name do not contain 'bar'",
|
||||
example: "ls -s | where name !~ bar",
|
||||
example: "ls | where name !~ bar",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all dirs in your home directory",
|
||||
description: "List the full path of all dirs in your home directory",
|
||||
example: "ls -a ~ | where type == dir",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"List all dirs in your home directory which have not been modified in 7 days",
|
||||
"List only the names (not paths) of all dirs in your home directory which have not been modified in 7 days",
|
||||
example: "ls -as ~ | where type == dir and modified < ((date now) - 7day)",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Recursively list all files and subdirectories under the current directory using a glob pattern",
|
||||
example: "ls -a **/*",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Recursively list *.rs and *.toml files using the glob command",
|
||||
example: "ls ...(glob **/*.{rs,toml})",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List given paths and show directories themselves",
|
||||
example: "['/path/to/directory' '/path/to/file'] | each {|| ls -D $in } | flatten",
|
||||
|
|
|
@ -69,9 +69,9 @@ impl Command for Find {
|
|||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Search and highlight text for a term in a string",
|
||||
example: r#"'Cargo.toml' | find toml"#,
|
||||
result: Some(Value::test_string("\u{1b}[37mCargo.\u{1b}[0m\u{1b}[41;37mtoml\u{1b}[0m\u{1b}[37m\u{1b}[0m".to_owned())),
|
||||
description: "Search and highlight text for a term in a string. Note that regular search is case insensitive",
|
||||
example: r#"'Cargo.toml' | find cargo"#,
|
||||
result: Some(Value::test_string("\u{1b}[37m\u{1b}[0m\u{1b}[41;37mCargo\u{1b}[0m\u{1b}[37m.toml\u{1b}[0m".to_owned())),
|
||||
},
|
||||
Example {
|
||||
description: "Search a number or a file size in a list of numbers",
|
||||
|
@ -457,9 +457,10 @@ fn find_with_rest_and_highlight(
|
|||
|
||||
let mut output: Vec<Value> = vec![];
|
||||
for line in lines {
|
||||
let line = line?.to_lowercase();
|
||||
let line = line?;
|
||||
let lower_val = line.to_lowercase();
|
||||
for term in &terms {
|
||||
if line.contains(term) {
|
||||
if lower_val.contains(term) {
|
||||
output.push(Value::string(
|
||||
highlight_search_string(
|
||||
&line,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use nu_engine::{command_prelude::*, ClosureEval};
|
||||
use nu_protocol::engine::Closure;
|
||||
use nu_protocol::engine::{Closure, CommandType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Where;
|
||||
|
@ -19,6 +19,10 @@ tables, known as "row conditions". On the other hand, reading the condition from
|
|||
not supported."#
|
||||
}
|
||||
|
||||
fn command_type(&self) -> CommandType {
|
||||
CommandType::Keyword
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("where")
|
||||
.input_output_types(vec![
|
||||
|
|
|
@ -39,7 +39,7 @@ fn from_delimited_stream(
|
|||
.from_reader(input_reader);
|
||||
|
||||
let headers = if noheaders {
|
||||
(1..=reader
|
||||
(0..reader
|
||||
.headers()
|
||||
.map_err(|err| from_csv_error(err, span))?
|
||||
.len())
|
||||
|
|
|
@ -52,12 +52,12 @@ impl Command for FromSsv {
|
|||
Value::test_list(
|
||||
vec![
|
||||
Value::test_record(record! {
|
||||
"column1" => Value::test_string("FOO"),
|
||||
"column2" => Value::test_string("BAR"),
|
||||
"column0" => Value::test_string("FOO"),
|
||||
"column1" => Value::test_string("BAR"),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"column1" => Value::test_string("1"),
|
||||
"column2" => Value::test_string("2"),
|
||||
"column0" => Value::test_string("1"),
|
||||
"column1" => Value::test_string("2"),
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
@ -170,7 +170,7 @@ fn parse_aligned_columns<'a>(
|
|||
let headers: Vec<(String, usize)> = indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, position)| (format!("column{}", i + 1), *position))
|
||||
.map(|(i, position)| (format!("column{}", i), *position))
|
||||
.collect();
|
||||
|
||||
construct(ls.iter().map(|s| s.to_owned()), headers)
|
||||
|
@ -215,7 +215,7 @@ fn parse_separated_columns<'a>(
|
|||
let parse_without_headers = |ls: Vec<&str>| {
|
||||
let num_columns = ls.iter().map(|r| r.len()).max().unwrap_or(0);
|
||||
|
||||
let headers = (1..=num_columns)
|
||||
let headers = (0..=num_columns)
|
||||
.map(|i| format!("column{i}"))
|
||||
.collect::<Vec<String>>();
|
||||
collect(headers, ls.into_iter(), separator)
|
||||
|
@ -370,9 +370,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
vec![owned("column1", "a"), owned("column2", "b")],
|
||||
vec![owned("column1", "1"), owned("column2", "2")],
|
||||
vec![owned("column1", "3"), owned("column2", "4")]
|
||||
vec![owned("column0", "a"), owned("column1", "b")],
|
||||
vec![owned("column0", "1"), owned("column1", "2")],
|
||||
vec![owned("column0", "3"), owned("column1", "4")]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -484,25 +484,25 @@ mod tests {
|
|||
result,
|
||||
vec![
|
||||
vec![
|
||||
owned("column1", "a multi-word value"),
|
||||
owned("column2", "b"),
|
||||
owned("column3", ""),
|
||||
owned("column4", "d"),
|
||||
owned("column5", "")
|
||||
],
|
||||
vec![
|
||||
owned("column1", "1"),
|
||||
owned("column0", "a multi-word value"),
|
||||
owned("column1", "b"),
|
||||
owned("column2", ""),
|
||||
owned("column3", "3-3"),
|
||||
owned("column4", "4"),
|
||||
owned("column5", "")
|
||||
owned("column3", "d"),
|
||||
owned("column4", "")
|
||||
],
|
||||
vec![
|
||||
owned("column0", "1"),
|
||||
owned("column1", ""),
|
||||
owned("column2", "3-3"),
|
||||
owned("column3", "4"),
|
||||
owned("column4", "")
|
||||
],
|
||||
vec![
|
||||
owned("column0", ""),
|
||||
owned("column1", ""),
|
||||
owned("column2", ""),
|
||||
owned("column3", ""),
|
||||
owned("column4", ""),
|
||||
owned("column5", "last")
|
||||
owned("column4", "last")
|
||||
],
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ast::{Expr, Expression};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
@ -14,6 +15,7 @@ struct Arguments {
|
|||
month_names: bool,
|
||||
full_year: Option<Spanned<i64>>,
|
||||
week_start: Option<Spanned<String>>,
|
||||
as_table: bool,
|
||||
}
|
||||
|
||||
impl Command for Cal {
|
||||
|
@ -26,6 +28,7 @@ impl Command for Cal {
|
|||
.switch("year", "Display the year column", Some('y'))
|
||||
.switch("quarter", "Display the quarter column", Some('q'))
|
||||
.switch("month", "Display the month column", Some('m'))
|
||||
.switch("as-table", "output as a table", Some('t'))
|
||||
.named(
|
||||
"full-year",
|
||||
SyntaxShape::Int,
|
||||
|
@ -43,7 +46,10 @@ impl Command for Cal {
|
|||
"Display the month names instead of integers",
|
||||
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
|
||||
.category(Category::Generators)
|
||||
}
|
||||
|
@ -75,10 +81,15 @@ impl Command for Cal {
|
|||
result: None,
|
||||
},
|
||||
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",
|
||||
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")?,
|
||||
full_year: call.get_flag(engine_state, stack, "full-year")?,
|
||||
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);
|
||||
|
@ -131,7 +143,27 @@ pub fn cal(
|
|||
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 {
|
||||
|
|
|
@ -12,13 +12,7 @@ impl Command for Generate {
|
|||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("generate")
|
||||
.input_output_types(vec![
|
||||
(Type::Nothing, Type::List(Box::new(Type::Any))),
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Any)),
|
||||
),
|
||||
])
|
||||
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))])
|
||||
.required("initial", SyntaxShape::Any, "Initial value.")
|
||||
.required(
|
||||
"closure",
|
||||
|
@ -63,23 +57,10 @@ used as the next argument to the closure, otherwise generation stops.
|
|||
)),
|
||||
},
|
||||
Example {
|
||||
example: "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10",
|
||||
description: "Generate a stream of fibonacci numbers",
|
||||
result: Some(Value::list(
|
||||
vec![
|
||||
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(),
|
||||
)),
|
||||
example:
|
||||
"generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
|
||||
description: "Generate a continuous stream of Fibonacci numbers",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ static CHAR_MAP: Lazy<IndexMap<&'static str, String>> = Lazy::new(|| {
|
|||
// These are some regular characters that either can't be used or
|
||||
// it's just easier to use them like this.
|
||||
|
||||
"nul" => '\x00'.to_string(), // nul character, 0x00
|
||||
"null_byte" => '\x00'.to_string(), // nul character, 0x00
|
||||
"zero_byte" => '\x00'.to_string(), // nul character, 0x00
|
||||
// This are the "normal" characters section
|
||||
"newline" => '\n'.to_string(),
|
||||
"enter" => '\n'.to_string(),
|
||||
|
|
|
@ -72,7 +72,9 @@ impl GuessWidth {
|
|||
|
||||
let mut rows = Vec::new();
|
||||
while let Ok(columns) = self.read() {
|
||||
rows.push(columns);
|
||||
if !columns.is_empty() {
|
||||
rows.push(columns);
|
||||
}
|
||||
}
|
||||
rows
|
||||
}
|
||||
|
@ -175,34 +177,47 @@ fn separator_position(lr: &[char], p: usize, pos: &[usize], n: usize) -> usize {
|
|||
|
||||
fn split(line: &str, pos: &[usize], trim_space: bool) -> Vec<String> {
|
||||
let mut n = 0;
|
||||
let mut start = 0;
|
||||
let mut start_char = 0;
|
||||
let mut columns = Vec::with_capacity(pos.len() + 1);
|
||||
let lr: Vec<char> = line.chars().collect();
|
||||
let (line_char_boundaries, line_chars): (Vec<usize>, Vec<char>) = line.char_indices().unzip();
|
||||
let mut w = 0;
|
||||
|
||||
for p in 0..lr.len() {
|
||||
if line_chars.is_empty() || line_chars.iter().all(|&c| c.is_whitespace()) {
|
||||
// current line is completely empty, or only filled with whitespace
|
||||
return Vec::new();
|
||||
} else if !pos.is_empty()
|
||||
&& line_chars.iter().all(|&c| !c.is_whitespace())
|
||||
&& pos[0] < UnicodeWidthStr::width(line)
|
||||
{
|
||||
// we have more than 1 column in the input, but the current line has no whitespace,
|
||||
// and it is longer than the first detected column separation position
|
||||
// this indicates some kind of decoration line. let's skip it
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
for p in 0..line_char_boundaries.len() {
|
||||
if pos.is_empty() || n > pos.len() - 1 {
|
||||
start = p;
|
||||
start_char = p;
|
||||
break;
|
||||
}
|
||||
|
||||
if pos[n] <= w {
|
||||
let end = separator_position(&lr, p, pos, n);
|
||||
if start > end {
|
||||
let end_char = separator_position(&line_chars, p, pos, n);
|
||||
if start_char > end_char {
|
||||
break;
|
||||
}
|
||||
let col = &line[start..end];
|
||||
let col = &line[line_char_boundaries[start_char]..line_char_boundaries[end_char]];
|
||||
let col = if trim_space { col.trim() } else { col };
|
||||
columns.push(col.to_string());
|
||||
n += 1;
|
||||
start = end;
|
||||
start_char = end_char;
|
||||
}
|
||||
|
||||
w += UnicodeWidthStr::width(lr[p].to_string().as_str());
|
||||
w += UnicodeWidthStr::width(line_chars[p].to_string().as_str());
|
||||
}
|
||||
|
||||
// add last part.
|
||||
let col = &line[start..];
|
||||
let col = &line[line_char_boundaries[start_char]..];
|
||||
let col = if trim_space { col.trim() } else { col };
|
||||
columns.push(col.to_string());
|
||||
columns
|
||||
|
@ -423,6 +438,162 @@ D: 104792064 17042676 87749388 17% /d";
|
|||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_multibyte() {
|
||||
let input = "A… B\nC… D";
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["A…", "B"], vec!["C…", "D"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_combining_diacritical_marks() {
|
||||
let input = "Name Surname
|
||||
Ștefan Țincu ";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["Name", "Surname"], vec!["Ștefan", "Țincu"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_single_column() {
|
||||
let input = "A
|
||||
|
||||
B
|
||||
|
||||
C";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["A"], vec!["B"], vec!["C"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_row_without_whitespace() {
|
||||
let input = "A B C D
|
||||
-------
|
||||
E F G H";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["A", "B", "C", "D"], vec!["E", "F", "G", "H"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_row_with_single_column() {
|
||||
let input = "A B C D
|
||||
E
|
||||
F G H I";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![
|
||||
vec!["A", "B", "C", "D"],
|
||||
vec!["E"],
|
||||
vec!["F", "G", "H", "I"],
|
||||
];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_empty_row() {
|
||||
let input = "A B C D
|
||||
|
||||
E F G H";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["A", "B", "C", "D"], vec!["E", "F", "G", "H"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_width_row_with_only_whitespace() {
|
||||
let input = "A B C D
|
||||
|
||||
E F G H";
|
||||
|
||||
let r = Box::new(std::io::BufReader::new(input.as_bytes())) as Box<dyn std::io::Read>;
|
||||
let reader = std::io::BufReader::new(r);
|
||||
|
||||
let mut guess_width = GuessWidth {
|
||||
reader,
|
||||
pos: Vec::new(),
|
||||
pre_lines: Vec::new(),
|
||||
pre_count: 0,
|
||||
limit_split: 0,
|
||||
};
|
||||
|
||||
let want = vec![vec!["A", "B", "C", "D"], vec!["E", "F", "G", "H"]];
|
||||
let got = guess_width.read_all();
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_table() {
|
||||
let lines = vec![
|
||||
|
|
98
crates/nu-command/src/strings/str_/deunicode.rs
Normal file
98
crates/nu-command/src/strings/str_/deunicode.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use deunicode::deunicode;
|
||||
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"str deunicode"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str deunicode")
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert Unicode string to pure ASCII."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "ascii"]
|
||||
}
|
||||
|
||||
fn is_const(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let args = CellPathOnlyArgs::from(cell_paths);
|
||||
|
||||
operate(action, args, input, call.head, engine_state.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> {
|
||||
vec![Example {
|
||||
description: "deunicode a string",
|
||||
example: "'A…B' | str deunicode",
|
||||
result: Some(Value::test_string("A...B")),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||
match input {
|
||||
Value::String { val, .. } => Value::string(deunicode(val), head),
|
||||
Value::Error { .. } => input.clone(),
|
||||
_ => Value::error(
|
||||
ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: input.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: input.span(),
|
||||
},
|
||||
head,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod case;
|
||||
mod contains;
|
||||
mod deunicode;
|
||||
mod distance;
|
||||
mod ends_with;
|
||||
mod expand;
|
||||
|
@ -15,6 +16,7 @@ mod trim;
|
|||
|
||||
pub use case::*;
|
||||
pub use contains::SubCommand as StrContains;
|
||||
pub use deunicode::SubCommand as StrDeunicode;
|
||||
pub use distance::SubCommand as StrDistance;
|
||||
pub use ends_with::SubCommand as StrEndswith;
|
||||
pub use expand::SubCommand as StrExpand;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression};
|
||||
use nu_path::{dots::expand_ndots, expand_tilde};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
did_you_mean,
|
||||
process::ChildProcess,
|
||||
ByteStream, NuGlob, OutDest,
|
||||
ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest,
|
||||
};
|
||||
use nu_system::ForegroundChild;
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use pathdiff::diff_paths;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
|
@ -33,8 +32,16 @@ impl Command for External {
|
|||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("command", SyntaxShape::String, "External command to run.")
|
||||
.rest("args", SyntaxShape::Any, "Arguments for external command.")
|
||||
.required(
|
||||
"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)
|
||||
}
|
||||
|
||||
|
@ -47,42 +54,33 @@ impl Command for External {
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
// Evaluate the command name in the same way the arguments are evaluated. Since this isn't
|
||||
// a spread, it should return a one-element vec.
|
||||
let name_expr = call
|
||||
.positional_nth(0)
|
||||
.ok_or_else(|| ShellError::MissingParameter {
|
||||
param_name: "command".into(),
|
||||
span: call.head,
|
||||
})?;
|
||||
let name = eval_argument(engine_state, stack, name_expr, false)?
|
||||
.pop()
|
||||
.expect("eval_argument returned zero-element vec")
|
||||
.into_spanned(name_expr.span);
|
||||
let name: Value = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let name_str: Cow<str> = match &name {
|
||||
Value::Glob { val, .. } => Cow::Borrowed(val),
|
||||
Value::String { val, .. } => Cow::Borrowed(val),
|
||||
_ => Cow::Owned(name.clone().coerce_into_string()?),
|
||||
};
|
||||
|
||||
let expanded_name = match &name {
|
||||
// Expand tilde and ndots on the name if it's a bare string / glob (#13000)
|
||||
Value::Glob { no_expand, .. } if !*no_expand => {
|
||||
expand_ndots_safe(expand_tilde(&*name_str))
|
||||
}
|
||||
_ => Path::new(&*name_str).to_owned(),
|
||||
};
|
||||
|
||||
// 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
|
||||
// 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")
|
||||
} 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
|
||||
// effect if it's an absolute path already
|
||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||
let Some(executable) = which(&expanded_name, &paths, &cwd) else {
|
||||
return Err(command_not_found(
|
||||
&name.item,
|
||||
call.head,
|
||||
engine_state,
|
||||
stack,
|
||||
));
|
||||
let Some(executable) = which(expanded_name, &paths, &cwd) else {
|
||||
return Err(command_not_found(&name_str, call.head, engine_state, stack));
|
||||
};
|
||||
executable
|
||||
};
|
||||
|
@ -101,15 +99,15 @@ impl Command for External {
|
|||
// Configure args.
|
||||
let args = eval_arguments_from_call(engine_state, stack, call)?;
|
||||
#[cfg(windows)]
|
||||
if is_cmd_internal_command(&name.item) {
|
||||
if is_cmd_internal_command(&name_str) {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
// The /D flag disables execution of AutoRun commands from registry.
|
||||
// The /C flag followed by a command name instructs CMD to execute
|
||||
// that command and quit.
|
||||
command.args(["/D", "/C", &name.item]);
|
||||
command.args(["/D", "/C", &name_str]);
|
||||
for arg in &args {
|
||||
command.raw_arg(escape_cmd_argument(arg)?.as_ref());
|
||||
command.raw_arg(escape_cmd_argument(arg)?);
|
||||
}
|
||||
} else {
|
||||
command.args(args.into_iter().map(|s| s.item));
|
||||
|
@ -217,76 +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) -> Cow<'_, 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 {
|
||||
Cow::Owned(s[1..s.len() - 1].to_string().replace(r#"\""#, "\""))
|
||||
} else if quoted_by_single_quotes || quoted_by_backticks {
|
||||
Cow::Borrowed(&s[1..s.len() - 1])
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate all arguments from a call, performing expansions when necessary.
|
||||
pub fn eval_arguments_from_call(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Vec<Spanned<String>>, ShellError> {
|
||||
) -> Result<Vec<Spanned<OsString>>, ShellError> {
|
||||
let ctrlc = &engine_state.ctrlc;
|
||||
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) {
|
||||
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)? {
|
||||
let tilde_expanded = expand_tilde(&arg);
|
||||
for glob_expanded in expand_glob(&tilde_expanded, &cwd, expr.span, ctrlc)? {
|
||||
let inner_quotes_removed = remove_inner_quotes(&glob_expanded);
|
||||
args.push(inner_quotes_removed.into_owned().into_spanned(expr.span));
|
||||
for arg in eval_argument(engine_state, stack, expr, spread)? {
|
||||
match arg {
|
||||
// Expand globs passed to run-external
|
||||
Value::Glob { val, no_expand, .. } if !no_expand => args.extend(
|
||||
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)
|
||||
}
|
||||
|
||||
/// Evaluates an expression, coercing the values to strings.
|
||||
///
|
||||
/// Note: The parser currently has a special hack that retains surrounding
|
||||
/// quotes for string literals in `Expression`, so that we can decide whether
|
||||
/// the expression is considered a bare string. The hack doesn't affect string
|
||||
/// literals within lists or records. This function will remove the quotes
|
||||
/// before evaluating the expression.
|
||||
/// Custom `coerce_into_string()`, including globs, since those are often args to `run-external`
|
||||
/// as well
|
||||
fn coerce_into_string(val: Value) -> Result<String, ShellError> {
|
||||
match val {
|
||||
Value::Glob { val, .. } => Ok(val),
|
||||
_ => val.coerce_into_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate an argument, returning more than one value if it was a list to be spread.
|
||||
fn eval_argument(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
expr: &Expression,
|
||||
spread: bool,
|
||||
) -> Result<Vec<String>, 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());
|
||||
}
|
||||
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let eval = get_eval_expression(engine_state);
|
||||
match eval(engine_state, stack, &expr)? {
|
||||
match eval(engine_state, stack, expr)? {
|
||||
Value::List { vals, .. } => {
|
||||
if spread {
|
||||
vals.into_iter()
|
||||
.map(|val| val.coerce_into_string())
|
||||
.collect()
|
||||
Ok(vals)
|
||||
} else {
|
||||
Err(ShellError::CannotPassListToExternal {
|
||||
arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(),
|
||||
|
@ -298,31 +274,12 @@ fn eval_argument(
|
|||
if spread {
|
||||
Err(ShellError::CannotSpreadAsList { span: expr.span })
|
||||
} 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
|
||||
/// is not a valid glob, then this returns the original string as the expansion result.
|
||||
///
|
||||
|
@ -333,19 +290,21 @@ fn expand_glob(
|
|||
cwd: &Path,
|
||||
span: Span,
|
||||
interrupt: &Option<Arc<AtomicBool>>,
|
||||
) -> Result<Vec<String>, ShellError> {
|
||||
) -> Result<Vec<OsString>, ShellError> {
|
||||
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) {
|
||||
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
|
||||
// dir
|
||||
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
|
||||
if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
|
||||
let mut result = vec![];
|
||||
let mut result: Vec<OsString> = vec![];
|
||||
|
||||
for m in matches {
|
||||
if nu_utils::ctrl_c::was_pressed(interrupt) {
|
||||
|
@ -353,7 +312,7 @@ fn expand_glob(
|
|||
}
|
||||
if let Ok(arg) = m {
|
||||
let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd);
|
||||
result.push(arg.to_string_lossy().to_string());
|
||||
result.push(arg.into());
|
||||
} else {
|
||||
result.push(arg.into());
|
||||
}
|
||||
|
@ -392,23 +351,6 @@ fn resolve_globbed_path_to_cwd_relative(
|
|||
}
|
||||
}
|
||||
|
||||
/// Transforms `--option="value"` into `--option=value`. `value` can be quoted
|
||||
/// with double quotes, single quotes, or backticks. Only removes the outermost
|
||||
/// pair of quotes after the equal sign.
|
||||
fn remove_inner_quotes(arg: &str) -> Cow<'_, str> {
|
||||
// Split `arg` on the first `=`.
|
||||
let Some((option, value)) = arg.split_once('=') else {
|
||||
return Cow::Borrowed(arg);
|
||||
};
|
||||
// Check that `option` doesn't contain quotes.
|
||||
if option.contains('"') || option.contains('\'') || option.contains('`') {
|
||||
return Cow::Borrowed(arg);
|
||||
}
|
||||
// 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
|
||||
/// first rendered using the `table` command.
|
||||
///
|
||||
|
@ -577,7 +519,7 @@ pub fn command_not_found(
|
|||
/// Note: the `which.rs` crate always uses PATHEXT from the environment. As
|
||||
/// such, changing PATHEXT within Nushell doesn't work without updating the
|
||||
/// 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)]
|
||||
let paths = format!("{};{}", cwd.display(), paths);
|
||||
which::which_in(name, Some(paths), cwd).ok()
|
||||
|
@ -593,17 +535,18 @@ fn is_cmd_internal_command(name: &str) -> bool {
|
|||
}
|
||||
|
||||
/// Returns true if a string contains CMD special characters.
|
||||
#[cfg(windows)]
|
||||
fn has_cmd_special_character(s: &str) -> bool {
|
||||
const SPECIAL_CHARS: &[char] = &['<', '>', '&', '|', '^'];
|
||||
SPECIAL_CHARS.iter().any(|c| s.contains(*c))
|
||||
fn has_cmd_special_character(s: impl AsRef<[u8]>) -> bool {
|
||||
s.as_ref()
|
||||
.iter()
|
||||
.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()`.
|
||||
#[cfg(windows)]
|
||||
fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError> {
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn escape_cmd_argument(arg: &Spanned<OsString>) -> Result<Cow<'_, OsStr>, ShellError> {
|
||||
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
|
||||
Err(ShellError::ExternalCommand {
|
||||
label:
|
||||
|
@ -612,12 +555,12 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
|
|||
help: "some characters currently cannot be securely escaped".into(),
|
||||
span: *span,
|
||||
})
|
||||
} else if arg.contains('"') {
|
||||
} else if bytes.contains(&b'"') {
|
||||
// If `arg` is already quoted by double quotes, confirm there's no
|
||||
// embedded double quotes, then leave it as is.
|
||||
if arg.chars().filter(|c| *c == '"').count() == 2
|
||||
&& arg.starts_with('"')
|
||||
&& arg.ends_with('"')
|
||||
if bytes.iter().filter(|b| **b == b'"').count() == 2
|
||||
&& bytes.starts_with(b"\"")
|
||||
&& bytes.ends_with(b"\"")
|
||||
{
|
||||
Ok(Cow::Borrowed(arg))
|
||||
} else {
|
||||
|
@ -628,76 +571,39 @@ fn escape_cmd_argument(arg: &Spanned<String>) -> Result<Cow<'_, str>, ShellError
|
|||
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.
|
||||
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 {
|
||||
// FIXME?: what if `arg.is_empty()`?
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_protocol::ast::ListItem;
|
||||
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'#"#);
|
||||
assert_eq!(remove_quotes(r#""foo\" bar""#), r#"foo" bar"#);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_expand_glob() {
|
||||
Playground::setup("test_expand_glob", |dirs, play| {
|
||||
|
@ -727,40 +633,14 @@ mod test {
|
|||
let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap();
|
||||
let expected = &["[*.txt"];
|
||||
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);
|
||||
|
||||
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="v\"value""#);
|
||||
let expected = r#"option=v"value"#;
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_pipeline_data() {
|
||||
let engine_state = EngineState::new();
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
// Tests against table/structured data
|
||||
#[test]
|
||||
fn cal_full_year() {
|
||||
let actual = nu!("cal -y --full-year 2010 | first | to json -r");
|
||||
let actual = nu!("cal -t -y --full-year 2010 | first | to json -r");
|
||||
|
||||
let first_week_2010_json =
|
||||
r#"{"year":2010,"su":null,"mo":null,"tu":null,"we":null,"th":null,"fr":1,"sa":2}"#;
|
||||
|
@ -14,7 +15,7 @@ fn cal_full_year() {
|
|||
fn cal_february_2020_leap_year() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal -ym --full-year 2020 --month-names | where month == "february" | to json -r
|
||||
cal --as-table -ym --full-year 2020 --month-names | where month == "february" | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -27,7 +28,7 @@ fn cal_february_2020_leap_year() {
|
|||
fn cal_fr_the_thirteenths_in_2015() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2015 | default 0 fr | where fr == 13 | length
|
||||
cal --as-table --full-year 2015 | default 0 fr | where fr == 13 | length
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -38,7 +39,7 @@ fn cal_fr_the_thirteenths_in_2015() {
|
|||
fn cal_rows_in_2020() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2020 | length
|
||||
cal --as-table --full-year 2020 | length
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -49,7 +50,7 @@ fn cal_rows_in_2020() {
|
|||
fn cal_week_day_start_mo() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r
|
||||
cal --as-table --full-year 2020 -m --month-names --week-start mo | where month == january | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -62,9 +63,43 @@ fn cal_week_day_start_mo() {
|
|||
fn cal_sees_pipeline_year() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 1020 | get mo | first 4 | to json -r
|
||||
cal --as-table --full-year 1020 | get mo | first 4 | to json -r
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[null,3,10,17]");
|
||||
}
|
||||
|
||||
// Tests against default string output
|
||||
#[test]
|
||||
fn cal_is_string() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal | describe
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "string (stream)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cal_year_num_lines() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --full-year 2024 | lines | length
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "68");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cal_week_start_string() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
cal --week-start fr | lines | get 1 | split row '│' | get 2 | ansi strip | str trim
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "sa");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,16 @@ fn find_with_list_search_with_char() {
|
|||
assert_eq!(actual.out, "[\"\\u001b[37m\\u001b[0m\\u001b[41;37ml\\u001b[0m\\u001b[37marry\\u001b[0m\",\"\\u001b[37mcur\\u001b[0m\\u001b[41;37ml\\u001b[0m\\u001b[37my\\u001b[0m\"]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_bytestream_search_with_char() {
|
||||
let actual =
|
||||
nu!("\"ABC\" | save foo.txt; let res = open foo.txt | find abc; rm foo.txt; $res | get 0");
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mABC\u{1b}[0m\u{1b}[37m\u{1b}[0m"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_with_list_search_with_number() {
|
||||
let actual = nu!("[1 2 3 4 5] | find 3 | get 0");
|
||||
|
|
|
@ -2,7 +2,7 @@ use nu_test_support::nu;
|
|||
|
||||
#[test]
|
||||
fn length_columns_in_cal_table() {
|
||||
let actual = nu!("cal | columns | length");
|
||||
let actual = nu!("cal --as-table | columns | length");
|
||||
|
||||
assert_eq!(actual.out, "7");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#[cfg(not(windows))]
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
@ -17,7 +16,6 @@ fn better_empty_redirection() {
|
|||
assert!(!actual.out.contains('2'));
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn explicit_glob() {
|
||||
Playground::setup("external with explicit glob", |dirs, sandbox| {
|
||||
|
@ -30,15 +28,15 @@ fn explicit_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls | glob '*.txt' | length
|
||||
^nu --testbin cococo ('*.txt' | into glob)
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "2");
|
||||
assert!(actual.out.contains("D&D_volume_1.txt"));
|
||||
assert!(actual.out.contains("D&D_volume_2.txt"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn bare_word_expand_path_glob() {
|
||||
Playground::setup("bare word should do the expansion", |dirs, sandbox| {
|
||||
|
@ -51,7 +49,7 @@ fn bare_word_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^ls *.txt
|
||||
^nu --testbin cococo *.txt
|
||||
"
|
||||
));
|
||||
|
||||
|
@ -60,7 +58,6 @@ fn bare_word_expand_path_glob() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn backtick_expand_path_glob() {
|
||||
Playground::setup("backtick should do the expansion", |dirs, sandbox| {
|
||||
|
@ -73,7 +70,7 @@ fn backtick_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls `*.txt`
|
||||
^nu --testbin cococo `*.txt`
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -82,7 +79,6 @@ fn backtick_expand_path_glob() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn single_quote_does_not_expand_path_glob() {
|
||||
Playground::setup("single quote do not run the expansion", |dirs, sandbox| {
|
||||
|
@ -95,15 +91,14 @@ fn single_quote_does_not_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls '*.txt'
|
||||
^nu --testbin cococo '*.txt'
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("No such file or directory"));
|
||||
assert_eq!(actual.out, "*.txt");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn double_quote_does_not_expand_path_glob() {
|
||||
Playground::setup("double quote do not run the expansion", |dirs, sandbox| {
|
||||
|
@ -116,22 +111,21 @@ fn double_quote_does_not_expand_path_glob() {
|
|||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^ls "*.txt"
|
||||
^nu --testbin cococo "*.txt"
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("No such file or directory"));
|
||||
assert_eq!(actual.out, "*.txt");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^ls *.abc; echo done
|
||||
nu --testbin fail; echo done
|
||||
"
|
||||
));
|
||||
|
||||
|
@ -155,16 +149,51 @@ fn external_args_with_quoted() {
|
|||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn external_arg_with_long_flag_value_quoted() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
fn external_arg_with_option_like_embedded_quotes() {
|
||||
// TODO: would be nice to make this work with cococo, but arg parsing interferes
|
||||
Playground::setup(
|
||||
"external arg with option like embedded quotes",
|
||||
|dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo --foo='bar' -foo='bar'
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "--foo=bar -foo=bar");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_with_non_option_like_embedded_quotes() {
|
||||
Playground::setup(
|
||||
"external arg with non option like embedded quotes",
|
||||
|dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^nu --testbin cococo foo='bar' 'foo'=bar
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "foo=bar foo=bar");
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_with_string_interpolation() {
|
||||
Playground::setup("external arg with string interpolation", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^echo --foo='bar'
|
||||
^nu --testbin cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "--foo=bar");
|
||||
assert_eq!(actual.out, "foo=4 foo=4 foo=4");
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -200,6 +229,99 @@ fn external_command_escape_args() {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_ndots_args() {
|
||||
let actual = nu!(r#"
|
||||
nu --testbin cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
if cfg!(windows) {
|
||||
// Windows is a bit weird right now, where if ndots has to fix something it's going to
|
||||
// change everything to backslashes too. Would be good to fix that
|
||||
r"foo/. foo/.. foo\..\.. foo/./bar foo/../bar foo\..\..\bar ./bar ../bar ..\..\bar"
|
||||
} else {
|
||||
r"foo/. foo/.. foo/../.. foo/./bar foo/../bar foo/../../bar ./bar ../bar ../../bar"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_url_args() {
|
||||
// If ndots is not handled correctly, we can lose the double forward slashes that are needed
|
||||
// here
|
||||
let actual = nu!(r#"
|
||||
nu --testbin cococo http://example.com http://example.com/.../foo //foo
|
||||
"#);
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
"http://example.com http://example.com/.../foo //foo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(target_os = "linux"),
|
||||
ignore = "only runs on Linux, where controlling the HOME var is reliable"
|
||||
)]
|
||||
fn external_command_expand_tilde() {
|
||||
Playground::setup("external command expand tilde", |dirs, _| {
|
||||
// Make a copy of the nu executable that we can use
|
||||
let mut src = std::fs::File::open(nu_test_support::fs::binaries().join("nu"))
|
||||
.expect("failed to open nu");
|
||||
let mut dst = std::fs::File::create_new(dirs.test().join("test_nu"))
|
||||
.expect("failed to create test_nu file");
|
||||
std::io::copy(&mut src, &mut dst).expect("failed to copy data for nu binary");
|
||||
|
||||
// Make test_nu have the same permissions so that it's executable
|
||||
dst.set_permissions(
|
||||
src.metadata()
|
||||
.expect("failed to get nu metadata")
|
||||
.permissions(),
|
||||
)
|
||||
.expect("failed to set permissions on test_nu");
|
||||
|
||||
// Close the files
|
||||
drop(dst);
|
||||
drop(src);
|
||||
|
||||
let actual = nu!(
|
||||
envs: vec![
|
||||
("HOME".to_string(), dirs.test().to_string_lossy().into_owned()),
|
||||
],
|
||||
r#"
|
||||
^~/test_nu --testbin cococo hello
|
||||
"#
|
||||
);
|
||||
assert_eq!(actual.out, "hello");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_arg_expand_tilde() {
|
||||
Playground::setup("external arg expand tilde", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
^nu --testbin cococo ~/foo ~/(2 + 2)
|
||||
"#
|
||||
));
|
||||
|
||||
let home = dirs_next::home_dir().expect("failed to find home dir");
|
||||
|
||||
assert_eq!(
|
||||
actual.out,
|
||||
format!(
|
||||
"{} {}",
|
||||
home.join("foo").display(),
|
||||
home.join("4").display()
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_command_not_expand_tilde_with_quotes() {
|
||||
Playground::setup(
|
||||
|
@ -231,21 +353,6 @@ fn external_command_receives_raw_binary_data() {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {
|
||||
Playground::setup("external failed command with semicolon", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
"
|
||||
^cargo asdf; echo done
|
||||
"
|
||||
));
|
||||
|
||||
assert!(!actual.out.contains("done"));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn can_run_batch_files() {
|
||||
|
|
|
@ -284,7 +284,7 @@ fn from_csv_text_skipping_headers_to_table() {
|
|||
r#"
|
||||
open los_tres_amigos.txt
|
||||
| from csv --noheaders
|
||||
| get column3
|
||||
| get column2
|
||||
| length
|
||||
"#
|
||||
));
|
||||
|
|
|
@ -74,7 +74,7 @@ fn from_ssv_text_treating_first_line_as_data_with_flag() {
|
|||
open oc_get_svc.txt
|
||||
| from ssv --noheaders -a
|
||||
| first
|
||||
| get column1
|
||||
| get column0
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -84,7 +84,7 @@ fn from_ssv_text_treating_first_line_as_data_with_flag() {
|
|||
open oc_get_svc.txt
|
||||
| from ssv --noheaders
|
||||
| first
|
||||
| get column1
|
||||
| get column0
|
||||
|
||||
"#
|
||||
));
|
||||
|
|
|
@ -207,7 +207,7 @@ fn from_tsv_text_skipping_headers_to_table() {
|
|||
r#"
|
||||
open los_tres_amigos.txt
|
||||
| from tsv --noheaders
|
||||
| get column3
|
||||
| get column2
|
||||
| length
|
||||
"#
|
||||
));
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
name = "nu-derive-value"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
@ -18,4 +18,4 @@ proc-macro2 = { workspace = true }
|
|||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
proc-macro-error = { workspace = true }
|
||||
convert_case = { workspace = true }
|
||||
convert_case = { workspace = true }
|
|
@ -3,6 +3,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident,
|
||||
Type,
|
||||
};
|
||||
|
||||
use crate::attributes::{self, ContainerAttributes};
|
||||
|
@ -116,15 +117,11 @@ fn derive_struct_from_value(
|
|||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?,
|
||||
/// favorite_toy: <Option<String> as nu_protocol::FromValue>::from_value(
|
||||
/// record
|
||||
/// .remove("favorite_toy")
|
||||
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
/// col_name: std::string::ToString::to_string("favorite_toy"),
|
||||
/// span: std::option::Option::None,
|
||||
/// src_span: span
|
||||
/// })?,
|
||||
/// )?,
|
||||
/// favorite_toy: record
|
||||
/// .remove("favorite_toy")
|
||||
/// .map(|v| <#ty as nu_protocol::FromValue>::from_value(v))
|
||||
/// .transpose()?
|
||||
/// .flatten(),
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
|
@ -480,20 +477,29 @@ fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenSt
|
|||
match fields {
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields.named.iter().map(|field| {
|
||||
// TODO: handle missing fields for Options as None
|
||||
let ident = field.ident.as_ref().expect("named has idents");
|
||||
let ident_s = ident.to_string();
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
#ident: <#ty as nu_protocol::FromValue>::from_value(
|
||||
record
|
||||
match type_is_option(ty) {
|
||||
true => quote! {
|
||||
#ident: record
|
||||
.remove(#ident_s)
|
||||
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
col_name: std::string::ToString::to_string(#ident_s),
|
||||
span: std::option::Option::None,
|
||||
src_span: span
|
||||
})?,
|
||||
)?
|
||||
.map(|v| <#ty as nu_protocol::FromValue>::from_value(v))
|
||||
.transpose()?
|
||||
.flatten()
|
||||
},
|
||||
|
||||
false => quote! {
|
||||
#ident: <#ty as nu_protocol::FromValue>::from_value(
|
||||
record
|
||||
.remove(#ident_s)
|
||||
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||
col_name: std::string::ToString::to_string(#ident_s),
|
||||
span: std::option::Option::None,
|
||||
src_span: span
|
||||
})?,
|
||||
)?
|
||||
},
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
|
@ -537,3 +543,25 @@ fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenSt
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
const FULLY_QUALIFIED_OPTION: &str = "std::option::Option";
|
||||
const PARTIALLY_QUALIFIED_OPTION: &str = "option::Option";
|
||||
const PRELUDE_OPTION: &str = "Option";
|
||||
|
||||
/// Check if the field type is an `Option`.
|
||||
///
|
||||
/// This function checks if a given type is an `Option`.
|
||||
/// We assume that an `Option` is [`std::option::Option`] because we can't see the whole code and
|
||||
/// can't ask the compiler itself.
|
||||
/// If the `Option` type isn't `std::option::Option`, the user will get a compile error due to a
|
||||
/// type mismatch.
|
||||
/// It's very unusual for people to override `Option`, so this should rarely be an issue.
|
||||
///
|
||||
/// When [rust#63084](https://github.com/rust-lang/rust/issues/63084) is resolved, we can use
|
||||
/// [`std::any::type_name`] for a static assertion check to get a more direct error messages.
|
||||
fn type_is_option(ty: &Type) -> bool {
|
||||
let s = ty.to_token_stream().to_string();
|
||||
s.starts_with(PRELUDE_OPTION)
|
||||
|| s.starts_with(PARTIALLY_QUALIFIED_OPTION)
|
||||
|| s.starts_with(FULLY_QUALIFIED_OPTION)
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
plugin = []
|
|
@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-explore"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-table = { path = "../nu-table", version = "0.94.3" }
|
||||
nu-json = { path = "../nu-json", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.95.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.95.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.95.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.3" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -32,4 +32,4 @@ ansi-str = { workspace = true }
|
|||
unicode-width = { workspace = true }
|
||||
lscolors = { workspace = true, default-features = false, features = [
|
||||
"nu-ansi-term",
|
||||
] }
|
||||
] }
|
|
@ -131,7 +131,7 @@ impl RecordView {
|
|||
Orientation::Left => (column, row),
|
||||
};
|
||||
|
||||
if row >= layer.count_rows() || column >= layer.count_columns() {
|
||||
if row >= layer.record_values.len() || column >= layer.column_names.len() {
|
||||
// actually must never happen; unless cursor works incorrectly
|
||||
// if being sure about cursor it can be deleted;
|
||||
return Value::nothing(Span::unknown());
|
||||
|
@ -610,7 +610,7 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 {
|
|||
/// scroll to the end of the data
|
||||
fn tail_data(state: &mut RecordView, page_size: usize) {
|
||||
let layer = state.get_layer_last_mut();
|
||||
let count_rows = layer.count_rows();
|
||||
let count_rows = layer.record_values.len();
|
||||
if count_rows > page_size {
|
||||
layer
|
||||
.cursor
|
||||
|
@ -722,43 +722,66 @@ fn get_percentage(value: usize, max: usize) -> usize {
|
|||
}
|
||||
|
||||
fn transpose_table(layer: &mut RecordLayer) {
|
||||
if layer.was_transposed {
|
||||
transpose_from(layer);
|
||||
} else {
|
||||
transpose_to(layer);
|
||||
}
|
||||
|
||||
layer.was_transposed = !layer.was_transposed;
|
||||
}
|
||||
|
||||
fn transpose_from(layer: &mut RecordLayer) {
|
||||
let count_rows = layer.record_values.len();
|
||||
let count_columns = layer.column_names.len();
|
||||
|
||||
if layer.was_transposed {
|
||||
let headers = pop_first_column(&mut layer.record_values);
|
||||
let headers = headers
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
Value::String { val, .. } => val,
|
||||
_ => unreachable!("must never happen"),
|
||||
})
|
||||
.collect();
|
||||
if let Some(data) = &mut layer.record_text {
|
||||
pop_first_column(data);
|
||||
*data = _transpose_table(data, count_rows, count_columns - 1);
|
||||
}
|
||||
|
||||
let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
|
||||
let headers = pop_first_column(&mut layer.record_values);
|
||||
let headers = headers
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
Value::String { val, .. } => val,
|
||||
_ => unreachable!("must never happen"),
|
||||
})
|
||||
.collect();
|
||||
|
||||
layer.record_values = data;
|
||||
layer.column_names = headers;
|
||||
let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
|
||||
|
||||
return;
|
||||
layer.record_values = data;
|
||||
layer.column_names = headers;
|
||||
}
|
||||
|
||||
fn transpose_to(layer: &mut RecordLayer) {
|
||||
let count_rows = layer.record_values.len();
|
||||
let count_columns = layer.column_names.len();
|
||||
|
||||
if let Some(data) = &mut layer.record_text {
|
||||
*data = _transpose_table(data, count_rows, count_columns);
|
||||
for (column, column_name) in layer.column_names.iter().enumerate() {
|
||||
let value = (column_name.to_owned(), Default::default());
|
||||
data[column].insert(0, value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = _transpose_table(&layer.record_values, count_rows, count_columns);
|
||||
|
||||
for (column, column_name) in layer.column_names.iter().enumerate() {
|
||||
let value = Value::string(column_name, NuSpan::unknown());
|
||||
|
||||
data[column].insert(0, value);
|
||||
}
|
||||
|
||||
layer.record_values = data;
|
||||
layer.column_names = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect();
|
||||
|
||||
layer.was_transposed = !layer.was_transposed;
|
||||
}
|
||||
|
||||
fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
|
||||
let mut data = vec![Value::default(); values.len()];
|
||||
fn pop_first_column<T>(values: &mut [Vec<T>]) -> Vec<T>
|
||||
where
|
||||
T: Default + Clone,
|
||||
{
|
||||
let mut data = vec![T::default(); values.len()];
|
||||
for (row, values) in values.iter_mut().enumerate() {
|
||||
data[row] = values.remove(0);
|
||||
}
|
||||
|
@ -766,12 +789,11 @@ fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
|
|||
data
|
||||
}
|
||||
|
||||
fn _transpose_table(
|
||||
values: &[Vec<Value>],
|
||||
count_rows: usize,
|
||||
count_columns: usize,
|
||||
) -> Vec<Vec<Value>> {
|
||||
let mut data = vec![vec![Value::default(); count_rows]; count_columns];
|
||||
fn _transpose_table<T>(values: &[Vec<T>], count_rows: usize, count_columns: usize) -> Vec<Vec<T>>
|
||||
where
|
||||
T: Clone + Default,
|
||||
{
|
||||
let mut data = vec![vec![T::default(); count_rows]; count_columns];
|
||||
for (row, values) in values.iter().enumerate() {
|
||||
for (column, value) in values.iter().enumerate() {
|
||||
data[column][row].clone_from(value);
|
||||
|
|
|
@ -88,6 +88,7 @@ impl StatefulWidget for TableWidget<'_> {
|
|||
|
||||
// todo: refactoring these to methods as they have quite a bit in common.
|
||||
impl<'a> TableWidget<'a> {
|
||||
// header at the top; header is always 1 line
|
||||
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
let padding_l = self.config.column_padding_left as u16;
|
||||
let padding_r = self.config.column_padding_right as u16;
|
||||
|
@ -130,25 +131,16 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if show_index {
|
||||
let area = Rect::new(width, data_y, area.width, data_height);
|
||||
width += render_index(
|
||||
buf,
|
||||
area,
|
||||
Rect::new(width, data_y, area.width, data_height),
|
||||
self.style_computer,
|
||||
self.index_row,
|
||||
padding_l,
|
||||
padding_r,
|
||||
);
|
||||
|
||||
width += render_vertical_line_with_split(
|
||||
buf,
|
||||
width,
|
||||
data_y,
|
||||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
separator_s,
|
||||
);
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
// if there is more data than we can show, add an ellipsis to the column headers to hint at that
|
||||
|
@ -162,6 +154,11 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
for col in self.index_column..self.columns.len() {
|
||||
let need_split_line = state.count_columns > 0 && width < area.width;
|
||||
if need_split_line {
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
let mut column = create_column(data, col);
|
||||
let column_width = calculate_column_width(&column);
|
||||
|
||||
|
@ -200,6 +197,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
let head_iter = [(&head, head_style)].into_iter();
|
||||
|
||||
// we don't change width here cause the whole column have the same width; so we add it when we print data
|
||||
let mut w = width;
|
||||
w += render_space(buf, w, head_y, 1, padding_l);
|
||||
w += render_column(buf, w, head_y, use_space, head_iter);
|
||||
|
@ -209,10 +207,10 @@ impl<'a> TableWidget<'a> {
|
|||
state.layout.push(&head, x, head_y, use_space, 1);
|
||||
}
|
||||
|
||||
let head_rows = column.iter().map(|(t, s)| (t, *s));
|
||||
let column_rows = column.iter().map(|(t, s)| (t, *s));
|
||||
|
||||
width += render_space(buf, width, data_y, data_height, padding_l);
|
||||
width += render_column(buf, width, data_y, use_space, head_rows);
|
||||
width += render_column(buf, width, data_y, use_space, column_rows);
|
||||
width += render_space(buf, width, data_y, data_height, padding_r);
|
||||
|
||||
for (row, (text, _)) in column.iter().enumerate() {
|
||||
|
@ -235,15 +233,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if width < area.width {
|
||||
width += render_vertical_line_with_split(
|
||||
buf,
|
||||
width,
|
||||
data_y,
|
||||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
separator_s,
|
||||
);
|
||||
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
|
||||
}
|
||||
|
||||
let rest = area.width.saturating_sub(width);
|
||||
|
@ -255,6 +245,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// header at the left; header is always 1 line
|
||||
fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
return;
|
||||
|
@ -353,6 +344,9 @@ impl<'a> TableWidget<'a> {
|
|||
state.count_rows = columns.len();
|
||||
state.count_columns = 0;
|
||||
|
||||
// note: is there a time where we would have more then 1 column?
|
||||
// seems like not really; cause it's literally KV table, or am I wrong?
|
||||
|
||||
for col in self.index_column..self.data.len() {
|
||||
let mut column =
|
||||
self.data[col][self.index_row..self.index_row + columns.len()].to_vec();
|
||||
|
@ -361,6 +355,13 @@ impl<'a> TableWidget<'a> {
|
|||
break;
|
||||
}
|
||||
|
||||
// see KV comment; this block might never got used
|
||||
let need_split_line = state.count_columns > 0 && left_w < area.width;
|
||||
if need_split_line {
|
||||
render_vertical_line(buf, area.x + left_w, area.y, area.height, separator_s);
|
||||
left_w += 1;
|
||||
}
|
||||
|
||||
let column_width = column_width as u16;
|
||||
let available = area.width - left_w;
|
||||
let is_last = col + 1 == self.data.len();
|
||||
|
@ -555,6 +556,51 @@ fn render_index(
|
|||
width
|
||||
}
|
||||
|
||||
fn render_split_line(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
height: u16,
|
||||
has_head: bool,
|
||||
style: NuStyle,
|
||||
) -> u16 {
|
||||
if has_head {
|
||||
render_vertical_split_line(buf, x, y, height, &[0], &[2], &[], style);
|
||||
} else {
|
||||
render_vertical_split_line(buf, x, y, height, &[], &[], &[], style);
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_vertical_split_line(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
height: u16,
|
||||
top_slit: &[u16],
|
||||
inner_slit: &[u16],
|
||||
bottom_slit: &[u16],
|
||||
style: NuStyle,
|
||||
) -> u16 {
|
||||
render_vertical_line(buf, x, y, height, style);
|
||||
|
||||
for &y in top_slit {
|
||||
render_top_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
for &y in inner_slit {
|
||||
render_inner_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
for &y in bottom_slit {
|
||||
render_bottom_connector(buf, x, y, style);
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
fn render_vertical_line_with_split(
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
|
@ -668,6 +714,12 @@ fn render_bottom_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
|
|||
buf.set_span(x, y, &span, 1);
|
||||
}
|
||||
|
||||
fn render_inner_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
|
||||
let style = nu_style_to_tui(style);
|
||||
let span = Span::styled("┼", style);
|
||||
buf.set_span(x, y, &span, 1);
|
||||
}
|
||||
|
||||
fn calculate_column_width(column: &[NuText]) -> usize {
|
||||
column
|
||||
.iter()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "nu-glob"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
description = """
|
||||
|
@ -14,4 +14,4 @@ categories = ["filesystem"]
|
|||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
doc-comment = "0.3"
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -23,5 +23,5 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-path = { path="../nu-path", version = "0.94.3" }
|
||||
# serde_json = "1.0"
|
||||
# nu-path = { path="../nu-path", version = "0.95.1" }
|
||||
# serde_json = "1.0"
|
|
@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"]
|
|||
description = "Nushell's integrated LSP server"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
|
||||
name = "nu-lsp"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "../nu-cli", version = "0.94.3" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-cli = { path = "../nu-cli", version = "0.95.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
|
||||
reedline = { workspace = true }
|
||||
|
||||
|
@ -23,8 +23,8 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-command = { path = "../nu-command", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.95.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
|
||||
assert-json-diff = "2.0"
|
||||
assert-json-diff = "2.0"
|
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-parser"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
|
||||
bytesize = { workspace = true }
|
||||
chrono = { default-features = false, features = ['std'], workspace = true }
|
||||
|
@ -27,4 +27,4 @@ serde_json = { workspace = true }
|
|||
rstest = { workspace = true, default-features = false }
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
plugin = ["nu-plugin-engine"]
|
|
@ -26,6 +26,7 @@ pub enum FlatShape {
|
|||
Flag,
|
||||
Float,
|
||||
Garbage,
|
||||
GlobInterpolation,
|
||||
GlobPattern,
|
||||
Int,
|
||||
InternalCall(DeclId),
|
||||
|
@ -67,6 +68,7 @@ impl FlatShape {
|
|||
FlatShape::Flag => "shape_flag",
|
||||
FlatShape::Float => "shape_float",
|
||||
FlatShape::Garbage => "shape_garbage",
|
||||
FlatShape::GlobInterpolation => "shape_glob_interpolation",
|
||||
FlatShape::GlobPattern => "shape_globpattern",
|
||||
FlatShape::Int => "shape_int",
|
||||
FlatShape::InternalCall(_) => "shape_internalcall",
|
||||
|
@ -277,7 +279,7 @@ fn flatten_expression_into(
|
|||
output[arg_start..].sort();
|
||||
}
|
||||
Expr::ExternalCall(head, args) => {
|
||||
if let Expr::String(..) = &head.expr {
|
||||
if let Expr::String(..) | Expr::GlobPattern(..) = &head.expr {
|
||||
output.push((head.span, FlatShape::External));
|
||||
} else {
|
||||
flatten_expression_into(working_set, head, output);
|
||||
|
@ -286,7 +288,7 @@ fn flatten_expression_into(
|
|||
for arg in args.as_ref() {
|
||||
match arg {
|
||||
ExternalArgument::Regular(expr) => {
|
||||
if let Expr::String(..) = &expr.expr {
|
||||
if let Expr::String(..) | Expr::GlobPattern(..) = &expr.expr {
|
||||
output.push((expr.span, FlatShape::ExternalArg));
|
||||
} else {
|
||||
flatten_expression_into(working_set, expr, output);
|
||||
|
@ -431,6 +433,25 @@ fn flatten_expression_into(
|
|||
}
|
||||
output.extend(flattened);
|
||||
}
|
||||
Expr::GlobInterpolation(exprs, quoted) => {
|
||||
let mut flattened = vec![];
|
||||
for expr in exprs {
|
||||
flatten_expression_into(working_set, expr, &mut flattened);
|
||||
}
|
||||
|
||||
if *quoted {
|
||||
// If we aren't a bare word interpolation, also highlight the outer quotes
|
||||
output.push((
|
||||
Span::new(expr.span.start, expr.span.start + 2),
|
||||
FlatShape::GlobInterpolation,
|
||||
));
|
||||
flattened.push((
|
||||
Span::new(expr.span.end - 1, expr.span.end),
|
||||
FlatShape::GlobInterpolation,
|
||||
));
|
||||
}
|
||||
output.extend(flattened);
|
||||
}
|
||||
Expr::Record(list) => {
|
||||
let outer_span = expr.span;
|
||||
let mut last_end = outer_span.start;
|
||||
|
|
|
@ -8,7 +8,6 @@ mod parse_keywords;
|
|||
mod parse_patterns;
|
||||
mod parse_shape_specs;
|
||||
mod parser;
|
||||
mod parser_path;
|
||||
mod type_check;
|
||||
|
||||
pub use deparse::{escape_for_script_arg, escape_quote_string};
|
||||
|
@ -18,8 +17,8 @@ pub use flatten::{
|
|||
pub use known_external::KnownExternal;
|
||||
pub use lex::{lex, lex_signature, Token, TokenContents};
|
||||
pub use lite_parser::{lite_parse, LiteBlock, LiteCommand};
|
||||
pub use nu_protocol::parser_path::*;
|
||||
pub use parse_keywords::*;
|
||||
pub use parser_path::*;
|
||||
|
||||
pub use parser::{
|
||||
is_math_expression_like, parse, parse_block, parse_expression, parse_external_call,
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::{
|
|||
exportable::Exportable,
|
||||
parse_block,
|
||||
parser::{parse_redirection, redirecting_builtin_error},
|
||||
parser_path::ParserPath,
|
||||
type_check::{check_block_input_output, type_compatible},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
@ -15,6 +14,7 @@ use nu_protocol::{
|
|||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
eval_const::eval_constant,
|
||||
parser_path::ParserPath,
|
||||
Alias, BlockId, DeclId, Module, ModuleId, ParseError, PositionalArg, ResolvedImportPattern,
|
||||
Span, Spanned, SyntaxShape, Type, Value, VarId,
|
||||
};
|
||||
|
@ -42,32 +42,44 @@ use crate::{
|
|||
};
|
||||
|
||||
/// These parser keywords can be aliased
|
||||
pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[b"overlay hide", b"overlay new", b"overlay use"];
|
||||
pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
||||
b"if",
|
||||
b"match",
|
||||
b"try",
|
||||
b"overlay",
|
||||
b"overlay hide",
|
||||
b"overlay new",
|
||||
b"overlay use",
|
||||
];
|
||||
|
||||
pub const RESERVED_VARIABLE_NAMES: [&str; 3] = ["in", "nu", "env"];
|
||||
|
||||
/// These parser keywords cannot be aliased (either not possible, or support not yet added)
|
||||
pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
||||
b"export",
|
||||
b"def",
|
||||
b"export def",
|
||||
b"for",
|
||||
b"extern",
|
||||
b"export extern",
|
||||
b"alias",
|
||||
b"export alias",
|
||||
b"export-env",
|
||||
b"const",
|
||||
b"def",
|
||||
b"extern",
|
||||
b"module",
|
||||
b"use",
|
||||
b"export",
|
||||
b"export alias",
|
||||
b"export const",
|
||||
b"export def",
|
||||
b"export extern",
|
||||
b"export module",
|
||||
b"export use",
|
||||
b"hide",
|
||||
// b"overlay",
|
||||
// b"overlay hide",
|
||||
// b"overlay new",
|
||||
// b"overlay use",
|
||||
b"for",
|
||||
b"loop",
|
||||
b"while",
|
||||
b"return",
|
||||
b"break",
|
||||
b"continue",
|
||||
b"let",
|
||||
b"const",
|
||||
b"mut",
|
||||
b"hide",
|
||||
b"export-env",
|
||||
b"source-env",
|
||||
b"source",
|
||||
b"where",
|
||||
b"register",
|
||||
|
@ -1192,7 +1204,7 @@ pub fn parse_export_in_block(
|
|||
"export alias" => parse_alias(working_set, lite_command, None),
|
||||
"export def" => parse_def(working_set, lite_command, None).0,
|
||||
"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
||||
"export use" => parse_use(working_set, lite_command).0,
|
||||
"export use" => parse_use(working_set, lite_command, None).0,
|
||||
"export module" => parse_module(working_set, lite_command, None).0,
|
||||
"export extern" => parse_extern(working_set, lite_command, None),
|
||||
_ => {
|
||||
|
@ -1211,6 +1223,7 @@ pub fn parse_export_in_module(
|
|||
working_set: &mut StateWorkingSet,
|
||||
lite_command: &LiteCommand,
|
||||
module_name: &[u8],
|
||||
parent_module: &mut Module,
|
||||
) -> (Pipeline, Vec<Exportable>) {
|
||||
let spans = &lite_command.parts[..];
|
||||
|
||||
|
@ -1416,7 +1429,8 @@ pub fn parse_export_in_module(
|
|||
pipe: lite_command.pipe,
|
||||
redirection: lite_command.redirection.clone(),
|
||||
};
|
||||
let (pipeline, exportables) = parse_use(working_set, &lite_command);
|
||||
let (pipeline, exportables) =
|
||||
parse_use(working_set, &lite_command, Some(parent_module));
|
||||
|
||||
let export_use_decl_id = if let Some(id) = working_set.find_decl(b"export use") {
|
||||
id
|
||||
|
@ -1759,7 +1773,7 @@ pub fn parse_module_block(
|
|||
))
|
||||
}
|
||||
b"use" => {
|
||||
let (pipeline, _) = parse_use(working_set, command);
|
||||
let (pipeline, _) = parse_use(working_set, command, Some(&mut module));
|
||||
|
||||
block.pipelines.push(pipeline)
|
||||
}
|
||||
|
@ -1774,7 +1788,7 @@ pub fn parse_module_block(
|
|||
}
|
||||
b"export" => {
|
||||
let (pipe, exportables) =
|
||||
parse_export_in_module(working_set, command, module_name);
|
||||
parse_export_in_module(working_set, command, module_name, &mut module);
|
||||
|
||||
for exportable in exportables {
|
||||
match exportable {
|
||||
|
@ -1884,6 +1898,48 @@ pub fn parse_module_block(
|
|||
(block, module, module_comments)
|
||||
}
|
||||
|
||||
fn module_needs_reloading(working_set: &StateWorkingSet, module_id: ModuleId) -> bool {
|
||||
let module = working_set.get_module(module_id);
|
||||
|
||||
fn submodule_need_reloading(working_set: &StateWorkingSet, submodule_id: ModuleId) -> bool {
|
||||
let submodule = working_set.get_module(submodule_id);
|
||||
let submodule_changed = if let Some((file_path, file_id)) = &submodule.file {
|
||||
let existing_contents = working_set.get_contents_of_file(*file_id);
|
||||
let file_contents = file_path.read(working_set);
|
||||
|
||||
if let (Some(existing), Some(new)) = (existing_contents, file_contents) {
|
||||
existing != new
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if submodule_changed {
|
||||
true
|
||||
} else {
|
||||
module_needs_reloading(working_set, submodule_id)
|
||||
}
|
||||
}
|
||||
|
||||
let export_submodule_changed = module
|
||||
.submodules
|
||||
.iter()
|
||||
.any(|(_, submodule_id)| submodule_need_reloading(working_set, *submodule_id));
|
||||
|
||||
if export_submodule_changed {
|
||||
return true;
|
||||
}
|
||||
|
||||
let private_submodule_changed = module
|
||||
.imported_modules
|
||||
.iter()
|
||||
.any(|submodule_id| submodule_need_reloading(working_set, *submodule_id));
|
||||
|
||||
private_submodule_changed
|
||||
}
|
||||
|
||||
/// Parse a module from a file.
|
||||
///
|
||||
/// The module name is inferred from the stem of the file, unless specified in `name_override`.
|
||||
|
@ -1922,23 +1978,26 @@ fn parse_module_file(
|
|||
|
||||
// Check if we've parsed the module before.
|
||||
if let Some(module_id) = working_set.find_module_by_span(new_span) {
|
||||
return Some(module_id);
|
||||
if !module_needs_reloading(working_set, module_id) {
|
||||
return Some(module_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file to the stack of files being processed.
|
||||
if let Err(e) = working_set.files.push(path.path_buf(), path_span) {
|
||||
if let Err(e) = working_set.files.push(path.clone().path_buf(), path_span) {
|
||||
working_set.error(e);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Parse the module
|
||||
let (block, module, module_comments) =
|
||||
let (block, mut module, module_comments) =
|
||||
parse_module_block(working_set, new_span, module_name.as_bytes());
|
||||
|
||||
// Remove the file from the stack of files being processed.
|
||||
working_set.files.pop();
|
||||
|
||||
let _ = working_set.add_block(Arc::new(block));
|
||||
module.file = Some((path, file_id));
|
||||
let module_id = working_set.add_module(&module_name, module, module_comments);
|
||||
|
||||
Some(module_id)
|
||||
|
@ -2228,6 +2287,7 @@ pub fn parse_module(
|
|||
pub fn parse_use(
|
||||
working_set: &mut StateWorkingSet,
|
||||
lite_command: &LiteCommand,
|
||||
parent_module: Option<&mut Module>,
|
||||
) -> (Pipeline, Vec<Exportable>) {
|
||||
let spans = &lite_command.parts;
|
||||
|
||||
|
@ -2373,12 +2433,14 @@ pub fn parse_use(
|
|||
);
|
||||
};
|
||||
|
||||
let mut imported_modules = vec![];
|
||||
let (definitions, errors) = module.resolve_import_pattern(
|
||||
working_set,
|
||||
module_id,
|
||||
&import_pattern.members,
|
||||
None,
|
||||
name_span,
|
||||
&mut imported_modules,
|
||||
);
|
||||
|
||||
working_set.parse_errors.extend(errors);
|
||||
|
@ -2420,6 +2482,9 @@ pub fn parse_use(
|
|||
|
||||
import_pattern.constants = constants.iter().map(|(_, id)| *id).collect();
|
||||
|
||||
if let Some(m) = parent_module {
|
||||
m.track_imported_modules(&imported_modules)
|
||||
}
|
||||
// Extend the current scope with the module's exportables
|
||||
working_set.use_decls(definitions.decls);
|
||||
working_set.use_modules(definitions.modules);
|
||||
|
@ -2853,6 +2918,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||
&[],
|
||||
Some(final_overlay_name.as_bytes()),
|
||||
call.head,
|
||||
&mut vec![],
|
||||
)
|
||||
} else {
|
||||
origin_module.resolve_import_pattern(
|
||||
|
@ -2863,6 +2929,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||
}],
|
||||
Some(final_overlay_name.as_bytes()),
|
||||
call.head,
|
||||
&mut vec![],
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -3740,28 +3807,37 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
|||
)
|
||||
})?;
|
||||
|
||||
let signatures = plugin
|
||||
let metadata_and_signatures = plugin
|
||||
.clone()
|
||||
.get(get_envs)
|
||||
.and_then(|p| p.get_signature())
|
||||
.and_then(|p| {
|
||||
let meta = p.get_metadata()?;
|
||||
let sigs = p.get_signature()?;
|
||||
Ok((meta, sigs))
|
||||
})
|
||||
.map_err(|err| {
|
||||
log::warn!("Error getting signatures: {err:?}");
|
||||
log::warn!("Error getting metadata and signatures: {err:?}");
|
||||
ParseError::LabeledError(
|
||||
"Error getting signatures".into(),
|
||||
"Error getting metadata and signatures".into(),
|
||||
err.to_string(),
|
||||
spans[0],
|
||||
)
|
||||
});
|
||||
|
||||
if let Ok(ref signatures) = signatures {
|
||||
// Add the loaded plugin to the delta
|
||||
working_set.update_plugin_registry(PluginRegistryItem::new(
|
||||
&identity,
|
||||
signatures.clone(),
|
||||
));
|
||||
match metadata_and_signatures {
|
||||
Ok((meta, sigs)) => {
|
||||
// Set the metadata on the plugin
|
||||
plugin.set_metadata(Some(meta.clone()));
|
||||
// Add the loaded plugin to the delta
|
||||
working_set.update_plugin_registry(PluginRegistryItem::new(
|
||||
&identity,
|
||||
meta,
|
||||
sigs.clone(),
|
||||
));
|
||||
Ok(sigs)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
|
||||
signatures
|
||||
},
|
||||
|sig| sig.map(|sig| vec![sig]),
|
||||
)?;
|
||||
|
|
|
@ -16,7 +16,6 @@ use nu_protocol::{
|
|||
IN_VARIABLE_ID,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
num::ParseIntError,
|
||||
str,
|
||||
|
@ -222,6 +221,209 @@ pub(crate) fn check_call(
|
|||
}
|
||||
}
|
||||
|
||||
/// Parses a string in the arg or head position of an external call.
|
||||
///
|
||||
/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
|
||||
/// or parentheses, it is parsed as a glob pattern so that tilde and glob expansion can be handled
|
||||
/// by `run-external`. Otherwise, we use a custom state machine to put together an interpolated
|
||||
/// string, where each balanced pair of quotes is parsed as a separate part of the string, and then
|
||||
/// concatenated together.
|
||||
///
|
||||
/// For example, `-foo="bar\nbaz"` becomes `$"-foo=bar\nbaz"`
|
||||
fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||
let contents = &working_set.get_span_contents(span);
|
||||
|
||||
if contents.starts_with(b"r#") {
|
||||
parse_raw_string(working_set, span)
|
||||
} else if contents
|
||||
.iter()
|
||||
.any(|b| matches!(b, b'"' | b'\'' | b'(' | b')'))
|
||||
{
|
||||
enum State {
|
||||
Bare {
|
||||
from: usize,
|
||||
},
|
||||
Quote {
|
||||
from: usize,
|
||||
quote_char: u8,
|
||||
escaped: bool,
|
||||
depth: i32,
|
||||
},
|
||||
}
|
||||
// Find the spans of parts of the string that can be parsed as their own strings for
|
||||
// concatenation.
|
||||
//
|
||||
// By passing each of these parts to `parse_string()`, we can eliminate the quotes and also
|
||||
// handle string interpolation.
|
||||
let make_span = |from: usize, index: usize| Span {
|
||||
start: span.start + from,
|
||||
end: span.start + index,
|
||||
};
|
||||
let mut spans = vec![];
|
||||
let mut state = State::Bare { from: 0 };
|
||||
let mut index = 0;
|
||||
while index < contents.len() {
|
||||
let ch = contents[index];
|
||||
match &mut state {
|
||||
State::Bare { from } => match ch {
|
||||
b'"' | b'\'' => {
|
||||
// Push bare string
|
||||
if index != *from {
|
||||
spans.push(make_span(*from, index));
|
||||
}
|
||||
// then transition to other state
|
||||
state = State::Quote {
|
||||
from: index,
|
||||
quote_char: ch,
|
||||
escaped: false,
|
||||
depth: 1,
|
||||
};
|
||||
}
|
||||
b'$' => {
|
||||
if let Some("e_char @ (b'"' | b'\'')) = contents.get(index + 1) {
|
||||
// Start a dollar quote (interpolated string)
|
||||
if index != *from {
|
||||
spans.push(make_span(*from, index));
|
||||
}
|
||||
state = State::Quote {
|
||||
from: index,
|
||||
quote_char,
|
||||
escaped: false,
|
||||
depth: 1,
|
||||
};
|
||||
// Skip over two chars (the dollar sign and the quote)
|
||||
index += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Continue to consume
|
||||
_ => (),
|
||||
},
|
||||
State::Quote {
|
||||
from,
|
||||
quote_char,
|
||||
escaped,
|
||||
depth,
|
||||
} => match ch {
|
||||
ch if ch == *quote_char && !*escaped => {
|
||||
// Count if there are more than `depth` quotes remaining
|
||||
if contents[index..]
|
||||
.iter()
|
||||
.filter(|b| *b == quote_char)
|
||||
.count() as i32
|
||||
> *depth
|
||||
{
|
||||
// Increment depth to be greedy
|
||||
*depth += 1;
|
||||
} else {
|
||||
// Decrement depth
|
||||
*depth -= 1;
|
||||
}
|
||||
if *depth == 0 {
|
||||
// End of string
|
||||
spans.push(make_span(*from, index + 1));
|
||||
// go back to Bare state
|
||||
state = State::Bare { from: index + 1 };
|
||||
}
|
||||
}
|
||||
b'\\' if !*escaped && *quote_char == b'"' => {
|
||||
// The next token is escaped so it doesn't count (only for double quote)
|
||||
*escaped = true;
|
||||
}
|
||||
_ => {
|
||||
*escaped = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// Add the final span
|
||||
match state {
|
||||
State::Bare { from } | State::Quote { from, .. } => {
|
||||
if from < contents.len() {
|
||||
spans.push(make_span(from, contents.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log the spans that will be parsed
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
let contents = spans
|
||||
.iter()
|
||||
.map(|span| String::from_utf8_lossy(working_set.get_span_contents(*span)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
trace!("parsing: external string, parts: {contents:?}")
|
||||
}
|
||||
|
||||
// Check if the whole thing is quoted. If not, it should be a glob
|
||||
let quoted =
|
||||
(contents.len() >= 3 && contents.starts_with(b"$\"") && contents.ends_with(b"\""))
|
||||
|| is_quoted(contents);
|
||||
|
||||
// Parse each as its own string
|
||||
let exprs: Vec<Expression> = spans
|
||||
.into_iter()
|
||||
.map(|span| parse_string(working_set, span))
|
||||
.collect();
|
||||
|
||||
if exprs
|
||||
.iter()
|
||||
.all(|expr| matches!(expr.expr, Expr::String(..)))
|
||||
{
|
||||
// If the exprs are all strings anyway, just collapse into a single string.
|
||||
let string = exprs
|
||||
.into_iter()
|
||||
.map(|expr| {
|
||||
let Expr::String(contents) = expr.expr else {
|
||||
unreachable!("already checked that this was a String")
|
||||
};
|
||||
contents
|
||||
})
|
||||
.collect::<String>();
|
||||
if quoted {
|
||||
Expression::new(working_set, Expr::String(string), span, Type::String)
|
||||
} else {
|
||||
Expression::new(
|
||||
working_set,
|
||||
Expr::GlobPattern(string, false),
|
||||
span,
|
||||
Type::Glob,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Flatten any string interpolations contained with the exprs.
|
||||
let exprs = exprs
|
||||
.into_iter()
|
||||
.flat_map(|expr| match expr.expr {
|
||||
Expr::StringInterpolation(subexprs) => subexprs,
|
||||
_ => vec![expr],
|
||||
})
|
||||
.collect();
|
||||
// Make an interpolation out of the expressions. Use `GlobInterpolation` if it's a bare
|
||||
// word, so that the unquoted state can get passed through to `run-external`.
|
||||
if quoted {
|
||||
Expression::new(
|
||||
working_set,
|
||||
Expr::StringInterpolation(exprs),
|
||||
span,
|
||||
Type::String,
|
||||
)
|
||||
} else {
|
||||
Expression::new(
|
||||
working_set,
|
||||
Expr::GlobInterpolation(exprs, false),
|
||||
span,
|
||||
Type::Glob,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parse_glob_pattern(working_set, span)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
|
||||
let contents = working_set.get_span_contents(span);
|
||||
|
||||
|
@ -229,8 +431,6 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External
|
|||
ExternalArgument::Regular(parse_dollar_expr(working_set, span))
|
||||
} else if contents.starts_with(b"[") {
|
||||
ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any))
|
||||
} else if contents.starts_with(b"r#") {
|
||||
ExternalArgument::Regular(parse_raw_string(working_set, span))
|
||||
} else if contents.len() > 3
|
||||
&& contents.starts_with(b"...")
|
||||
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
|
||||
|
@ -241,18 +441,7 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External
|
|||
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
||||
))
|
||||
} else {
|
||||
// Eval stage trims the quotes, so we don't have to do the same thing when parsing.
|
||||
let (contents, err) = unescape_string_preserving_quotes(contents, span);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err);
|
||||
}
|
||||
|
||||
ExternalArgument::Regular(Expression::new(
|
||||
working_set,
|
||||
Expr::String(contents),
|
||||
span,
|
||||
Type::String,
|
||||
))
|
||||
ExternalArgument::Regular(parse_external_string(working_set, span))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,18 +463,7 @@ pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
|||
let arg = parse_expression(working_set, &[head_span]);
|
||||
Box::new(arg)
|
||||
} else {
|
||||
// Eval stage will unquote the string, so we don't bother with that here
|
||||
let (contents, err) = unescape_string_preserving_quotes(&head_contents, head_span);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err)
|
||||
}
|
||||
|
||||
Box::new(Expression::new(
|
||||
working_set,
|
||||
Expr::String(contents),
|
||||
head_span,
|
||||
Type::String,
|
||||
))
|
||||
Box::new(parse_external_string(working_set, head_span))
|
||||
};
|
||||
|
||||
let args = spans[1..]
|
||||
|
@ -756,15 +934,12 @@ pub fn parse_internal_call(
|
|||
let output = signature.get_output_type();
|
||||
|
||||
// storing the var ID for later due to borrowing issues
|
||||
let lib_dirs_var_id = if decl.is_builtin() {
|
||||
match decl.name() {
|
||||
"use" | "overlay use" | "source-env" | "nu-check" => {
|
||||
find_dirs_var(working_set, LIB_DIRS_VAR)
|
||||
}
|
||||
_ => None,
|
||||
let lib_dirs_var_id = match decl.name() {
|
||||
"use" | "overlay use" | "source-env" if decl.is_keyword() => {
|
||||
find_dirs_var(working_set, LIB_DIRS_VAR)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
"nu-check" if decl.is_builtin() => find_dirs_var(working_set, LIB_DIRS_VAR),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// The index into the positional parameter in the definition
|
||||
|
@ -2639,23 +2814,6 @@ pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option<Pars
|
|||
}
|
||||
}
|
||||
|
||||
/// XXX: This is here temporarily as a patch, but we should replace this with properly representing
|
||||
/// the quoted state of a string in the AST
|
||||
fn unescape_string_preserving_quotes(bytes: &[u8], span: Span) -> (String, Option<ParseError>) {
|
||||
let (bytes, err) = if bytes.starts_with(b"\"") {
|
||||
let (bytes, err) = unescape_string(bytes, span);
|
||||
(Cow::Owned(bytes), err)
|
||||
} else {
|
||||
(Cow::Borrowed(bytes), None)
|
||||
};
|
||||
|
||||
// The original code for args used lossy conversion here, even though that's not what we
|
||||
// typically use for strings. Revisit whether that's actually desirable later, but don't
|
||||
// want to introduce a breaking change for this patch.
|
||||
let token = String::from_utf8_lossy(&bytes).into_owned();
|
||||
(token, err)
|
||||
}
|
||||
|
||||
pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||
trace!("parsing: string");
|
||||
|
||||
|
@ -2670,6 +2828,36 @@ pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression
|
|||
if bytes[0] != b'\'' && bytes[0] != b'"' && bytes[0] != b'`' && bytes.contains(&b'(') {
|
||||
return parse_string_interpolation(working_set, span);
|
||||
}
|
||||
// Check for unbalanced quotes:
|
||||
{
|
||||
if bytes.starts_with(b"\"")
|
||||
&& (bytes.iter().filter(|ch| **ch == b'"').count() > 1 && !bytes.ends_with(b"\""))
|
||||
{
|
||||
let close_delimiter_index = bytes
|
||||
.iter()
|
||||
.skip(1)
|
||||
.position(|ch| *ch == b'"')
|
||||
.expect("Already check input bytes contains at least two double quotes");
|
||||
// needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
|
||||
let span = Span::new(span.start + close_delimiter_index + 2, span.end);
|
||||
working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
|
||||
return garbage(working_set, span);
|
||||
}
|
||||
|
||||
if bytes.starts_with(b"\'")
|
||||
&& (bytes.iter().filter(|ch| **ch == b'\'').count() > 1 && !bytes.ends_with(b"\'"))
|
||||
{
|
||||
let close_delimiter_index = bytes
|
||||
.iter()
|
||||
.skip(1)
|
||||
.position(|ch| *ch == b'\'')
|
||||
.expect("Already check input bytes contains at least two double quotes");
|
||||
// needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
|
||||
let span = Span::new(span.start + close_delimiter_index + 2, span.end);
|
||||
working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
|
||||
return garbage(working_set, span);
|
||||
}
|
||||
}
|
||||
|
||||
let (s, err) = unescape_unquote_string(bytes, span);
|
||||
if let Some(err) = err {
|
||||
|
@ -5219,7 +5407,7 @@ pub fn parse_builtin_commands(
|
|||
}
|
||||
b"alias" => parse_alias(working_set, lite_command, None),
|
||||
b"module" => parse_module(working_set, lite_command, None).0,
|
||||
b"use" => parse_use(working_set, lite_command).0,
|
||||
b"use" => parse_use(working_set, lite_command, None).0,
|
||||
b"overlay" => {
|
||||
if let Some(redirection) = lite_command.redirection.as_ref() {
|
||||
working_set.error(redirecting_builtin_error("overlay", redirection));
|
||||
|
@ -6012,7 +6200,7 @@ pub fn discover_captures_in_expr(
|
|||
}
|
||||
Expr::String(_) => {}
|
||||
Expr::RawString(_) => {}
|
||||
Expr::StringInterpolation(exprs) => {
|
||||
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
|
||||
for expr in exprs {
|
||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use nu_parser::*;
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, ExternalArgument, PathMember, Range},
|
||||
ast::{Argument, Call, Expr, Expression, ExternalArgument, PathMember, Range},
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
|
@ -182,7 +182,7 @@ pub fn multi_test_parse_int() {
|
|||
Test(
|
||||
"ranges or relative paths not confused for int",
|
||||
b"./a/b",
|
||||
Expr::String("./a/b".into()),
|
||||
Expr::GlobPattern("./a/b".into(), false),
|
||||
None,
|
||||
),
|
||||
Test(
|
||||
|
@ -694,6 +694,50 @@ pub fn parse_call_missing_req_flag() {
|
|||
));
|
||||
}
|
||||
|
||||
fn test_external_call(input: &str, tag: &str, f: impl FnOnce(&Expression, &[ExternalArgument])) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
||||
assert!(
|
||||
working_set.parse_errors.is_empty(),
|
||||
"{tag}: errors: {:?}",
|
||||
working_set.parse_errors
|
||||
);
|
||||
|
||||
let pipeline = &block.pipelines[0];
|
||||
assert_eq!(1, pipeline.len());
|
||||
let element = &pipeline.elements[0];
|
||||
match &element.expr.expr {
|
||||
Expr::ExternalCall(name, args) => f(name, args),
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in pipeline: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_external_call_interpolation(
|
||||
tag: &str,
|
||||
subexpr_count: usize,
|
||||
quoted: bool,
|
||||
expr: &Expression,
|
||||
) -> bool {
|
||||
match &expr.expr {
|
||||
Expr::StringInterpolation(exprs) => {
|
||||
assert!(quoted, "{tag}: quoted");
|
||||
assert_eq!(expr.ty, Type::String, "{tag}: expr.ty");
|
||||
assert_eq!(subexpr_count, exprs.len(), "{tag}: subexpr_count");
|
||||
true
|
||||
}
|
||||
Expr::GlobInterpolation(exprs, is_quoted) => {
|
||||
assert_eq!(quoted, *is_quoted, "{tag}: quoted");
|
||||
assert_eq!(expr.ty, Type::Glob, "{tag}: expr.ty");
|
||||
assert_eq!(subexpr_count, exprs.len(), "{tag}: subexpr_count");
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("foo-external-call", "foo-external-call", "bare word")]
|
||||
#[case("^foo-external-call", "foo-external-call", "bare word with caret")]
|
||||
|
@ -713,200 +757,370 @@ pub fn parse_call_missing_req_flag() {
|
|||
r"foo\external-call",
|
||||
"bare word with backslash and caret"
|
||||
)]
|
||||
#[case(
|
||||
"^'foo external call'",
|
||||
"'foo external call'",
|
||||
"single quote with caret"
|
||||
)]
|
||||
#[case(
|
||||
"^'foo/external call'",
|
||||
"'foo/external call'",
|
||||
"single quote with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r"^'foo\external call'",
|
||||
r"'foo\external call'",
|
||||
"single quote with backslash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo external call""#,
|
||||
r#""foo external call""#,
|
||||
"double quote with caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo/external call""#,
|
||||
r#""foo/external call""#,
|
||||
"double quote with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo\\external call""#,
|
||||
r#""foo\external call""#,
|
||||
"double quote with backslash and caret"
|
||||
)]
|
||||
#[case("`foo external call`", "`foo external call`", "backtick quote")]
|
||||
#[case("`foo external call`", "foo external call", "backtick quote")]
|
||||
#[case(
|
||||
"^`foo external call`",
|
||||
"`foo external call`",
|
||||
"foo external call",
|
||||
"backtick quote with caret"
|
||||
)]
|
||||
#[case(
|
||||
"`foo/external call`",
|
||||
"`foo/external call`",
|
||||
"foo/external call",
|
||||
"backtick quote with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
"^`foo/external call`",
|
||||
"`foo/external call`",
|
||||
"foo/external call",
|
||||
"backtick quote with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r"^`foo\external call`",
|
||||
r"`foo\external call`",
|
||||
r"foo\external call",
|
||||
"backtick quote with backslash"
|
||||
)]
|
||||
#[case(
|
||||
r"^`foo\external call`",
|
||||
r"`foo\external call`",
|
||||
r"foo\external call",
|
||||
"backtick quote with backslash and caret"
|
||||
)]
|
||||
fn test_external_call_name(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
||||
assert!(
|
||||
working_set.parse_errors.is_empty(),
|
||||
"{tag}: errors: {:?}",
|
||||
working_set.parse_errors
|
||||
);
|
||||
|
||||
let pipeline = &block.pipelines[0];
|
||||
assert_eq!(1, pipeline.len());
|
||||
let element = &pipeline.elements[0];
|
||||
match &element.expr.expr {
|
||||
Expr::ExternalCall(name, args) => {
|
||||
match &name.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!(expected, string);
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(0, args.len());
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in pipeline: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("^foo bar-baz", "bar-baz", "bare word")]
|
||||
#[case("^foo bar/baz", "bar/baz", "bare word with forward slash")]
|
||||
#[case(r"^foo bar\baz", r"bar\baz", "bare word with backslash")]
|
||||
#[case("^foo 'bar baz'", "'bar baz'", "single quote")]
|
||||
#[case("foo 'bar/baz'", "'bar/baz'", "single quote with forward slash")]
|
||||
#[case(r"foo 'bar\baz'", r"'bar\baz'", "single quote with backslash")]
|
||||
#[case(r#"^foo "bar baz""#, r#""bar baz""#, "double quote")]
|
||||
#[case(r#"^foo "bar/baz""#, r#""bar/baz""#, "double quote with forward slash")]
|
||||
#[case(r#"^foo "bar\\baz""#, r#""bar\baz""#, "double quote with backslash")]
|
||||
#[case("^foo `bar baz`", "`bar baz`", "backtick quote")]
|
||||
#[case("^foo `bar/baz`", "`bar/baz`", "backtick quote with forward slash")]
|
||||
#[case(r"^foo `bar\baz`", r"`bar\baz`", "backtick quote with backslash")]
|
||||
fn test_external_call_argument_regular(
|
||||
pub fn test_external_call_head_glob(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
||||
assert!(
|
||||
working_set.parse_errors.is_empty(),
|
||||
"{tag}: errors: {:?}",
|
||||
working_set.parse_errors
|
||||
);
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, is_quoted) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect name");
|
||||
assert!(!*is_quoted);
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(0, args.len());
|
||||
})
|
||||
}
|
||||
|
||||
let pipeline = &block.pipelines[0];
|
||||
assert_eq!(1, pipeline.len());
|
||||
let element = &pipeline.elements[0];
|
||||
match &element.expr.expr {
|
||||
Expr::ExternalCall(name, args) => {
|
||||
match &name.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!("foo", string, "{tag}: incorrect name");
|
||||
#[rstest]
|
||||
#[case(
|
||||
r##"^r#'foo-external-call'#"##,
|
||||
"foo-external-call",
|
||||
"raw string with caret"
|
||||
)]
|
||||
#[case(
|
||||
r##"^r#'foo/external-call'#"##,
|
||||
"foo/external-call",
|
||||
"raw string with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r##"^r#'foo\external-call'#"##,
|
||||
r"foo\external-call",
|
||||
"raw string with backslash and caret"
|
||||
)]
|
||||
pub fn test_external_call_head_raw_string(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::RawString(string) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(0, args.len());
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("^'foo external call'", "foo external call", "single quote with caret")]
|
||||
#[case(
|
||||
"^'foo/external call'",
|
||||
"foo/external call",
|
||||
"single quote with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r"^'foo\external call'",
|
||||
r"foo\external call",
|
||||
"single quote with backslash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo external call""#,
|
||||
r#"foo external call"#,
|
||||
"double quote with caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo/external call""#,
|
||||
r#"foo/external call"#,
|
||||
"double quote with forward slash and caret"
|
||||
)]
|
||||
#[case(
|
||||
r#"^"foo\\external call""#,
|
||||
r#"foo\external call"#,
|
||||
"double quote with backslash and caret"
|
||||
)]
|
||||
pub fn test_external_call_head_string(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!(expected, string);
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(0, args.len());
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r"~/.foo/(1)", 2, false, "unquoted interpolated string")]
|
||||
#[case(
|
||||
r"~\.foo(2)\(1)",
|
||||
4,
|
||||
false,
|
||||
"unquoted interpolated string with backslash"
|
||||
)]
|
||||
#[case(r"^~/.foo/(1)", 2, false, "unquoted interpolated string with caret")]
|
||||
#[case(r#"^$"~/.foo/(1)""#, 2, true, "quoted interpolated string with caret")]
|
||||
pub fn test_external_call_head_interpolated_string(
|
||||
#[case] input: &str,
|
||||
#[case] subexpr_count: usize,
|
||||
#[case] quoted: bool,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
if !check_external_call_interpolation(tag, subexpr_count, quoted, name) {
|
||||
panic!("{tag}: Unexpected expression in command name position: {name:?}");
|
||||
}
|
||||
assert_eq!(0, args.len());
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("^foo foo-external-call", "foo-external-call", "bare word")]
|
||||
#[case(
|
||||
"^foo foo/external-call",
|
||||
"foo/external-call",
|
||||
"bare word with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
r"^foo foo\external-call",
|
||||
r"foo\external-call",
|
||||
"bare word with backslash"
|
||||
)]
|
||||
#[case(
|
||||
"^foo `foo external call`",
|
||||
"foo external call",
|
||||
"backtick quote with caret"
|
||||
)]
|
||||
#[case(
|
||||
"^foo `foo/external call`",
|
||||
"foo/external call",
|
||||
"backtick quote with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
r"^foo `foo\external call`",
|
||||
r"foo\external call",
|
||||
"backtick quote with backslash"
|
||||
)]
|
||||
pub fn test_external_call_arg_glob(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, _) => {
|
||||
assert_eq!("foo", string, "{tag}: incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Regular(expr) => match &expr.expr {
|
||||
Expr::GlobPattern(string, is_quoted) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect arg");
|
||||
assert!(!*is_quoted);
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Regular(expr) => match &expr.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect arg");
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Spread(..) => {
|
||||
panic!("Unexpected external spread argument in command arg position: {other:?}")
|
||||
panic!("Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Spread(..) => {
|
||||
panic!("Unexpected external spread argument in command arg position: {other:?}")
|
||||
}
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in pipeline: {other:?}");
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r##"^foo r#'foo-external-call'#"##, "foo-external-call", "raw string")]
|
||||
#[case(
|
||||
r##"^foo r#'foo/external-call'#"##,
|
||||
"foo/external-call",
|
||||
"raw string with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
r##"^foo r#'foo\external-call'#"##,
|
||||
r"foo\external-call",
|
||||
"raw string with backslash"
|
||||
)]
|
||||
pub fn test_external_call_arg_raw_string(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, _) => {
|
||||
assert_eq!("foo", string, "{tag}: incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Regular(expr) => match &expr.expr {
|
||||
Expr::RawString(string) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect arg");
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Spread(..) => {
|
||||
panic!("Unexpected external spread argument in command arg position: {other:?}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("^foo 'foo external call'", "foo external call", "single quote")]
|
||||
#[case(
|
||||
"^foo 'foo/external call'",
|
||||
"foo/external call",
|
||||
"single quote with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
r"^foo 'foo\external call'",
|
||||
r"foo\external call",
|
||||
"single quote with backslash"
|
||||
)]
|
||||
#[case(r#"^foo "foo external call""#, r#"foo external call"#, "double quote")]
|
||||
#[case(
|
||||
r#"^foo "foo/external call""#,
|
||||
r#"foo/external call"#,
|
||||
"double quote with forward slash"
|
||||
)]
|
||||
#[case(
|
||||
r#"^foo "foo\\external call""#,
|
||||
r#"foo\external call"#,
|
||||
"double quote with backslash"
|
||||
)]
|
||||
pub fn test_external_call_arg_string(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, _) => {
|
||||
assert_eq!("foo", string, "{tag}: incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Regular(expr) => match &expr.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!(expected, string, "{tag}: incorrect arg");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Spread(..) => {
|
||||
panic!(
|
||||
"{tag}: Unexpected external spread argument in command arg position: {other:?}"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r"^foo ~/.foo/(1)", 2, false, "unquoted interpolated string")]
|
||||
#[case(r#"^foo $"~/.foo/(1)""#, 2, true, "quoted interpolated string")]
|
||||
pub fn test_external_call_arg_interpolated_string(
|
||||
#[case] input: &str,
|
||||
#[case] subexpr_count: usize,
|
||||
#[case] quoted: bool,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, _) => {
|
||||
assert_eq!("foo", string, "{tag}: incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("{tag}: Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Regular(expr) => {
|
||||
if !check_external_call_interpolation(tag, subexpr_count, quoted, expr) {
|
||||
panic!("Unexpected expression in command arg position: {expr:?}")
|
||||
}
|
||||
}
|
||||
other @ ExternalArgument::Spread(..) => {
|
||||
panic!("Unexpected external spread argument in command arg position: {other:?}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_external_call_argument_spread() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let block = parse(&mut working_set, None, b"^foo ...[a b c]", true);
|
||||
assert!(
|
||||
working_set.parse_errors.is_empty(),
|
||||
"errors: {:?}",
|
||||
working_set.parse_errors
|
||||
);
|
||||
let input = r"^foo ...[a b c]";
|
||||
let tag = "spread";
|
||||
|
||||
let pipeline = &block.pipelines[0];
|
||||
assert_eq!(1, pipeline.len());
|
||||
let element = &pipeline.elements[0];
|
||||
match &element.expr.expr {
|
||||
Expr::ExternalCall(name, args) => {
|
||||
match &name.expr {
|
||||
Expr::String(string) => {
|
||||
assert_eq!("foo", string, "incorrect name");
|
||||
test_external_call(input, tag, |name, args| {
|
||||
match &name.expr {
|
||||
Expr::GlobPattern(string, _) => {
|
||||
assert_eq!("foo", string, "incorrect name");
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Spread(expr) => match &expr.expr {
|
||||
Expr::List(items) => {
|
||||
assert_eq!(3, items.len());
|
||||
// that's good enough, don't really need to go so deep into it...
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in command name position: {other:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(1, args.len());
|
||||
match &args[0] {
|
||||
ExternalArgument::Spread(expr) => match &expr.expr {
|
||||
Expr::List(items) => {
|
||||
assert_eq!(3, items.len());
|
||||
// that's good enough, don't really need to go so deep into it...
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Regular(..) => {
|
||||
panic!(
|
||||
"Unexpected external regular argument in command arg position: {other:?}"
|
||||
)
|
||||
panic!("Unexpected expression in command arg position: {other:?}")
|
||||
}
|
||||
},
|
||||
other @ ExternalArgument::Regular(..) => {
|
||||
panic!("Unexpected external regular argument in command arg position: {other:?}")
|
||||
}
|
||||
}
|
||||
other => {
|
||||
panic!("Unexpected expression in pipeline: {other:?}");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1132,6 +1346,44 @@ mod string {
|
|||
assert_eq!(subexprs[0], &Expr::String("(1 + 3)(7 - 5)".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn parse_string_interpolation_bare() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(
|
||||
&mut working_set,
|
||||
None,
|
||||
b"\"\" ++ foo(1 + 3)bar(7 - 5)",
|
||||
true,
|
||||
);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
|
||||
assert_eq!(block.len(), 1);
|
||||
let pipeline = &block.pipelines[0];
|
||||
assert_eq!(pipeline.len(), 1);
|
||||
let element = &pipeline.elements[0];
|
||||
assert!(element.redirection.is_none());
|
||||
|
||||
let subexprs: Vec<&Expr> = match &element.expr.expr {
|
||||
Expr::BinaryOp(_, _, rhs) => match &rhs.expr {
|
||||
Expr::StringInterpolation(expressions) => {
|
||||
expressions.iter().map(|e| &e.expr).collect()
|
||||
}
|
||||
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
||||
},
|
||||
_ => panic!("Expected an `Expr::BinaryOp`"),
|
||||
};
|
||||
|
||||
assert_eq!(subexprs.len(), 4);
|
||||
|
||||
assert_eq!(subexprs[0], &Expr::String("foo".to_string()));
|
||||
assert!(matches!(subexprs[1], &Expr::FullCellPath(..)));
|
||||
assert_eq!(subexprs[2], &Expr::String("bar".to_string()));
|
||||
assert!(matches!(subexprs[3], &Expr::FullCellPath(..)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn parse_nested_expressions() {
|
||||
let engine_state = EngineState::new();
|
||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-path"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
exclude = ["/fuzz"]
|
||||
|
||||
[lib]
|
||||
|
@ -18,4 +18,4 @@ dirs-next = { workspace = true }
|
|||
omnipath = { workspace = true }
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies]
|
||||
pwd = { workspace = true }
|
||||
pwd = { workspace = true }
|
161
crates/nu-path/src/form.rs
Normal file
161
crates/nu-path/src/form.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
use std::ffi::OsStr;
|
||||
|
||||
mod private {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
// This trait should not be extended by external crates in order to uphold safety guarantees.
|
||||
// As such, this trait is put inside a private module to prevent external impls.
|
||||
// This ensures that all possible [`PathForm`]s can only be defined here and will:
|
||||
// - be zero sized (enforced anyways by the `repr(transparent)` on `Path`)
|
||||
// - have a no-op [`Drop`] implementation
|
||||
pub trait Sealed: 'static {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait for the different kinds of path forms.
|
||||
/// Each form has its own invariants that are guaranteed be upheld.
|
||||
/// The list of path forms are:
|
||||
/// - [`Any`]: a path with no invariants. It may be a relative or an absolute path.
|
||||
/// - [`Relative`]: a strictly relative path.
|
||||
/// - [`Absolute`]: a strictly absolute path.
|
||||
/// - [`Canonical`]: a path that must be in canonicalized form.
|
||||
pub trait PathForm: private::Sealed {}
|
||||
impl PathForm for Any {}
|
||||
impl PathForm for Relative {}
|
||||
impl PathForm for Absolute {}
|
||||
impl PathForm for Canonical {}
|
||||
|
||||
/// A path whose form is unknown. It could be a relative, absolute, or canonical path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Any;
|
||||
|
||||
impl private::Sealed for Any {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(_: &P) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A strictly relative path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Relative;
|
||||
|
||||
impl private::Sealed for Relative {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool {
|
||||
std::path::Path::new(path).is_relative()
|
||||
}
|
||||
}
|
||||
|
||||
/// An absolute path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Absolute;
|
||||
|
||||
impl private::Sealed for Absolute {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool {
|
||||
std::path::Path::new(path).is_absolute()
|
||||
}
|
||||
}
|
||||
|
||||
// A canonical path.
|
||||
//
|
||||
// An absolute path with all intermediate components normalized and symbolic links resolved.
|
||||
pub struct Canonical;
|
||||
|
||||
impl private::Sealed for Canonical {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(_: &P) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that may be relative paths.
|
||||
/// This includes only the [`Any`] and [`Relative`] path forms.
|
||||
///
|
||||
/// [`push`](crate::PathBuf::push) and [`join`](crate::Path::join)
|
||||
/// operations only support [`MaybeRelative`] path forms as input.
|
||||
pub trait MaybeRelative: PathForm {}
|
||||
impl MaybeRelative for Any {}
|
||||
impl MaybeRelative for Relative {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that may be absolute paths.
|
||||
/// This includes the [`Any`], [`Absolute`], and [`Canonical`] path forms.
|
||||
pub trait MaybeAbsolute: PathForm {}
|
||||
impl MaybeAbsolute for Any {}
|
||||
impl MaybeAbsolute for Absolute {}
|
||||
impl MaybeAbsolute for Canonical {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that are absolute paths.
|
||||
/// This includes only the [`Absolute`] and [`Canonical`] path forms.
|
||||
///
|
||||
/// Only [`PathForm`]s that implement this trait can be easily converted to [`std::path::Path`]
|
||||
/// or [`std::path::PathBuf`]. This is to encourage/force other Nushell crates to account for
|
||||
/// the emulated current working directory, instead of using the [`std::env::current_dir`].
|
||||
pub trait IsAbsolute: PathForm {}
|
||||
impl IsAbsolute for Absolute {}
|
||||
impl IsAbsolute for Canonical {}
|
||||
|
||||
/// A marker trait that signifies one [`PathForm`] can be used as or trivially converted to
|
||||
/// another [`PathForm`].
|
||||
///
|
||||
/// The list of possible conversions are:
|
||||
/// - [`Relative`], [`Absolute`], or [`Canonical`] into [`Any`].
|
||||
/// - [`Canonical`] into [`Absolute`].
|
||||
/// - Any form into itself.
|
||||
pub trait PathCast<Form: PathForm>: PathForm {}
|
||||
impl<Form: PathForm> PathCast<Form> for Form {}
|
||||
impl PathCast<Any> for Relative {}
|
||||
impl PathCast<Any> for Absolute {}
|
||||
impl PathCast<Any> for Canonical {}
|
||||
impl PathCast<Absolute> for Canonical {}
|
||||
|
||||
/// A trait used to specify the output [`PathForm`] of a path join operation.
|
||||
///
|
||||
/// The output path forms based on the left hand side path form are as follows:
|
||||
///
|
||||
/// | Left hand side | Output form |
|
||||
/// | --------------:|:------------ |
|
||||
/// | [`Any`] | [`Any`] |
|
||||
/// | [`Relative`] | [`Any`] |
|
||||
/// | [`Absolute`] | [`Absolute`] |
|
||||
/// | [`Canonical`] | [`Absolute`] |
|
||||
pub trait PathJoin: PathForm {
|
||||
type Output: PathForm;
|
||||
}
|
||||
impl PathJoin for Any {
|
||||
type Output = Self;
|
||||
}
|
||||
impl PathJoin for Relative {
|
||||
type Output = Any;
|
||||
}
|
||||
impl PathJoin for Absolute {
|
||||
type Output = Self;
|
||||
}
|
||||
impl PathJoin for Canonical {
|
||||
type Output = Absolute;
|
||||
}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that support setting the file name or extension.
|
||||
///
|
||||
/// This includes the [`Any`], [`Relative`], and [`Absolute`] path forms.
|
||||
/// [`Canonical`] paths do not support this, since appending file names and extensions that contain
|
||||
/// path separators can cause the path to no longer be canonical.
|
||||
pub trait PathSet: PathForm {}
|
||||
impl PathSet for Any {}
|
||||
impl PathSet for Relative {}
|
||||
impl PathSet for Absolute {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that support pushing [`MaybeRelative`] paths.
|
||||
///
|
||||
/// This includes only [`Any`] and [`Absolute`] path forms.
|
||||
/// Pushing onto a [`Relative`] path could cause it to become [`Absolute`],
|
||||
/// which is why they do not support pushing.
|
||||
/// In the future, a `push_rel` and/or a `try_push` method could be added as an alternative.
|
||||
/// Similarly, [`Canonical`] paths may become uncanonical if a non-canonical path is pushed onto it.
|
||||
pub trait PathPush: PathSet {}
|
||||
impl PathPush for Any {}
|
||||
impl PathPush for Absolute {}
|
|
@ -2,12 +2,15 @@ mod assert_path_eq;
|
|||
mod components;
|
||||
pub mod dots;
|
||||
pub mod expansions;
|
||||
pub mod form;
|
||||
mod helpers;
|
||||
mod path;
|
||||
mod tilde;
|
||||
mod trailing_slash;
|
||||
|
||||
pub use components::components;
|
||||
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
|
||||
pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir};
|
||||
pub use path::*;
|
||||
pub use tilde::expand_tilde;
|
||||
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};
|
||||
|
|
3095
crates/nu-path/src/path.rs
Normal file
3095
crates/nu-path/src/path.rs
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-core"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3", default-features = false }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1", default-features = false }
|
||||
|
||||
rmp-serde = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
@ -25,4 +25,4 @@ default = ["local-socket"]
|
|||
local-socket = ["interprocess", "nu-plugin-protocol/local-socket"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { workspace = true }
|
||||
windows = { workspace = true }
|
|
@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-engine"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.95.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false }
|
||||
|
||||
serde = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -31,4 +31,4 @@ local-socket = ["nu-plugin-core/local-socket"]
|
|||
windows = { workspace = true, features = [
|
||||
# For setting process creation flags
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
] }
|
|
@ -252,7 +252,7 @@ pub fn load_plugin_registry_item(
|
|||
})?;
|
||||
|
||||
match &plugin.data {
|
||||
PluginRegistryItemData::Valid { commands } => {
|
||||
PluginRegistryItemData::Valid { metadata, commands } => {
|
||||
let plugin = add_plugin_to_working_set(working_set, &identity)?;
|
||||
|
||||
// Ensure that the plugin is reset. We're going to load new signatures, so we want to
|
||||
|
@ -260,6 +260,9 @@ pub fn load_plugin_registry_item(
|
|||
// doesn't.
|
||||
plugin.reset()?;
|
||||
|
||||
// Set the plugin metadata from the file
|
||||
plugin.set_metadata(Some(metadata.clone()));
|
||||
|
||||
// Create the declarations from the commands
|
||||
for signature in commands {
|
||||
let decl = PluginDeclaration::new(plugin.clone(), signature.clone());
|
||||
|
|
|
@ -11,8 +11,8 @@ use nu_plugin_protocol::{
|
|||
PluginOutput, ProtocolInfo, StreamId, StreamMessage,
|
||||
};
|
||||
use nu_protocol::{
|
||||
ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginSignature, ShellError, Span,
|
||||
Spanned, Value,
|
||||
ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature,
|
||||
ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
|
@ -716,6 +716,7 @@ impl PluginInterface {
|
|||
|
||||
// Convert the call into one with a header and handle the stream, if necessary
|
||||
let (call, writer) = match call {
|
||||
PluginCall::Metadata => (PluginCall::Metadata, Default::default()),
|
||||
PluginCall::Signature => (PluginCall::Signature, Default::default()),
|
||||
PluginCall::CustomValueOp(value, op) => {
|
||||
(PluginCall::CustomValueOp(value, op), Default::default())
|
||||
|
@ -913,6 +914,17 @@ impl PluginInterface {
|
|||
self.receive_plugin_call_response(result.receiver, context, result.state)
|
||||
}
|
||||
|
||||
/// Get the metadata from the plugin.
|
||||
pub fn get_metadata(&self) -> Result<PluginMetadata, ShellError> {
|
||||
match self.plugin_call(PluginCall::Metadata, None)? {
|
||||
PluginCallResponse::Metadata(meta) => Ok(meta),
|
||||
PluginCallResponse::Error(err) => Err(err.into()),
|
||||
_ => Err(ShellError::PluginFailedToDecode {
|
||||
msg: "Received unexpected response to plugin Metadata call".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the command signatures from the plugin.
|
||||
pub fn get_signature(&self) -> Result<Vec<PluginSignature>, ShellError> {
|
||||
match self.plugin_call(PluginCall::Signature, None)? {
|
||||
|
@ -1206,6 +1218,7 @@ impl CurrentCallState {
|
|||
source: &PluginSource,
|
||||
) -> Result<(), ShellError> {
|
||||
match call {
|
||||
PluginCall::Metadata => Ok(()),
|
||||
PluginCall::Signature => Ok(()),
|
||||
PluginCall::Run(CallInfo { call, .. }) => self.prepare_call_args(call, source),
|
||||
PluginCall::CustomValueOp(_, op) => {
|
||||
|
|
|
@ -18,7 +18,7 @@ use nu_protocol::{
|
|||
ast::{Math, Operator},
|
||||
engine::Closure,
|
||||
ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData,
|
||||
PluginSignature, ShellError, Span, Spanned, Value,
|
||||
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -1019,6 +1019,25 @@ fn start_fake_plugin_call_responder(
|
|||
.expect("failed to spawn thread");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_get_metadata() -> Result<(), ShellError> {
|
||||
let test = TestCase::new();
|
||||
let manager = test.plugin("test");
|
||||
let interface = manager.get_interface();
|
||||
|
||||
start_fake_plugin_call_responder(manager, 1, |_| {
|
||||
vec![ReceivedPluginCallMessage::Response(
|
||||
PluginCallResponse::Metadata(PluginMetadata::new().with_version("test")),
|
||||
)]
|
||||
});
|
||||
|
||||
let metadata = interface.get_metadata()?;
|
||||
|
||||
assert_eq!(Some("test"), metadata.version.as_deref());
|
||||
assert!(test.has_unconsumed_write());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_get_signature() -> Result<(), ShellError> {
|
||||
let test = TestCase::new();
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::{PluginInterface, PluginSource};
|
|||
use nu_plugin_core::CommunicationMode;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
|
||||
PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -31,6 +31,8 @@ pub struct PersistentPlugin {
|
|||
struct MutableState {
|
||||
/// Reference to the plugin if running
|
||||
running: Option<RunningPlugin>,
|
||||
/// Metadata for the plugin, e.g. version.
|
||||
metadata: Option<PluginMetadata>,
|
||||
/// Plugin's preferred communication mode (if known)
|
||||
preferred_mode: Option<PreferredCommunicationMode>,
|
||||
/// Garbage collector config
|
||||
|
@ -59,6 +61,7 @@ impl PersistentPlugin {
|
|||
identity,
|
||||
mutable: Mutex::new(MutableState {
|
||||
running: None,
|
||||
metadata: None,
|
||||
preferred_mode: None,
|
||||
gc_config,
|
||||
}),
|
||||
|
@ -268,6 +271,16 @@ impl RegisteredPlugin for PersistentPlugin {
|
|||
self.stop_internal(true)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<PluginMetadata> {
|
||||
self.mutable.lock().ok().and_then(|m| m.metadata.clone())
|
||||
}
|
||||
|
||||
fn set_metadata(&self, metadata: Option<PluginMetadata>) {
|
||||
if let Ok(mut mutable) = self.mutable.lock() {
|
||||
mutable.metadata = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_gc_config(&self, gc_config: &PluginGcConfig) {
|
||||
if let Ok(mut mutable) = self.mutable.lock() {
|
||||
// Save the new config for future calls
|
||||
|
|
|
@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
|
||||
bincode = "1.3"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
@ -21,4 +21,4 @@ typetag = "0.2"
|
|||
|
||||
[features]
|
||||
default = ["local-socket"]
|
||||
local-socket = []
|
||||
local-socket = []
|
|
@ -23,7 +23,7 @@ pub mod test_util;
|
|||
|
||||
use nu_protocol::{
|
||||
ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData,
|
||||
PluginSignature, ShellError, Span, Spanned, Value,
|
||||
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
@ -119,6 +119,7 @@ pub struct ByteStreamInfo {
|
|||
/// Calls that a plugin can execute. The type parameter determines the input type.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum PluginCall<D> {
|
||||
Metadata,
|
||||
Signature,
|
||||
Run(CallInfo<D>),
|
||||
CustomValueOp(Spanned<PluginCustomValue>, CustomValueOp),
|
||||
|
@ -132,6 +133,7 @@ impl<D> PluginCall<D> {
|
|||
f: impl FnOnce(D) -> Result<T, ShellError>,
|
||||
) -> Result<PluginCall<T>, ShellError> {
|
||||
Ok(match self {
|
||||
PluginCall::Metadata => PluginCall::Metadata,
|
||||
PluginCall::Signature => PluginCall::Signature,
|
||||
PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?),
|
||||
PluginCall::CustomValueOp(custom_value, op) => {
|
||||
|
@ -143,6 +145,7 @@ impl<D> PluginCall<D> {
|
|||
/// The span associated with the call.
|
||||
pub fn span(&self) -> Option<Span> {
|
||||
match self {
|
||||
PluginCall::Metadata => None,
|
||||
PluginCall::Signature => None,
|
||||
PluginCall::Run(CallInfo { call, .. }) => Some(call.head),
|
||||
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
||||
|
@ -309,6 +312,7 @@ pub enum StreamMessage {
|
|||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum PluginCallResponse<D> {
|
||||
Error(LabeledError),
|
||||
Metadata(PluginMetadata),
|
||||
Signature(Vec<PluginSignature>),
|
||||
Ordering(Option<Ordering>),
|
||||
PipelineData(D),
|
||||
|
@ -323,6 +327,7 @@ impl<D> PluginCallResponse<D> {
|
|||
) -> Result<PluginCallResponse<T>, ShellError> {
|
||||
Ok(match self {
|
||||
PluginCallResponse::Error(err) => PluginCallResponse::Error(err),
|
||||
PluginCallResponse::Metadata(meta) => PluginCallResponse::Metadata(meta),
|
||||
PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs),
|
||||
PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering),
|
||||
PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "nu-plugin-test-support"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Testing support for Nushell plugins"
|
||||
|
@ -12,17 +12,17 @@ bench = false
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.94.3", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.94.3" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] }
|
||||
nu-parser = { path = "../nu-parser", version = "0.95.1", features = ["plugin"] }
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.95.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
similar = "2.5"
|
||||
|
||||
[dev-dependencies]
|
||||
typetag = "0.2"
|
||||
serde = "1.0"
|
||||
serde = "1.0"
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
use nu_plugin_engine::{GetPlugin, PluginInterface};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
|
||||
PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError,
|
||||
};
|
||||
|
||||
pub struct FakePersistentPlugin {
|
||||
|
@ -42,6 +42,12 @@ impl RegisteredPlugin for FakePersistentPlugin {
|
|||
None
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<PluginMetadata> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_metadata(&self, _metadata: Option<PluginMetadata>) {}
|
||||
|
||||
fn set_gc_config(&self, _gc_config: &PluginGcConfig) {
|
||||
// We don't have a GC
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@
|
|||
//! }
|
||||
//!
|
||||
//! impl Plugin for LowercasePlugin {
|
||||
//! fn version(&self) -> String {
|
||||
//! env!("CARGO_PKG_VERSION").into()
|
||||
//! }
|
||||
//!
|
||||
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
//! vec![Box::new(Lowercase)]
|
||||
//! }
|
||||
|
|
|
@ -53,6 +53,10 @@ struct IntoU32;
|
|||
struct IntoIntFromU32;
|
||||
|
||||
impl Plugin for CustomU32Plugin {
|
||||
fn version(&self) -> String {
|
||||
"0.0.0".into()
|
||||
}
|
||||
|
||||
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
|
||||
vec![Box::new(IntoU32), Box::new(IntoIntFromU32)]
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ struct HelloPlugin;
|
|||
struct Hello;
|
||||
|
||||
impl Plugin for HelloPlugin {
|
||||
fn version(&self) -> String {
|
||||
"0.0.0".into()
|
||||
}
|
||||
|
||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
vec![Box::new(Hello)]
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@ impl PluginCommand for Lowercase {
|
|||
}
|
||||
|
||||
impl Plugin for LowercasePlugin {
|
||||
fn version(&self) -> String {
|
||||
"0.0.0".into()
|
||||
}
|
||||
|
||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
vec![Box::new(Lowercase)]
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.94.3" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.94.3" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.94.3", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.95.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.95.1" }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false }
|
||||
|
||||
log = { workspace = true }
|
||||
thiserror = "1.0"
|
||||
|
@ -29,4 +29,4 @@ local-socket = ["nu-plugin-core/local-socket"]
|
|||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
# For setting the process group ID (EnterForeground / LeaveForeground)
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
|
@ -24,6 +24,10 @@
|
|||
//! struct MyCommand;
|
||||
//!
|
||||
//! impl Plugin for MyPlugin {
|
||||
//! fn version(&self) -> String {
|
||||
//! env!("CARGO_PKG_VERSION").into()
|
||||
//! }
|
||||
//!
|
||||
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
//! vec![Box::new(MyCommand)]
|
||||
//! }
|
||||
|
|
|
@ -60,6 +60,9 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
|
|||
/// }
|
||||
///
|
||||
/// # impl Plugin for LowercasePlugin {
|
||||
/// # fn version(&self) -> String {
|
||||
/// # "0.0.0".into()
|
||||
/// # }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// # vec![Box::new(Lowercase)]
|
||||
/// # }
|
||||
|
@ -195,6 +198,9 @@ pub trait PluginCommand: Sync {
|
|||
/// }
|
||||
///
|
||||
/// # impl Plugin for HelloPlugin {
|
||||
/// # fn version(&self) -> String {
|
||||
/// # "0.0.0".into()
|
||||
/// # }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// # vec![Box::new(Hello)]
|
||||
/// # }
|
||||
|
|
|
@ -11,8 +11,8 @@ use nu_plugin_protocol::{
|
|||
ProtocolInfo,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::Closure, Config, LabeledError, PipelineData, PluginSignature, ShellError, Span,
|
||||
Spanned, Value,
|
||||
engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature,
|
||||
ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap, HashMap},
|
||||
|
@ -29,6 +29,9 @@ use std::{
|
|||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
pub enum ReceivedPluginCall {
|
||||
Metadata {
|
||||
engine: EngineInterface,
|
||||
},
|
||||
Signature {
|
||||
engine: EngineInterface,
|
||||
},
|
||||
|
@ -280,8 +283,11 @@ impl InterfaceManager for EngineInterfaceManager {
|
|||
}
|
||||
};
|
||||
match call {
|
||||
// We just let the receiver handle it rather than trying to store signature here
|
||||
// or something
|
||||
// Ask the plugin for metadata
|
||||
PluginCall::Metadata => {
|
||||
self.send_plugin_call(ReceivedPluginCall::Metadata { engine: interface })
|
||||
}
|
||||
// Ask the plugin for signatures
|
||||
PluginCall::Signature => {
|
||||
self.send_plugin_call(ReceivedPluginCall::Signature { engine: interface })
|
||||
}
|
||||
|
@ -416,6 +422,13 @@ impl EngineInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/// Write a call response of plugin metadata.
|
||||
pub(crate) fn write_metadata(&self, metadata: PluginMetadata) -> Result<(), ShellError> {
|
||||
let response = PluginCallResponse::Metadata(metadata);
|
||||
self.write(PluginOutput::CallResponse(self.context()?, response))?;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
/// Write a call response of plugin signatures.
|
||||
///
|
||||
/// Any custom values in the examples will be rendered using `to_base_value()`.
|
||||
|
|
|
@ -322,6 +322,26 @@ fn manager_consume_goodbye_closes_plugin_call_channel() -> Result<(), ShellError
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manager_consume_call_metadata_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
||||
let mut manager = TestCase::new().engine();
|
||||
set_default_protocol_info(&mut manager)?;
|
||||
|
||||
let rx = manager
|
||||
.take_plugin_call_receiver()
|
||||
.expect("couldn't take receiver");
|
||||
|
||||
manager.consume(PluginInput::Call(0, PluginCall::Metadata))?;
|
||||
|
||||
match rx.try_recv().expect("call was not forwarded to receiver") {
|
||||
ReceivedPluginCall::Metadata { engine } => {
|
||||
assert_eq!(Some(0), engine.context);
|
||||
Ok(())
|
||||
}
|
||||
call => panic!("wrong call type: {call:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manager_consume_call_signature_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
||||
let mut manager = TestCase::new().engine();
|
||||
|
|
|
@ -16,7 +16,8 @@ use nu_plugin_core::{
|
|||
};
|
||||
use nu_plugin_protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput};
|
||||
use nu_protocol::{
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, ShellError, Spanned, Value,
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginMetadata,
|
||||
ShellError, Spanned, Value,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -52,6 +53,10 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
|||
/// struct Hello;
|
||||
///
|
||||
/// impl Plugin for HelloPlugin {
|
||||
/// fn version(&self) -> String {
|
||||
/// env!("CARGO_PKG_VERSION").into()
|
||||
/// }
|
||||
///
|
||||
/// fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// vec![Box::new(Hello)]
|
||||
/// }
|
||||
|
@ -89,6 +94,23 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
|||
/// # }
|
||||
/// ```
|
||||
pub trait Plugin: Sync {
|
||||
/// The version of the plugin.
|
||||
///
|
||||
/// The recommended implementation, which will use the version from your crate's `Cargo.toml`
|
||||
/// file:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use nu_plugin::{Plugin, PluginCommand};
|
||||
/// # struct MyPlugin;
|
||||
/// # impl Plugin for MyPlugin {
|
||||
/// fn version(&self) -> String {
|
||||
/// env!("CARGO_PKG_VERSION").into()
|
||||
/// }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> { vec![] }
|
||||
/// # }
|
||||
/// ```
|
||||
fn version(&self) -> String;
|
||||
|
||||
/// The commands supported by the plugin
|
||||
///
|
||||
/// Each [`PluginCommand`] contains both the signature of the command and the functionality it
|
||||
|
@ -216,6 +238,7 @@ pub trait Plugin: Sync {
|
|||
/// # struct MyPlugin;
|
||||
/// # impl MyPlugin { fn new() -> Self { Self }}
|
||||
/// # impl Plugin for MyPlugin {
|
||||
/// # fn version(&self) -> String { "0.0.0".into() }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {todo!();}
|
||||
/// # }
|
||||
/// fn main() {
|
||||
|
@ -504,6 +527,12 @@ where
|
|||
}
|
||||
|
||||
match plugin_call {
|
||||
// Send metadata back to nushell so it can be stored with the plugin signatures
|
||||
ReceivedPluginCall::Metadata { engine } => {
|
||||
engine
|
||||
.write_metadata(PluginMetadata::new().with_version(plugin.version()))
|
||||
.try_to_report(&engine)?;
|
||||
}
|
||||
// Sending the signature back to nushell to create the declaration definition
|
||||
ReceivedPluginCall::Signature { engine } => {
|
||||
let sigs = commands
|
||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -18,4 +18,4 @@ nu-ansi-term = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
heapless = { version = "0.8", default-features = false }
|
||||
rand = "0.8"
|
||||
rand = "0.8"
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-protocol"
|
||||
version = "0.94.3"
|
||||
version = "0.95.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -13,10 +13,10 @@ version = "0.94.3"
|
|||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||
nu-derive-value = { path = "../nu-derive-value", version = "0.94.3" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.95.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.95.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.95.1" }
|
||||
nu-derive-value = { path = "../nu-derive-value", version = "0.95.1" }
|
||||
|
||||
brotli = { workspace = true, optional = true }
|
||||
byte-unit = { version = "5.1", features = [ "serde" ] }
|
||||
|
@ -47,11 +47,11 @@ plugin = [
|
|||
serde_json = { workspace = true }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.94.3" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.95.1" }
|
||||
pretty_assertions = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
os_pipe = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
all-features = true
|
|
@ -32,8 +32,11 @@ pub enum Expr {
|
|||
Keyword(Box<Keyword>),
|
||||
ValueWithUnit(Box<ValueWithUnit>),
|
||||
DateTime(chrono::DateTime<FixedOffset>),
|
||||
/// The boolean is `true` if the string is quoted.
|
||||
Filepath(String, bool),
|
||||
/// The boolean is `true` if the string is quoted.
|
||||
Directory(String, bool),
|
||||
/// The boolean is `true` if the string is quoted.
|
||||
GlobPattern(String, bool),
|
||||
String(String),
|
||||
RawString(String),
|
||||
|
@ -43,6 +46,8 @@ pub enum Expr {
|
|||
Overlay(Option<BlockId>), // block ID of the overlay's origin module
|
||||
Signature(Box<Signature>),
|
||||
StringInterpolation(Vec<Expression>),
|
||||
/// The boolean is `true` if the string is quoted.
|
||||
GlobInterpolation(Vec<Expression>, bool),
|
||||
Nothing,
|
||||
Garbage,
|
||||
}
|
||||
|
@ -84,6 +89,7 @@ impl Expr {
|
|||
| Expr::RawString(_)
|
||||
| Expr::CellPath(_)
|
||||
| Expr::StringInterpolation(_)
|
||||
| Expr::GlobInterpolation(_, _)
|
||||
| Expr::Nothing => {
|
||||
// These expressions do not use the output of the pipeline in any meaningful way,
|
||||
// so we can discard the previous output by redirecting it to `Null`.
|
||||
|
|
|
@ -232,7 +232,7 @@ impl Expression {
|
|||
}
|
||||
false
|
||||
}
|
||||
Expr::StringInterpolation(items) => {
|
||||
Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => {
|
||||
for i in items {
|
||||
if i.has_in_variable(working_set) {
|
||||
return true;
|
||||
|
@ -441,7 +441,7 @@ impl Expression {
|
|||
Expr::Signature(_) => {}
|
||||
Expr::String(_) => {}
|
||||
Expr::RawString(_) => {}
|
||||
Expr::StringInterpolation(items) => {
|
||||
Expr::StringInterpolation(items) | Expr::GlobInterpolation(items, _) => {
|
||||
for i in items {
|
||||
i.replace_span(working_set, replaced, new_span)
|
||||
}
|
||||
|
|
|
@ -258,6 +258,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
|
|||
Expr::Signature(_) => "signature".to_string(),
|
||||
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
||||
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
||||
Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(),
|
||||
Expr::Subexpression(_) => "subexpression".to_string(),
|
||||
Expr::Table(_) => "table".to_string(),
|
||||
Expr::UnaryNot(_) => "unary not".to_string(),
|
||||
|
|
|
@ -81,7 +81,9 @@ pub(super) fn build_usage(comment_lines: &[&[u8]]) -> (String, String) {
|
|||
usage.push_str(&comment_line);
|
||||
}
|
||||
|
||||
if let Some((brief_usage, extra_usage)) = usage.split_once("\n\n") {
|
||||
if let Some((brief_usage, extra_usage)) = usage.split_once("\r\n\r\n") {
|
||||
(brief_usage.to_string(), extra_usage.to_string())
|
||||
} else if let Some((brief_usage, extra_usage)) = usage.split_once("\n\n") {
|
||||
(brief_usage.to_string(), extra_usage.to_string())
|
||||
} else {
|
||||
(usage, String::default())
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user