diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2225848cb4..2193598686 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,14 @@ updates: ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] + groups: + # Only update polars as a whole as there are many subcrates that need to + # be updated at once. We explicitly depend on some of them, so batch their + # updates to not take up dependabot PR slots with dysfunctional PRs + polars: + patterns: + - "polars" + - "polars-*" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index a0295b3b4d..694ff6e1bb 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -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 }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cb92b0902..25fcfde55e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,10 +33,10 @@ 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.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 - name: cargo fmt run: cargo fmt --all -- --check @@ -66,10 +66,10 @@ 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.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 - name: Tests run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} @@ -95,10 +95,10 @@ 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.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 - name: Install Nushell run: cargo install --path . --locked --no-default-features @@ -146,10 +146,10 @@ 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.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 - name: Clippy run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index d95ddd438a..8097b02fe6 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -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.10 + 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 @@ -122,15 +122,15 @@ jobs: echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml - name: Setup Rust toolchain and cache - uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` with: rustflags: '' - name: Setup Nushell - uses: hustcer/setup-nu@v3.10 + 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.10 + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d58501044..dd3371e0d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,23 +62,23 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - name: Update Rust Toolchain Target run: | echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml - name: Setup Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 + uses: actions-rust-lang/setup-rust-toolchain@v1.9.0 # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` with: cache: false rustflags: '' - name: Setup Nushell - uses: hustcer/setup-nu@v3.10 + 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 diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 709f09c2e7..95fc51b970 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -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.21.0 + uses: crate-ci/typos@v1.22.9 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..25731e87b2 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,26 @@ +cff-version: 1.2.0 +title: 'Nushell' +message: >- + If you use this software and wish to cite it, + you can use the metadata from this file. +type: software +authors: + - name: "The Nushell Project Team" +identifiers: + - type: url + value: 'https://github.com/nushell/nushell' + description: Repository +repository-code: 'https://github.com/nushell/nushell' +url: 'https://www.nushell.sh/' +abstract: >- + The goal of the Nushell project is to take the Unix + philosophy of shells, where pipes connect simple commands + together, and bring it to the modern style of development. + Thus, rather than being either a shell, or a programming + language, Nushell connects both by bringing a rich + programming language and a full-featured shell together + into one package. +keywords: + - nushell + - shell +license: MIT diff --git a/Cargo.lock b/Cargo.lock index 2d339ec7da..d4f759aed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,17 +478,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8" -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor 2.5.1", -] - [[package]] name = "brotli" version = "5.0.0" @@ -497,17 +486,7 @@ checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor 4.0.0", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "brotli-decompressor", ] [[package]] @@ -871,7 +850,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm", "strum", - "strum_macros 0.26.2", + "strum_macros", "unicode-width", ] @@ -941,6 +920,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1129,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" @@ -1160,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" @@ -1239,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" @@ -1295,6 +1325,9 @@ name = "either" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +dependencies = [ + "serde", +] [[package]] name = "eml-parser" @@ -1333,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" @@ -1343,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" @@ -1696,9 +1752,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.5.0", "libc", @@ -1794,6 +1850,7 @@ dependencies = [ "ahash 0.8.11", "allocator-api2", "rayon", + "serde", ] [[package]] @@ -1856,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" @@ -1908,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" @@ -1997,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" @@ -2034,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", @@ -2296,9 +2368,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -2310,9 +2382,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets 0.52.5", @@ -2524,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" @@ -2770,7 +2868,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.94.1" +version = "0.95.1" dependencies = [ "assert_cmd", "crossterm", @@ -2823,7 +2921,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.94.1" +version = "0.95.1" dependencies = [ "chrono", "crossterm", @@ -2858,7 +2956,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.94.1" +version = "0.95.1" dependencies = [ "indexmap", "miette", @@ -2870,7 +2968,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.94.1" +version = "0.95.1" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2895,7 +2993,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.94.1" +version = "0.95.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2907,7 +3005,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.94.1" +version = "0.95.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -2918,7 +3016,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-ansi-term", "nu-engine", @@ -2930,12 +3028,12 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.94.1" +version = "0.95.1" dependencies = [ "alphanumeric-sort", "base64 0.22.1", "bracoxide", - "brotli 5.0.0", + "brotli", "byteorder", "bytesize", "calamine", @@ -2945,6 +3043,7 @@ dependencies = [ "chrono-tz 0.8.6", "crossterm", "csv", + "deunicode", "dialoguer", "digest", "dirs-next", @@ -3028,7 +3127,7 @@ dependencies = [ "uu_mv", "uu_uname", "uu_whoami", - "uucore 0.0.25", + "uucore", "uuid", "v_htmlescape", "wax", @@ -3037,9 +3136,20 @@ dependencies = [ "winreg", ] +[[package]] +name = "nu-derive-value" +version = "0.95.1" +dependencies = [ + "convert_case", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "nu-engine" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-glob", "nu-path", @@ -3049,7 +3159,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.94.1" +version = "0.95.1" dependencies = [ "ansi-str", "anyhow", @@ -3074,14 +3184,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.94.1" +version = "0.95.1" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.94.1" +version = "0.95.1" dependencies = [ "linked-hash-map", "num-traits", @@ -3091,7 +3201,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.94.1" +version = "0.95.1" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3112,7 +3222,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.94.1" +version = "0.95.1" dependencies = [ "bytesize", "chrono", @@ -3128,7 +3238,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.94.1" +version = "0.95.1" dependencies = [ "dirs-next", "omnipath", @@ -3137,7 +3247,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.94.1" +version = "0.95.1" dependencies = [ "log", "nix", @@ -3152,7 +3262,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.94.1" +version = "0.95.1" dependencies = [ "interprocess", "log", @@ -3166,7 +3276,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.94.1" +version = "0.95.1" dependencies = [ "log", "nu-engine", @@ -3181,7 +3291,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.94.1" +version = "0.95.1" dependencies = [ "bincode", "nu-protocol", @@ -3193,7 +3303,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3211,7 +3321,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.94.1" +version = "0.95.1" dependencies = [ "heapless", "nu-ansi-term", @@ -3220,17 +3330,19 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.94.1" +version = "0.95.1" dependencies = [ - "brotli 5.0.0", + "brotli", "byte-unit", "chrono", "chrono-humanize", + "convert_case", "fancy-regex", "indexmap", "lru", "miette", "nix", + "nu-derive-value", "nu-path", "nu-system", "nu-test-support", @@ -3243,7 +3355,7 @@ dependencies = [ "serde", "serde_json", "strum", - "strum_macros 0.26.2", + "strum_macros", "tempfile", "thiserror", "typetag", @@ -3251,7 +3363,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.94.1" +version = "0.95.1" dependencies = [ "log", "miette", @@ -3262,7 +3374,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.94.1" +version = "0.95.1" dependencies = [ "chrono", "itertools 0.12.1", @@ -3280,7 +3392,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.94.1" +version = "0.95.1" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3294,7 +3406,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-utils", "unicode-width", @@ -3302,7 +3414,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-glob", "nu-path", @@ -3314,7 +3426,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.94.1" +version = "0.95.1" dependencies = [ "crossterm_winapi", "log", @@ -3340,7 +3452,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3350,7 +3462,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.94.1" +version = "0.95.1" dependencies = [ "eml-parser", "ical", @@ -3363,7 +3475,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.94.1" +version = "0.95.1" dependencies = [ "git2", "nu-plugin", @@ -3372,7 +3484,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.94.1" +version = "0.95.1" dependencies = [ "nu-plugin", "nu-protocol", @@ -3381,12 +3493,14 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.94.1" +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", @@ -3396,6 +3510,7 @@ dependencies = [ "nu-plugin", "nu-plugin-test-support", "nu-protocol", + "nu-utils", "num", "polars", "polars-arrow", @@ -3404,7 +3519,7 @@ dependencies = [ "polars-plan", "polars-utils", "serde", - "sqlparser 0.45.0", + "sqlparser", "tempfile", "typetag", "uuid", @@ -3412,19 +3527,22 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.94.1" +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.1" +version = "0.95.1" dependencies = [ "interprocess", "serde", @@ -3550,7 +3668,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.94.1" +version = "0.95.1" dependencies = [ "chrono", "fancy-regex", @@ -3647,9 +3765,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.1.2" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32" +checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" dependencies = [ "is-wsl", "libc", @@ -3731,9 +3849,9 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.1.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4014,9 +4132,9 @@ dependencies = [ [[package]] name = "polars" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea21b858b16b9c0e17a12db2800d11aa5b4bd182be6b3022eb537bbfc1f2db5" +checksum = "ce49e10a756f68eb99c102c6b2a0cbc0c583a0fa7263536ad0913d94be878d2d" dependencies = [ "getrandom", "polars-arrow", @@ -4034,9 +4152,9 @@ dependencies = [ [[package]] name = "polars-arrow" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "725b09f2b5ef31279b66e27bbab63c58d49d8f6696b66b1f46c7eaab95e80f75" +checksum = "b436f83f62e864f0d91871e26528f2c5552c7cf07c8d77547f1b8e3fde22bd27" dependencies = [ "ahash 0.8.11", "atoi", @@ -4082,9 +4200,9 @@ dependencies = [ [[package]] name = "polars-compute" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a796945b14b14fbb79b91ef0406e6fddca2be636e889f81ea5d6ee7d36efb4fe" +checksum = "f6758f834f07e622a2f859bebb542b2b7f8879b8704dbb2b2bbab460ddcdca4b" dependencies = [ "bytemuck", "either", @@ -4098,9 +4216,9 @@ dependencies = [ [[package]] name = "polars-core" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465f70d3e96b6d0b1a43c358ba451286b8c8bd56696feff020d65702aa33e35c" +checksum = "7ed262e9bdda15a12a9bfcfc9200bec5253335633dbd86cf5b94fda0194244b3" dependencies = [ "ahash 0.8.11", "bitflags 2.5.0", @@ -4132,9 +4250,9 @@ dependencies = [ [[package]] name = "polars-error" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757" +checksum = "53e1707a17475ba5e74c349154b415e3148a1a275e395965427971b5e53ad621" dependencies = [ "avro-schema", "polars-arrow-format", @@ -4144,10 +4262,30 @@ dependencies = [ ] [[package]] -name = "polars-io" -version = "0.39.2" +name = "polars-expr" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c8589e418cbe4a48228d64b2a8a40284a82ec3c98817c0c2bcc0267701338b" +checksum = "31a9688d5842e7a7fbad88e67a174778794a91d97d3bba1b3c09dd1656fee3b2" +dependencies = [ + "ahash 0.8.11", + "bitflags 2.5.0", + "once_cell", + "polars-arrow", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "smartstring", +] + +[[package]] +name = "polars-io" +version = "0.41.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18798dacd94fb9263f65f63f0feab0908675422646d6f7fc37043b85ff6dca35" dependencies = [ "ahash 0.8.11", "async-trait", @@ -4186,9 +4324,9 @@ dependencies = [ [[package]] name = "polars-json" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81224492a649a12b668480c0cf219d703f432509765d2717e72fe32ad16fc701" +checksum = "044ea319f667efbf8007c4c38171c2956e0e7f9b078eb66e31e82f80d1e14b51" dependencies = [ "ahash 0.8.11", "chrono", @@ -4207,18 +4345,21 @@ dependencies = [ [[package]] name = "polars-lazy" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2632b1af668e2058d5f8f916d8fbde3cac63d03ae29a705f598e41dcfeb7f" +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", @@ -4230,14 +4371,33 @@ dependencies = [ ] [[package]] -name = "polars-ops" -version = "0.39.2" +name = "polars-mem-engine" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdbdb4d9a92109bc2e0ce8e17af5ae8ab643bb5b7ee9d1d74f0aeffd1fbc95f" +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", - "base64 0.21.7", + "base64 0.22.1", "bytemuck", "chrono", "chrono-tz 0.8.6", @@ -4267,14 +4427,14 @@ dependencies = [ [[package]] name = "polars-parquet" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b421d2196f786fdfe162db614c8485f8308fe41575d4de634a39bbe460d1eb6a" +checksum = "c684638c36c60c691d707d414249fe8af4a19a35a39d418464b140fe23732e5d" dependencies = [ "ahash 0.8.11", "async-stream", - "base64 0.21.7", - "brotli 3.5.0", + "base64 0.22.1", + "brotli", "ethnum", "flate2", "futures", @@ -4282,9 +4442,11 @@ dependencies = [ "num-traits", "parquet-format-safe", "polars-arrow", + "polars-compute", "polars-error", "polars-utils", "seq-macro", + "serde", "simdutf8", "snap", "streaming-decompression", @@ -4293,9 +4455,9 @@ dependencies = [ [[package]] name = "polars-pipe" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48700f1d5bd56a15451e581f465c09541492750360f18637b196f995470a015c" +checksum = "832af9fbebc4c074d95fb19e1ef9e1bf37c343641238c2476febff296a7028ea" dependencies = [ "crossbeam-channel", "crossbeam-queue", @@ -4305,6 +4467,7 @@ dependencies = [ "polars-arrow", "polars-compute", "polars-core", + "polars-expr", "polars-io", "polars-ops", "polars-plan", @@ -4318,13 +4481,14 @@ dependencies = [ [[package]] name = "polars-plan" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb8e2302e20c44defd5be8cad9c96e75face63c3a5f609aced8c4ec3b3ac97d" +checksum = "801390ea815c05c9cf8337f3148090c9c10c9595a839fa0706b77cc2405b4466" dependencies = [ "ahash 0.8.11", "bytemuck", "chrono-tz 0.8.6", + "either", "hashbrown 0.14.5", "once_cell", "percent-encoding", @@ -4341,15 +4505,15 @@ dependencies = [ "regex", "serde", "smartstring", - "strum_macros 0.25.3", + "strum_macros", "version_check", ] [[package]] name = "polars-row" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a515bdc68c2ae3702e3de70d89601f3b71ca8137e282a226dddb53ee4bacfa2e" +checksum = "dee955e91b605fc91db4d0a8ea02609d3a09ff79256d905214a2a6f758cd6f7b" dependencies = [ "bytemuck", "polars-arrow", @@ -4359,29 +4523,33 @@ dependencies = [ [[package]] name = "polars-sql" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4bb7cc1c04c3023d1953b2f1dec50515e8fd8169a5a2bf4967b3b082232db7" +checksum = "d89c00a4b399501d5bd478e8e8022b9391047fe8570324ecba20c4e4833c0e87" dependencies = [ "hex", + "once_cell", "polars-arrow", "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.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc18e3ad92eec55db89d88f16c22d436559ba7030cf76f86f6ed7a754b673f1" +checksum = "9689b3aff99d64befe300495528bdc44c36d2656c3a8b242a790d4f43df027fc" dependencies = [ "atoi", + "bytemuck", "chrono", "chrono-tz 0.8.6", "now", @@ -4398,9 +4566,9 @@ dependencies = [ [[package]] name = "polars-utils" -version = "0.39.2" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c760b6c698cfe2fbbbd93d6cfb408db14ececfe1d92445dae2229ce1b5b21ae8" +checksum = "12081e346983a91e26f395597e1d53dea1b4ecd694653aee1cc402d2fae01f04" dependencies = [ "ahash 0.8.11", "bytemuck", @@ -4636,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", ] @@ -4715,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", ] @@ -4820,8 +4988,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea" +source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73" dependencies = [ "arboard", "chrono", @@ -4834,7 +5001,7 @@ dependencies = [ "serde_json", "strip-ansi-escapes", "strum", - "strum_macros 0.26.2", + "strum_macros", "thiserror", "unicode-segmentation", "unicode-width", @@ -5188,7 +5355,7 @@ dependencies = [ "ahash 0.8.11", "cssparser", "ego-tree", - "html5ever", + "html5ever 0.26.0", "once_cell", "selectors", "tendril", @@ -5404,9 +5571,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", @@ -5553,18 +5720,9 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.39.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7" -dependencies = [ - "log", -] - -[[package]] -name = "sqlparser" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7bbffee862a796d67959a89859d6b1046bb5016d63e23835ad0da182777bbe0" +checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25" dependencies = [ "log", ] @@ -5678,20 +5836,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.2", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.60", + "strum_macros", ] [[package]] @@ -6068,6 +6213,7 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -6289,6 +6435,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" @@ -6356,98 +6512,82 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_cp" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56" +checksum = "6fb99d355ccb02e8c514e4a1d93e4aa4eedea9837de24635dfd24c165971444e" dependencies = [ "clap", "filetime", "indicatif", "libc", "quick-error 2.0.1", - "uucore 0.0.26", + "uucore", "walkdir", "xattr", ] [[package]] name = "uu_mkdir" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae" +checksum = "219588fbc146f18188781208ac4034616c51cf151677b4e1f9caf63ca8a7f2cf" dependencies = [ "clap", - "uucore 0.0.26", + "uucore", ] [[package]] name = "uu_mktemp" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57" +checksum = "b1e79ad2c5911908fce23a6069c52ca82e1997e2ed4bf6abf2d867c79c3dc73f" dependencies = [ "clap", "rand", "tempfile", - "uucore 0.0.26", + "uucore", ] [[package]] name = "uu_mv" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022" +checksum = "cd57c8d02f8a99ed56ed9f6fddab403ee0e2bf9e8f3a5ca8f0f9e4d6e3e392a0" dependencies = [ "clap", "fs_extra", "indicatif", - "uucore 0.0.26", + "uucore", ] [[package]] name = "uu_uname" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5" +checksum = "ad1ca90f9b292bccaad0de70e6feccac5182c6713a5e1ca72d97bf3555b608b4" dependencies = [ "clap", "platform-info", - "uucore 0.0.26", + "uucore", ] [[package]] name = "uu_whoami" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4" +checksum = "bc7c52e42e0425710461700adc1063f468f2ba8a8ff83ee69ba661095ab7b77a" dependencies = [ "clap", "libc", - "uucore 0.0.26", + "uucore", "windows-sys 0.48.0", ] [[package]] name = "uucore" -version = "0.0.25" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4" -dependencies = [ - "clap", - "glob", - "libc", - "nix", - "once_cell", - "os_display", - "uucore_procs", - "wild", -] - -[[package]] -name = "uucore" -version = "0.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2ea2f77699e5ff5c7e001af588ceb34cae8b5f9af5496bea5a6476aaa8e780" +checksum = "7b54aad02cf7e96f5fafabb6b836efa73eef934783b17530095a29ffd4fdc154" dependencies = [ "clap", "dunce", @@ -6484,9 +6624,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", @@ -6729,6 +6869,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" @@ -7141,6 +7295,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" diff --git a/Cargo.toml b/Cargo.toml index a8576baa95..5701fe3958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.94.1" +version = "0.95.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -39,6 +39,7 @@ members = [ "crates/nu-lsp", "crates/nu-pretty-hex", "crates/nu-protocol", + "crates/nu-derive-value", "crates/nu-plugin", "crates/nu-plugin-core", "crates/nu-plugin-engine", @@ -74,10 +75,12 @@ chardetng = "0.1.17" chrono = { default-features = false, version = "0.4.34" } chrono-humanize = "0.2.3" chrono-tz = "0.8" +convert_case = "0.6" 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" @@ -93,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" @@ -117,17 +120,20 @@ num-format = "0.4" num-traits = "0.2" omnipath = "0.1" once_cell = "1.18" -open = "5.1" -os_pipe = { version = "1.1", features = ["io_safety"] } +open = "5.2" +os_pipe = { version = "1.2", features = ["io_safety"] } pathdiff = "0.2" percent-encoding = "2" pretty_assertions = "1.4" print-positions = "0.6" +proc-macro-error = { version = "1.0", default-features = false } +proc-macro2 = "1.0" procfs = "0.16.0" pwd = "1.3" quick-xml = "0.31.0" quickcheck = "1.0" quickcheck_macros = "1.0" +quote = "1.0" rand = "0.8" ratatui = "0.26" rayon = "1.10" @@ -147,6 +153,7 @@ serde_urlencoded = "0.7.1" serde_yaml = "0.9" sha2 = "0.10" strip-ansi-escapes = "0.2.0" +syn = "2.0" sysinfo = "0.30" tabled = { version = "0.14.0", default-features = false } tempfile = "3.10" @@ -159,14 +166,14 @@ unicode-segmentation = "1.11" unicode-width = "0.1" ureq = { version = "2.9", default-features = false } url = "2.2" -uu_cp = "0.0.25" -uu_mkdir = "0.0.25" -uu_mktemp = "0.0.25" -uu_mv = "0.0.25" -uu_whoami = "0.0.25" -uu_uname = "0.0.25" -uucore = "0.0.25" -uuid = "1.8.0" +uu_cp = "0.0.27" +uu_mkdir = "0.0.27" +uu_mktemp = "0.0.27" +uu_mv = "0.0.27" +uu_whoami = "0.0.27" +uu_uname = "0.0.27" +uucore = "0.0.27" +uuid = "1.9.1" v_htmlescape = "0.15.0" wax = "0.6" which = "6.0.0" @@ -174,27 +181,27 @@ windows = "0.54" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.94.1" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.94.1" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.94.1" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.94.1", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.94.1" } -nu-command = { path = "./crates/nu-command", version = "0.94.1" } -nu-engine = { path = "./crates/nu-engine", version = "0.94.1" } -nu-explore = { path = "./crates/nu-explore", version = "0.94.1" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.94.1" } -nu-parser = { path = "./crates/nu-parser", version = "0.94.1" } -nu-path = { path = "./crates/nu-path", version = "0.94.1" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.94.1" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.94.1" } -nu-std = { path = "./crates/nu-std", version = "0.94.1" } -nu-system = { path = "./crates/nu-system", version = "0.94.1" } -nu-utils = { path = "./crates/nu-utils", version = "0.94.1" } - +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"] } crossterm = { workspace = true } ctrlc = { workspace = true } +dirs-next = { workspace = true } log = { workspace = true } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } mimalloc = { version = "0.1.42", default-features = false, optional = true } @@ -218,9 +225,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.94.1" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.94.1" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.94.1" } +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" @@ -244,7 +251,6 @@ default = ["default-no-clipboard", "system-clipboard"] # See https://github.com/nushell/nushell/pull/11535 default-no-clipboard = [ "plugin", - "which-support", "trash-support", "sqlite", "mimalloc", @@ -264,7 +270,6 @@ system-clipboard = [ ] # Stable (Default) -which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"] trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"] # SQLite commands for nushell @@ -298,7 +303,7 @@ bench = false # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious [patch.crates-io] -# reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # Run all benchmarks with `cargo bench` diff --git a/README.md b/README.md index 0bfb7fc0c3..9b88a92528 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/ac Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers: -[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg?columns=3)](https://repology.org/project/nushell/versions) For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md). @@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list. - [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell) - [Dorothy](http://github.com/bevry/dorothy) - [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell) +- [x-cmd](https://x-cmd.com/mod/nu) ## Contributing diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 84552daef9..7a23715c8d 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) { &mut engine, &mut stack, PipelineData::empty(), - None, - false, + Default::default(), ) .unwrap(); @@ -90,8 +89,7 @@ fn bench_command( &mut engine, &mut stack, PipelineData::empty(), - None, - false, + Default::default(), ) .unwrap(), ); diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 516afd9f0b..2ed504d502 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -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.1" +version = "0.95.1" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" } -nu-command = { path = "../nu-command", version = "0.94.1" } -nu-test-support = { path = "../nu-test-support", version = "0.94.1" } +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.1" } -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-path = { path = "../nu-path", version = "0.94.1" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.1", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } -nu-color-config = { path = "../nu-color-config", version = "0.94.1" } +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"] } diff --git a/crates/nu-cli/src/completions/base.rs b/crates/nu-cli/src/completions/base.rs index 0debabe688..cf13dae68c 100644 --- a/crates/nu-cli/src/completions/base.rs +++ b/crates/nu-cli/src/completions/base.rs @@ -1,13 +1,12 @@ -use crate::completions::{CompletionOptions, SortBy}; +use crate::completions::CompletionOptions; use nu_protocol::{ engine::{Stack, StateWorkingSet}, - levenshtein_distance, Span, + Span, }; use reedline::Suggestion; -// Completer trait represents the three stages of the completion -// fetch, filter and sort pub trait Completer { + /// Fetch, filter, and sort completions #[allow(clippy::too_many_arguments)] fn fetch( &mut self, @@ -19,32 +18,6 @@ pub trait Completer { pos: usize, options: &CompletionOptions, ) -> Vec; - - fn get_sort_by(&self) -> SortBy { - SortBy::Ascending - } - - fn sort(&self, items: Vec, prefix: Vec) -> Vec { - let prefix_str = String::from_utf8_lossy(&prefix).to_string(); - let mut filtered_items = items; - - // Sort items - match self.get_sort_by() { - SortBy::LevenshteinDistance => { - filtered_items.sort_by(|a, b| { - let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value); - let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value); - a_distance.cmp(&b_distance) - }); - } - SortBy::Ascending => { - filtered_items.sort_by(|a, b| a.suggestion.value.cmp(&b.suggestion.value)); - } - SortBy::None => {} - }; - - filtered_items - } } #[derive(Debug, Default, PartialEq)] diff --git a/crates/nu-cli/src/completions/command_completions.rs b/crates/nu-cli/src/completions/command_completions.rs index 2549854540..e37f8b2576 100644 --- a/crates/nu-cli/src/completions/command_completions.rs +++ b/crates/nu-cli/src/completions/command_completions.rs @@ -9,7 +9,7 @@ use nu_protocol::{ }; use reedline::Suggestion; -use super::SemanticSuggestion; +use super::{completion_common::sort_suggestions, SemanticSuggestion}; pub struct CommandCompletion { flattened: Vec<(Span, FlatShape)>, @@ -161,7 +161,7 @@ impl Completer for CommandCompletion { &mut self, working_set: &StateWorkingSet, _stack: &Stack, - _prefix: Vec, + prefix: Vec, span: Span, offset: usize, pos: usize, @@ -198,7 +198,11 @@ impl Completer for CommandCompletion { }; if !subcommands.is_empty() { - return subcommands; + return sort_suggestions( + &String::from_utf8_lossy(&prefix), + subcommands, + SortBy::LevenshteinDistance, + ); } let config = working_set.get_config(); @@ -223,11 +227,11 @@ impl Completer for CommandCompletion { vec![] }; - subcommands.into_iter().chain(commands).collect::>() - } - - fn get_sort_by(&self) -> SortBy { - SortBy::LevenshteinDistance + sort_suggestions( + &String::from_utf8_lossy(&prefix), + commands, + SortBy::LevenshteinDistance, + ) } } diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 007a0e288a..78398b4d19 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -51,8 +51,7 @@ impl NuCompleter { ..Default::default() }; - // Fetch - let mut suggestions = completer.fetch( + completer.fetch( working_set, &self.stack, prefix.clone(), @@ -60,12 +59,7 @@ impl NuCompleter { offset, pos, &options, - ); - - // Sort - suggestions = completer.sort(suggestions, prefix); - - suggestions + ) } fn external_completion( diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index 4a8b0d57ac..b3bca778a9 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -1,16 +1,21 @@ -use crate::completions::{matches, CompletionOptions}; +use crate::{ + completions::{matches, CompletionOptions}, + SemanticSuggestion, +}; use nu_ansi_term::Style; use nu_engine::env_to_string; -use nu_path::home_dir; +use nu_path::{expand_to_real_path, home_dir}; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - Span, + levenshtein_distance, Span, }; use nu_utils::get_ls_colors; use std::path::{ is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR, }; +use super::SortBy; + #[derive(Clone, Default)] pub struct PathBuiltFromString { parts: Vec, @@ -45,6 +50,7 @@ fn complete_rec( return completions; }; + let mut entries = Vec::new(); for entry in result.filter_map(|e| e.ok()) { let entry_name = entry.file_name().to_string_lossy().into_owned(); let entry_isdir = entry.path().is_dir(); @@ -53,20 +59,26 @@ fn complete_rec( built.isdir = entry_isdir; if !dir || entry_isdir { - match partial.split_first() { - Some((base, rest)) => { - if matches(base, &entry_name, options) { - if !rest.is_empty() || isdir { - completions - .extend(complete_rec(rest, &built, cwd, options, dir, isdir)); - } else { - completions.push(built); - } + entries.push((entry_name, built)); + } + } + + let prefix = partial.first().unwrap_or(&""); + let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry); + + for (entry_name, built) in sorted_entries { + match partial.split_first() { + Some((base, rest)) => { + if matches(base, &entry_name, options) { + if !rest.is_empty() || isdir { + completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir)); + } else { + completions.push(built); } } - None => { - completions.push(built); - } + } + None => { + completions.push(built); } } } @@ -185,9 +197,14 @@ pub fn complete_item( .map(|p| { let path = original_cwd.apply(p); let style = ls_colors.as_ref().map(|lsc| { - lsc.style_for_path_with_metadata(&path, std::fs::symlink_metadata(&path).ok().as_ref()) - .map(lscolors::Style::to_nu_ansi_term_style) - .unwrap_or_default() + lsc.style_for_path_with_metadata( + &path, + std::fs::symlink_metadata(expand_to_real_path(&path)) + .ok() + .as_ref(), + ) + .map(lscolors::Style::to_nu_ansi_term_style) + .unwrap_or_default() }); (span, escape_path(path, want_directory), style) }) @@ -251,3 +268,38 @@ pub fn adjust_if_intermediate( readjusted, } } + +/// Convenience function to sort suggestions using [`sort_completions`] +pub fn sort_suggestions( + prefix: &str, + items: Vec, + sort_by: SortBy, +) -> Vec { + sort_completions(prefix, items, sort_by, |it| &it.suggestion.value) +} + +/// # Arguments +/// * `prefix` - What the user's typed, for sorting by Levenshtein distance +pub fn sort_completions( + prefix: &str, + mut items: Vec, + sort_by: SortBy, + get_value: fn(&T) -> &str, +) -> Vec { + // Sort items + match sort_by { + SortBy::LevenshteinDistance => { + items.sort_by(|a, b| { + let a_distance = levenshtein_distance(prefix, get_value(a)); + let b_distance = levenshtein_distance(prefix, get_value(b)); + a_distance.cmp(&b_distance) + }); + } + SortBy::Ascending => { + items.sort_by(|a, b| get_value(a).cmp(get_value(b))); + } + SortBy::None => {} + }; + + items +} diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 17c8e6a924..0d2c674ef9 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -12,6 +12,8 @@ use nu_protocol::{ use nu_utils::IgnoreCaseExt; use std::collections::HashMap; +use super::completion_common::sort_suggestions; + pub struct CustomCompletion { stack: Stack, decl_id: usize, @@ -52,18 +54,16 @@ impl Completer for CustomCompletion { decl_id: self.decl_id, head: span, arguments: vec![ - Argument::Positional(Expression { - span: Span::unknown(), - ty: Type::String, - expr: Expr::String(self.line.clone()), - custom_completion: None, - }), - Argument::Positional(Expression { - span: Span::unknown(), - ty: Type::Int, - expr: Expr::Int(line_pos as i64), - custom_completion: None, - }), + Argument::Positional(Expression::new_unknown( + Expr::String(self.line.clone()), + Span::unknown(), + Type::String, + )), + Argument::Positional(Expression::new_unknown( + Expr::Int(line_pos as i64), + Span::unknown(), + Type::Int, + )), ], parser_info: HashMap::new(), }, @@ -124,15 +124,12 @@ impl Completer for CustomCompletion { }) .unwrap_or_default(); - if let Some(custom_completion_options) = custom_completion_options { + let suggestions = if let Some(custom_completion_options) = custom_completion_options { filter(&prefix, suggestions, &custom_completion_options) } else { filter(&prefix, suggestions, completion_options) - } - } - - fn get_sort_by(&self) -> SortBy { - self.sort_by + }; + sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by) } } diff --git a/crates/nu-cli/src/completions/directory_completions.rs b/crates/nu-cli/src/completions/directory_completions.rs index 024322f997..61b0439444 100644 --- a/crates/nu-cli/src/completions/directory_completions.rs +++ b/crates/nu-cli/src/completions/directory_completions.rs @@ -1,14 +1,14 @@ use crate::completions::{ completion_common::{adjust_if_intermediate, complete_item, AdjustView}, - Completer, CompletionOptions, SortBy, + Completer, CompletionOptions, }; use nu_ansi_term::Style; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - levenshtein_distance, Span, + Span, }; use reedline::Suggestion; -use std::path::{Path, MAIN_SEPARATOR as SEP}; +use std::path::Path; use super::SemanticSuggestion; @@ -36,7 +36,7 @@ impl Completer for DirectoryCompletion { // Filter only the folders #[allow(deprecated)] - let output: Vec<_> = directory_completion( + let items: Vec<_> = directory_completion( span, &prefix, &working_set.permanent_state.current_work_dir(), @@ -62,41 +62,11 @@ impl Completer for DirectoryCompletion { }) .collect(); - output - } - - // Sort results prioritizing the non hidden folders - fn sort(&self, items: Vec, prefix: Vec) -> Vec { - let prefix_str = String::from_utf8_lossy(&prefix).to_string(); - - // Sort items - let mut sorted_items = items; - - match self.get_sort_by() { - SortBy::Ascending => { - sorted_items.sort_by(|a, b| { - // Ignore trailing slashes in folder names when sorting - a.suggestion - .value - .trim_end_matches(SEP) - .cmp(b.suggestion.value.trim_end_matches(SEP)) - }); - } - SortBy::LevenshteinDistance => { - sorted_items.sort_by(|a, b| { - let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value); - let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value); - a_distance.cmp(&b_distance) - }); - } - _ => (), - } - // Separate the results between hidden and non hidden let mut hidden: Vec = vec![]; let mut non_hidden: Vec = vec![]; - for item in sorted_items.into_iter() { + for item in items.into_iter() { let item_path = Path::new(&item.suggestion.value); if let Some(value) = item_path.file_name() { diff --git a/crates/nu-cli/src/completions/dotnu_completions.rs b/crates/nu-cli/src/completions/dotnu_completions.rs index c939578b41..1b2bbab20c 100644 --- a/crates/nu-cli/src/completions/dotnu_completions.rs +++ b/crates/nu-cli/src/completions/dotnu_completions.rs @@ -1,4 +1,4 @@ -use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy}; +use crate::completions::{file_path_completion, Completer, CompletionOptions}; use nu_protocol::{ engine::{Stack, StateWorkingSet}, Span, @@ -6,7 +6,7 @@ use nu_protocol::{ use reedline::Suggestion; use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR}; -use super::SemanticSuggestion; +use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy}; #[derive(Clone, Default)] pub struct DotNuCompletion {} @@ -131,10 +131,6 @@ impl Completer for DotNuCompletion { }) .collect(); - output - } - - fn get_sort_by(&self) -> SortBy { - SortBy::LevenshteinDistance + sort_suggestions(&prefix_str, output, SortBy::Ascending) } } diff --git a/crates/nu-cli/src/completions/file_completions.rs b/crates/nu-cli/src/completions/file_completions.rs index f6205f6792..10a548e7ab 100644 --- a/crates/nu-cli/src/completions/file_completions.rs +++ b/crates/nu-cli/src/completions/file_completions.rs @@ -1,15 +1,15 @@ use crate::completions::{ completion_common::{adjust_if_intermediate, complete_item, AdjustView}, - Completer, CompletionOptions, SortBy, + Completer, CompletionOptions, }; use nu_ansi_term::Style; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - levenshtein_distance, Span, + Span, }; use nu_utils::IgnoreCaseExt; use reedline::Suggestion; -use std::path::{Path, MAIN_SEPARATOR as SEP}; +use std::path::Path; use super::SemanticSuggestion; @@ -40,7 +40,7 @@ impl Completer for FileCompletion { } = adjust_if_intermediate(&prefix, working_set, span); #[allow(deprecated)] - let output: Vec<_> = complete_item( + let items: Vec<_> = complete_item( readjusted, span, &prefix, @@ -67,41 +67,13 @@ impl Completer for FileCompletion { }) .collect(); - output - } - - // Sort results prioritizing the non hidden folders - fn sort(&self, items: Vec, prefix: Vec) -> Vec { - let prefix_str = String::from_utf8_lossy(&prefix).to_string(); - - // Sort items - let mut sorted_items = items; - - match self.get_sort_by() { - SortBy::Ascending => { - sorted_items.sort_by(|a, b| { - // Ignore trailing slashes in folder names when sorting - a.suggestion - .value - .trim_end_matches(SEP) - .cmp(b.suggestion.value.trim_end_matches(SEP)) - }); - } - SortBy::LevenshteinDistance => { - sorted_items.sort_by(|a, b| { - let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value); - let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value); - a_distance.cmp(&b_distance) - }); - } - _ => (), - } + // Sort results prioritizing the non hidden folders // Separate the results between hidden and non hidden let mut hidden: Vec = vec![]; let mut non_hidden: Vec = vec![]; - for item in sorted_items.into_iter() { + for item in items.into_iter() { let item_path = Path::new(&item.suggestion.value); if let Some(value) = item_path.file_name() { diff --git a/crates/nu-cli/src/completions/flag_completions.rs b/crates/nu-cli/src/completions/flag_completions.rs index b0dcc0963b..ea4ddd6856 100644 --- a/crates/nu-cli/src/completions/flag_completions.rs +++ b/crates/nu-cli/src/completions/flag_completions.rs @@ -1,4 +1,6 @@ -use crate::completions::{Completer, CompletionOptions}; +use crate::completions::{ + completion_common::sort_suggestions, Completer, CompletionOptions, SortBy, +}; use nu_protocol::{ ast::{Expr, Expression}, engine::{Stack, StateWorkingSet}, @@ -90,7 +92,7 @@ impl Completer for FlagCompletion { } } - return output; + return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending); } vec![] diff --git a/crates/nu-cli/src/completions/variable_completions.rs b/crates/nu-cli/src/completions/variable_completions.rs index 0572fe93c1..72a69e942c 100644 --- a/crates/nu-cli/src/completions/variable_completions.rs +++ b/crates/nu-cli/src/completions/variable_completions.rs @@ -9,6 +9,8 @@ use nu_protocol::{ use reedline::Suggestion; use std::str; +use super::{completion_common::sort_suggestions, SortBy}; + #[derive(Clone)] pub struct VariableCompletion { var_context: (Vec, Vec>), // tuple with $var and the sublevels (.b.c.d) @@ -40,6 +42,7 @@ impl Completer for VariableCompletion { end: span.end - offset, }; let sublevels_count = self.var_context.1.len(); + let prefix_str = String::from_utf8_lossy(&prefix); // Completions for the given variable if !var_str.is_empty() { @@ -69,7 +72,7 @@ impl Completer for VariableCompletion { } } - return output; + return sort_suggestions(&prefix_str, output, SortBy::Ascending); } } else { // No nesting provided, return all env vars @@ -93,7 +96,7 @@ impl Completer for VariableCompletion { } } - return output; + return sort_suggestions(&prefix_str, output, SortBy::Ascending); } } @@ -117,7 +120,7 @@ impl Completer for VariableCompletion { } } - return output; + return sort_suggestions(&prefix_str, output, SortBy::Ascending); } } @@ -139,7 +142,7 @@ impl Completer for VariableCompletion { } } - return output; + return sort_suggestions(&prefix_str, output, SortBy::Ascending); } } } @@ -226,6 +229,8 @@ impl Completer for VariableCompletion { } } + output = sort_suggestions(&prefix_str, output, SortBy::Ascending); + output.dedup(); // TODO: Removes only consecutive duplicates, is it intended? output diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index ec7ad2f412..3a02f75e86 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -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 } diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 8fa3bf30e5..13141f6174 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -8,15 +8,45 @@ use nu_protocol::{ }; use std::sync::Arc; +#[derive(Default)] +pub struct EvaluateCommandsOpts { + pub table_mode: Option, + pub error_style: Option, + pub no_newline: bool, +} + /// Run a command (or commands) given to us by the user pub fn evaluate_commands( commands: &Spanned, engine_state: &mut EngineState, stack: &mut Stack, input: PipelineData, - table_mode: Option, - no_newline: bool, + opts: EvaluateCommandsOpts, ) -> Result<(), ShellError> { + let EvaluateCommandsOpts { + table_mode, + error_style, + no_newline, + } = opts; + + // Handle the configured error style early + if let Some(e_style) = error_style { + match e_style.coerce_str()?.parse() { + Ok(e_style) => { + Arc::make_mut(&mut engine_state.config).error_style = e_style; + } + Err(err) => { + return Err(ShellError::GenericError { + error: "Invalid value for `--error-style`".into(), + msg: err.into(), + span: Some(e_style.span()), + help: None, + inner: vec![], + }); + } + } + } + // Translate environment variables from Strings to Values convert_env_values(engine_state, stack)?; diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index c4342dc3a0..6f151adad1 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -17,7 +17,7 @@ mod validation; pub use commands::add_cli_context; pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind}; pub use config_files::eval_config_contents; -pub use eval_cmds::evaluate_commands; +pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts}; pub use eval_file::evaluate_file; pub use menus::NuHelpCompleter; pub use nu_cmd_base::util::get_init_cwd; diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index 3f920a00cb..dd5a3199dc 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -1,6 +1,5 @@ use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crossterm::event::{KeyCode, KeyModifiers}; -use log::trace; use nu_ansi_term::Style; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_engine::eval_block; @@ -76,15 +75,15 @@ const DEFAULT_HELP_MENU: &str = r#" // Adds all menus to line editor pub(crate) fn add_menus( mut line_editor: Reedline, - engine_state: Arc, + engine_state_ref: Arc, stack: &Stack, config: &Config, ) -> Result { - trace!("add_menus: config: {:#?}", &config); + //log::trace!("add_menus: config: {:#?}", &config); line_editor = line_editor.clear_menus(); for menu in &config.menus { - line_editor = add_menu(line_editor, menu, engine_state.clone(), stack, config)? + line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)? } // Checking if the default menus have been added from the config file @@ -94,13 +93,16 @@ pub(crate) fn add_menus( ("help_menu", DEFAULT_HELP_MENU), ]; + let mut engine_state = (*engine_state_ref).clone(); + let mut menu_eval_results = vec![]; + for (name, definition) in default_menus { if !config .menus .iter() .any(|menu| menu.name.to_expanded_string("", config) == name) { - let (block, _) = { + let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); let output = parse( &mut working_set, @@ -112,15 +114,31 @@ pub(crate) fn add_menus( (output, working_set.render()) }; + engine_state.merge_delta(delta)?; + let mut temp_stack = Stack::new().capture(); let input = PipelineData::Empty; - let res = eval_block::(&engine_state, &mut temp_stack, &block, input)?; + menu_eval_results.push(eval_block::( + &engine_state, + &mut temp_stack, + &block, + input, + )?); + } + } - if let PipelineData::Value(value, None) = res { - for menu in create_menus(&value)? { - line_editor = - add_menu(line_editor, &menu, engine_state.clone(), stack, config)?; - } + let new_engine_state_ref = Arc::new(engine_state); + + for res in menu_eval_results.into_iter() { + if let PipelineData::Value(value, None) = res { + for menu in create_menus(&value)? { + line_editor = add_menu( + line_editor, + &menu, + new_engine_state_ref.clone(), + stack, + config, + )?; } } } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index b10d632921..148b7a7a97 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -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 @@ -401,14 +345,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { ) .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); @@ -423,14 +360,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"); @@ -440,14 +370,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()); @@ -464,14 +387,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(); @@ -481,28 +397,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(); @@ -519,14 +421,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; @@ -553,14 +448,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 { @@ -597,14 +485,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(); @@ -619,26 +500,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 { @@ -646,13 +521,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 ); } @@ -776,22 +648,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) @@ -1069,14 +935,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); } } @@ -1101,13 +960,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 ); } } @@ -1124,13 +980,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 ); } } @@ -1150,13 +1003,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 ); } } @@ -1379,13 +1229,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(); @@ -1397,13 +1244,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 { @@ -1416,13 +1260,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 ); } } diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index e296943af6..41ef168390 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -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 { diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index e4912e012f..d3cf73056f 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -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 diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index a5b0b13aa8..a7f88dc3b0 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -11,7 +11,7 @@ use std::{ sync::Arc, }; use support::{ - completions_helpers::{new_partial_engine, new_quote_engine}, + completions_helpers::{new_dotnu_engine, new_partial_engine, new_quote_engine}, file, folder, match_suggestions, new_engine, }; @@ -85,8 +85,29 @@ fn custom_completer() -> NuCompleter { NuCompleter::new(Arc::new(engine), Arc::new(stack)) } +#[fixture] +fn subcommand_completer() -> NuCompleter { + // Create a new engine + let (dir, _, mut engine, mut stack) = new_engine(); + + // Use fuzzy matching, because subcommands are sorted by Levenshtein distance, + // and that's not very useful with prefix matching + let commands = r#" + $env.config.completions.algorithm = "fuzzy" + def foo [] {} + def "foo bar" [] {} + def "foo abaz" [] {} + def "foo aabrr" [] {} + def food [] {} + "#; + assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack, dir).is_ok()); + + // Instantiate a new completer + NuCompleter::new(Arc::new(engine), Arc::new(stack)) +} + #[test] -fn variables_dollar_sign_with_varialblecompletion() { +fn variables_dollar_sign_with_variablecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); @@ -138,43 +159,42 @@ fn variables_customcompletion_subcommands_with_customcompletion_2( #[test] fn dotnu_completions() { // Create a new engine - let (_, _, engine, stack) = new_engine(); + let (_, _, engine, stack) = new_dotnu_engine(); // Instantiate a new completer let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); + let expected = vec![ + "asdf.nu".into(), + "bar.nu".into(), + "bat.nu".into(), + "baz.nu".into(), + #[cfg(windows)] + "dir_module\\".into(), + #[cfg(not(windows))] + "dir_module/".into(), + "foo.nu".into(), + "spam.nu".into(), + "xyzzy.nu".into(), + ]; + // Test source completion let completion_str = "source-env ".to_string(); let suggestions = completer.complete(&completion_str, completion_str.len()); - assert_eq!(2, suggestions.len()); - assert_eq!("custom_completion.nu", suggestions.first().unwrap().value); - #[cfg(windows)] - assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value); - #[cfg(not(windows))] - assert_eq!("directory_completion/", suggestions.get(1).unwrap().value); + match_suggestions(expected.clone(), suggestions); // Test use completion let completion_str = "use ".to_string(); let suggestions = completer.complete(&completion_str, completion_str.len()); - assert_eq!(2, suggestions.len()); - assert_eq!("custom_completion.nu", suggestions.first().unwrap().value); - #[cfg(windows)] - assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value); - #[cfg(not(windows))] - assert_eq!("directory_completion/", suggestions.get(1).unwrap().value); + match_suggestions(expected.clone(), suggestions); // Test overlay use completion let completion_str = "overlay use ".to_string(); let suggestions = completer.complete(&completion_str, completion_str.len()); - assert_eq!(2, suggestions.len()); - assert_eq!("custom_completion.nu", suggestions.first().unwrap().value); - #[cfg(windows)] - assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value); - #[cfg(not(windows))] - assert_eq!("directory_completion/", suggestions.get(1).unwrap().value); + match_suggestions(expected, suggestions); } #[test] @@ -276,9 +296,10 @@ fn partial_completions() { // Create the expected values let expected_paths: Vec = vec![ - folder(dir.join("partial_a")), - folder(dir.join("partial_b")), - folder(dir.join("partial_c")), + folder(dir.join("partial")), + folder(dir.join("partial-a")), + folder(dir.join("partial-b")), + folder(dir.join("partial-c")), ]; // Match the results @@ -292,13 +313,14 @@ fn partial_completions() { // Create the expected values let expected_paths: Vec = vec![ - file(dir.join("partial_a").join("have_ext.exe")), - file(dir.join("partial_a").join("have_ext.txt")), - file(dir.join("partial_a").join("hello")), - file(dir.join("partial_a").join("hola")), - file(dir.join("partial_b").join("hello_b")), - file(dir.join("partial_b").join("hi_b")), - file(dir.join("partial_c").join("hello_c")), + file(dir.join("partial").join("hello.txt")), + file(dir.join("partial-a").join("have_ext.exe")), + file(dir.join("partial-a").join("have_ext.txt")), + file(dir.join("partial-a").join("hello")), + file(dir.join("partial-a").join("hola")), + file(dir.join("partial-b").join("hello_b")), + file(dir.join("partial-b").join("hi_b")), + file(dir.join("partial-c").join("hello_c")), ]; // Match the results @@ -311,14 +333,15 @@ fn partial_completions() { // Create the expected values let expected_paths: Vec = vec![ - file(dir.join("partial_a").join("anotherfile")), - file(dir.join("partial_a").join("have_ext.exe")), - file(dir.join("partial_a").join("have_ext.txt")), - file(dir.join("partial_a").join("hello")), - file(dir.join("partial_a").join("hola")), - file(dir.join("partial_b").join("hello_b")), - file(dir.join("partial_b").join("hi_b")), - file(dir.join("partial_c").join("hello_c")), + file(dir.join("partial").join("hello.txt")), + file(dir.join("partial-a").join("anotherfile")), + file(dir.join("partial-a").join("have_ext.exe")), + file(dir.join("partial-a").join("have_ext.txt")), + file(dir.join("partial-a").join("hello")), + file(dir.join("partial-a").join("hola")), + file(dir.join("partial-b").join("hello_b")), + file(dir.join("partial-b").join("hi_b")), + file(dir.join("partial-c").join("hello_c")), ]; // Match the results @@ -343,19 +366,25 @@ fn partial_completions() { // Create the expected values let expected_paths: Vec = vec![ file( - dir.join("partial_a") + dir.join("partial") .join("..") .join("final_partial") .join("somefile"), ), file( - dir.join("partial_b") + dir.join("partial-a") .join("..") .join("final_partial") .join("somefile"), ), file( - dir.join("partial_c") + dir.join("partial-b") + .join("..") + .join("final_partial") + .join("somefile"), + ), + file( + dir.join("partial-c") .join("..") .join("final_partial") .join("somefile"), @@ -366,28 +395,28 @@ fn partial_completions() { match_suggestions(expected_paths, suggestions); // Test completion for all files under directories whose names begin with "pa" - let file_str = file(dir.join("partial_a").join("have")); + let file_str = file(dir.join("partial-a").join("have")); let target_file = format!("rm {file_str}"); let suggestions = completer.complete(&target_file, target_file.len()); // Create the expected values let expected_paths: Vec = vec![ - file(dir.join("partial_a").join("have_ext.exe")), - file(dir.join("partial_a").join("have_ext.txt")), + file(dir.join("partial-a").join("have_ext.exe")), + file(dir.join("partial-a").join("have_ext.txt")), ]; // Match the results match_suggestions(expected_paths, suggestions); // Test completion for all files under directories whose names begin with "pa" - let file_str = file(dir.join("partial_a").join("have_ext.")); + let file_str = file(dir.join("partial-a").join("have_ext.")); let file_dir = format!("rm {file_str}"); let suggestions = completer.complete(&file_dir, file_dir.len()); // Create the expected values let expected_paths: Vec = vec![ - file(dir.join("partial_a").join("have_ext.exe")), - file(dir.join("partial_a").join("have_ext.txt")), + file(dir.join("partial-a").join("have_ext.exe")), + file(dir.join("partial-a").join("have_ext.txt")), ]; // Match the results @@ -652,6 +681,27 @@ fn command_watch_with_filecompletion() { match_suggestions(expected_paths, suggestions) } +#[rstest] +fn subcommand_completions(mut subcommand_completer: NuCompleter) { + let prefix = "foo br"; + let suggestions = subcommand_completer.complete(prefix, prefix.len()); + match_suggestions( + vec!["foo bar".to_string(), "foo aabrr".to_string()], + suggestions, + ); + + let prefix = "foo b"; + let suggestions = subcommand_completer.complete(prefix, prefix.len()); + match_suggestions( + vec![ + "foo bar".to_string(), + "foo abaz".to_string(), + "foo aabrr".to_string(), + ], + suggestions, + ); +} + #[test] fn file_completion_quoted() { let (_, _, engine, stack) = new_quote_engine(); @@ -662,11 +712,11 @@ fn file_completion_quoted() { let suggestions = completer.complete(target_dir, target_dir.len()); let expected_paths: Vec = vec![ - "\'[a] bc.txt\'".to_string(), "`--help`".to_string(), "`-42`".to_string(), "`-inf`".to_string(), "`4.2`".to_string(), + "\'[a] bc.txt\'".to_string(), "`te st.txt`".to_string(), "`te#st.txt`".to_string(), "`te'st.txt`".to_string(), @@ -763,11 +813,13 @@ fn variables_completions() { // Test completions for $nu let suggestions = completer.complete("$nu.", 4); - assert_eq!(15, suggestions.len()); + assert_eq!(18, suggestions.len()); let expected: Vec = vec![ + "cache-dir".into(), "config-path".into(), "current-exe".into(), + "data-dir".into(), "default-config-dir".into(), "env-path".into(), "history-enabled".into(), @@ -781,6 +833,7 @@ fn variables_completions() { "plugin-path".into(), "startup-time".into(), "temp-path".into(), + "vendor-autoload-dir".into(), ]; // Match results @@ -854,6 +907,11 @@ fn variables_completions() { // Match results match_suggestions(expected, suggestions); + + let suggestions = completer.complete("$", 1); + let expected: Vec = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()]; + + match_suggestions(expected, suggestions); } #[test] diff --git a/crates/nu-cli/tests/completions/support/completions_helpers.rs b/crates/nu-cli/tests/completions/support/completions_helpers.rs index 47f46ab00e..027ac9d997 100644 --- a/crates/nu-cli/tests/completions/support/completions_helpers.rs +++ b/crates/nu-cli/tests/completions/support/completions_helpers.rs @@ -68,6 +68,52 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { (dir, dir_str, engine_state, stack) } +// creates a new engine with the current path into the completions fixtures folder +pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) { + // Target folder inside assets + let dir = fs::fixtures().join("dotnu_completions"); + let dir_str = dir + .clone() + .into_os_string() + .into_string() + .unwrap_or_default(); + let dir_span = nu_protocol::Span::new(0, dir_str.len()); + + // Create a new engine with default context + let mut engine_state = create_default_context(); + + // Add $nu + engine_state.generate_nu_constant(); + + // New stack + let mut stack = Stack::new(); + + // Add pwd as env var + stack.add_env_var("PWD".to_string(), Value::string(dir_str.clone(), dir_span)); + stack.add_env_var( + "TEST".to_string(), + Value::string("NUSHELL".to_string(), dir_span), + ); + + stack.add_env_var( + "NU_LIB_DIRS".to_string(), + Value::List { + vals: vec![ + Value::string(file(dir.join("lib-dir1")), dir_span), + Value::string(file(dir.join("lib-dir2")), dir_span), + Value::string(file(dir.join("lib-dir3")), dir_span), + ], + internal_span: dir_span, + }, + ); + + // Merge environment into the permanent state + let merge_result = engine_state.merge_env(&mut stack, &dir); + assert!(merge_result.is_ok()); + + (dir, dir_str, engine_state, stack) +} + pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("quoted_completions"); @@ -149,9 +195,13 @@ pub fn match_suggestions(expected: Vec, suggestions: Vec) { Expected: {expected:#?}\n" ) } - expected.iter().zip(suggestions).for_each(|it| { - assert_eq!(it.0, &it.1.value); - }); + assert_eq!( + expected, + suggestions + .into_iter() + .map(|it| it.value) + .collect::>() + ); } // append the separator to the converted path diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 1dcd95ba94..2fe8610f49 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.94.1" +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.1" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-path = { path = "../nu-path", version = "0.94.1" } -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } +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 } diff --git a/crates/nu-cmd-base/src/hook.rs b/crates/nu-cmd-base/src/hook.rs index 76c13bd5c3..cef5348618 100644 --- a/crates/nu-cmd-base/src/hook.rs +++ b/crates/nu-cmd-base/src/hook.rs @@ -194,7 +194,7 @@ pub fn eval_hook( let Some(follow) = val.get("code") else { return Err(ShellError::CantFindColumn { col_name: "code".into(), - span, + span: Some(span), src_span: span, }); }; diff --git a/crates/nu-cmd-base/src/util.rs b/crates/nu-cmd-base/src/util.rs index 559161329b..905c990a9e 100644 --- a/crates/nu-cmd-base/src/util.rs +++ b/crates/nu-cmd-base/src/util.rs @@ -20,13 +20,14 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf type MakeRangeError = fn(&str, Span) -> ShellError; +/// Returns a inclusive pair of boundary in given `range`. pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> { match range { Range::IntRange(range) => { let start = range.start().try_into().unwrap_or(0); let end = match range.end() { - Bound::Included(v) => (v + 1) as isize, - Bound::Excluded(v) => v as isize, + Bound::Included(v) => v as isize, + Bound::Excluded(v) => (v - 1) as isize, Bound::Unbounded => isize::MAX, }; Ok((start, end)) diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index 9e6270163d..8609adb44c 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -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.1" +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.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.1" } -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-json = { version = "0.94.1", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-pretty-hex = { version = "0.94.1", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } +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 } @@ -32,11 +32,7 @@ serde_urlencoded = { workspace = true } v_htmlescape = { workspace = true } itertools = { workspace = true } -[features] -extra = ["default"] -default = [] - [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" } -nu-command = { path = "../nu-command", version = "0.94.1" } -nu-test-support = { path = "../nu-test-support", version = "0.94.1" } \ No newline at end of file +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" } \ No newline at end of file diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index cf85f92ac5..9891a971f6 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -1,3 +1,5 @@ +use std::io::{self, Read, Write}; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; @@ -118,15 +120,41 @@ fn into_bits( let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - if let PipelineData::ByteStream(stream, ..) = input { - // TODO: in the future, we may want this to stream out, converting each to bytes - Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data()) + if let PipelineData::ByteStream(stream, metadata) = input { + Ok(PipelineData::ByteStream( + byte_stream_to_bits(stream, head), + metadata, + )) } else { let args = Arguments { cell_paths }; operate(action, args, input, call.head, engine_state.ctrlc.clone()) } } +fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { + if let Some(mut reader) = stream.reader() { + let mut is_first = true; + ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) + } else { + // EOF + Ok(false) + } + }) + } else { + ByteStream::read(io::empty(), head, None, ByteStreamType::String) + } +} + fn convert_to_smallest_number_type(num: i64, span: Span) -> Value { if let Some(v) = num.to_i8() { let bytes = v.to_ne_bytes(); diff --git a/crates/nu-cmd-extra/src/extra/formats/to/html.rs b/crates/nu-cmd-extra/src/extra/formats/to/html.rs index c2ca80e2c6..83d0b58b3f 100644 --- a/crates/nu-cmd-extra/src/extra/formats/to/html.rs +++ b/crates/nu-cmd-extra/src/extra/formats/to/html.rs @@ -368,6 +368,7 @@ fn theme_demo(span: Span) -> PipelineData { .collect(); Value::list(result, span).into_pipeline_data_with_metadata(PipelineMetadata { data_source: DataSource::HtmlThemes, + content_type: None, }) } diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 1c72627779..50e89e61e3 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -1,5 +1,4 @@ -use nu_engine::{command_prelude::*, get_eval_expression}; -use nu_parser::parse_expression; +use nu_engine::command_prelude::*; use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream}; #[derive(Clone)] @@ -57,14 +56,7 @@ impl Command for FormatPattern { string_span.start + 1, )?; - format( - input_val, - &ops, - engine_state, - &mut working_set, - stack, - call.head, - ) + format(input_val, &ops, engine_state, call.head) } } } @@ -100,8 +92,6 @@ enum FormatOperation { FixedText(String), // raw input is something like {column1.column2} ValueFromColumn(String, Span), - // raw input is something like {$it.column1.column2} or {$var}. - ValueNeedEval(String, Span), } /// Given a pattern that is fed into the Format command, we can process it and subdivide it @@ -110,7 +100,6 @@ enum FormatOperation { /// there without any further processing. /// FormatOperation::ValueFromColumn contains the name of a column whose values will be /// formatted according to the input pattern. -/// FormatOperation::ValueNeedEval contains expression which need to eval, it has the following form: /// "$it.column1.column2" or "$variable" fn extract_formatting_operations( input: String, @@ -161,10 +150,17 @@ fn extract_formatting_operations( if !column_name.is_empty() { if column_need_eval { - output.push(FormatOperation::ValueNeedEval( - column_name.clone(), - Span::new(span_start + column_span_start, span_start + column_span_end), - )); + return Err(ShellError::GenericError { + error: "Removed functionality".into(), + msg: "The ability to use variables ($it) in `format pattern` has been removed." + .into(), + span: Some(error_span), + help: Some( + "You can use other formatting options, such as string interpolation." + .into(), + ), + inner: vec![], + }); } else { output.push(FormatOperation::ValueFromColumn( column_name.clone(), @@ -185,8 +181,6 @@ fn format( input_data: Value, format_operations: &[FormatOperation], engine_state: &EngineState, - working_set: &mut StateWorkingSet, - stack: &mut Stack, head_span: Span, ) -> Result { let data_as_value = input_data; @@ -194,13 +188,7 @@ fn format( // We can only handle a Record or a List of Records match data_as_value { Value::Record { .. } => { - match format_record( - format_operations, - &data_as_value, - engine_state, - working_set, - stack, - ) { + match format_record(format_operations, &data_as_value, engine_state) { Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), Err(value) => Err(value), } @@ -211,13 +199,7 @@ fn format( for val in vals.iter() { match val { Value::Record { .. } => { - match format_record( - format_operations, - val, - engine_state, - working_set, - stack, - ) { + match format_record(format_operations, val, engine_state) { Ok(value) => { list.push(Value::string(value, head_span)); } @@ -256,12 +238,9 @@ fn format_record( format_operations: &[FormatOperation], data_as_value: &Value, engine_state: &EngineState, - working_set: &mut StateWorkingSet, - stack: &mut Stack, ) -> Result { let config = engine_state.get_config(); let mut output = String::new(); - let eval_expression = get_eval_expression(engine_state); for op in format_operations { match op { @@ -283,23 +262,6 @@ fn format_record( Err(se) => return Err(se), } } - FormatOperation::ValueNeedEval(_col_name, span) => { - let exp = parse_expression(working_set, &[*span]); - match working_set.parse_errors.first() { - None => { - let parsed_result = eval_expression(engine_state, stack, &exp); - if let Ok(val) = parsed_result { - output.push_str(&val.to_abbreviated_string(config)) - } - } - Some(err) => { - return Err(ShellError::TypeMismatch { - err_message: format!("expression is invalid, detail message: {err:?}"), - span: *span, - }) - } - } - } } } Ok(output) diff --git a/crates/nu-cmd-extra/tests/commands/bits/into.rs b/crates/nu-cmd-extra/tests/commands/bits/into.rs new file mode 100644 index 0000000000..b7e7700583 --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/into.rs @@ -0,0 +1,13 @@ +use nu_test_support::nu; + +#[test] +fn byte_stream_into_bits() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits"); + assert_eq!("00000001 00000010 00000011", result.out); +} + +#[test] +fn byte_stream_into_bits_is_stream() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe"); + assert_eq!("string (stream)", result.out); +} diff --git a/crates/nu-cmd-extra/tests/commands/bits/mod.rs b/crates/nu-cmd-extra/tests/commands/bits/mod.rs new file mode 100644 index 0000000000..0d4ee04b0d --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/mod.rs @@ -0,0 +1 @@ +mod into; diff --git a/crates/nu-cmd-extra/tests/commands/mod.rs b/crates/nu-cmd-extra/tests/commands/mod.rs index 2354122e35..fd216cecb6 100644 --- a/crates/nu-cmd-extra/tests/commands/mod.rs +++ b/crates/nu-cmd-extra/tests/commands/mod.rs @@ -1 +1,2 @@ +mod bits; mod bytes; diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index f7b43521d1..16ac1b893a 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -6,26 +6,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang" edition = "2021" license = "MIT" name = "nu-cmd-lang" -version = "0.94.1" +version = "0.95.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } +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 = [] -which-support = [] trash-support = [] sqlite = [] static-link-openssl = [] diff --git a/crates/nu-cmd-lang/src/core_commands/break_.rs b/crates/nu-cmd-lang/src/core_commands/break_.rs index 4698f12c34..90cc1a73f2 100644 --- a/crates/nu-cmd-lang/src/core_commands/break_.rs +++ b/crates/nu-cmd-lang/src/core_commands/break_.rs @@ -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, diff --git a/crates/nu-cmd-lang/src/core_commands/collect.rs b/crates/nu-cmd-lang/src/core_commands/collect.rs index 1c28646548..d6282eec35 100644 --- a/crates/nu-cmd-lang/src/core_commands/collect.rs +++ b/crates/nu-cmd-lang/src/core_commands/collect.rs @@ -50,6 +50,7 @@ is particularly large, this can cause high memory usage."# // check where some input came from. Some(PipelineMetadata { data_source: DataSource::FilePath(_), + content_type: None, }) => None, other => other, }; diff --git a/crates/nu-cmd-lang/src/core_commands/continue_.rs b/crates/nu-cmd-lang/src/core_commands/continue_.rs index f65a983269..cfa3e38335 100644 --- a/crates/nu-cmd-lang/src/core_commands/continue_.rs +++ b/crates/nu-cmd-lang/src/core_commands/continue_.rs @@ -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, diff --git a/crates/nu-cmd-lang/src/core_commands/def.rs b/crates/nu-cmd-lang/src/core_commands/def.rs index eb1124da19..913f74803c 100644 --- a/crates/nu-cmd-lang/src/core_commands/def.rs +++ b/crates/nu-cmd-lang/src/core_commands/def.rs @@ -60,10 +60,15 @@ impl Command for Def { example: r#"def --env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#, result: Some(Value::test_string("BAZ")), }, + Example { + description: "cd affects the environment, so '--env' is required to change directory from within a command", + example: r#"def --env gohome [] { cd ~ }; gohome; $env.PWD == ('~' | path expand)"#, + result: Some(Value::test_string("true")), + }, Example { description: "Define a custom wrapper for an external command", - example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#, - result: Some(Value::test_list(vec![Value::test_string("spam")])), + example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#, + result: Some(Value::test_string("spam\tspam")), }, ] } diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 8ca3fbac56..adf13cc0bb 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -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", diff --git a/crates/nu-cmd-lang/src/core_commands/error_make.rs b/crates/nu-cmd-lang/src/core_commands/error_make.rs index 987e083cbc..07efcd7885 100644 --- a/crates/nu-cmd-lang/src/core_commands/error_make.rs +++ b/crates/nu-cmd-lang/src/core_commands/error_make.rs @@ -56,16 +56,7 @@ impl Command for ErrorMake { Example { description: "Create a simple custom error", example: r#"error make {msg: "my custom error message"}"#, - result: Some(Value::error( - ShellError::GenericError { - error: "my custom error message".into(), - msg: "".into(), - span: None, - help: None, - inner: vec![], - }, - Span::unknown(), - )), + result: None, }, Example { description: "Create a more complex custom error", @@ -82,16 +73,7 @@ impl Command for ErrorMake { } help: "A help string, suggesting a fix to the user" # optional }"#, - result: Some(Value::error( - ShellError::GenericError { - error: "my custom error message".into(), - msg: "my custom label text".into(), - span: Some(Span::new(123, 456)), - help: Some("A help string, suggesting a fix to the user".into()), - inner: vec![], - }, - Span::unknown(), - )), + result: None, }, Example { description: diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index 387af45282..9410be74c7 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -28,11 +28,6 @@ impl Command for For { "Range of the loop.", ) .required("block", SyntaxShape::Block, "The block to run.") - .switch( - "numbered", - "return a numbered item ($it.index and $it.item)", - Some('n'), - ) .creates_scope() .category(Category::Core) } @@ -77,8 +72,6 @@ impl Command for For { let value = eval_expression(engine_state, stack, keyword_expr)?; - let numbered = call.has_flag(engine_state, stack, "numbered")?; - let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id); @@ -88,7 +81,7 @@ impl Command for For { let span = value.span(); match value { Value::List { vals, .. } => { - for (idx, x) in vals.into_iter().enumerate() { + for x in vals.into_iter() { if nu_utils::ctrl_c::was_pressed(&ctrlc) { break; } @@ -97,20 +90,7 @@ impl Command for For { // a different set of environment variables. // Hence, a 'cd' in the first loop won't affect the next loop. - stack.add_var( - var_id, - if numbered { - Value::record( - record! { - "index" => Value::int(idx as i64, head), - "item" => x, - }, - head, - ) - } else { - x - }, - ); + stack.add_var(var_id, x); match eval_block(&engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { @@ -136,21 +116,8 @@ impl Command for For { } } Value::Range { val, .. } => { - for (idx, x) in val.into_range_iter(span, ctrlc).enumerate() { - stack.add_var( - var_id, - if numbered { - Value::record( - record! { - "index" => Value::int(idx as i64, head), - "item" => x, - }, - head, - ) - } else { - x - }, - ); + for x in val.into_range_iter(span, ctrlc) { + stack.add_var(var_id, x); match eval_block(&engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { @@ -198,8 +165,7 @@ impl Command for For { }, Example { description: "Number each item and print a message", - example: - "for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }", + example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#, result: None, }, ] diff --git a/crates/nu-cmd-lang/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs index 83808c8e06..738d901759 100644 --- a/crates/nu-cmd-lang/src/core_commands/if_.rs +++ b/crates/nu-cmd-lang/src/core_commands/if_.rs @@ -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 } @@ -122,6 +131,10 @@ impl Command for If { } } + fn search_terms(&self) -> Vec<&str> { + vec!["else", "conditional"] + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index 9b1e36a057..a9c642ca3c 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -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, diff --git a/crates/nu-cmd-lang/src/core_commands/match_.rs b/crates/nu-cmd-lang/src/core_commands/match_.rs index 41b5c24702..d28a59cbad 100644 --- a/crates/nu-cmd-lang/src/core_commands/match_.rs +++ b/crates/nu-cmd-lang/src/core_commands/match_.rs @@ -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, diff --git a/crates/nu-cmd-lang/src/core_commands/scope/command.rs b/crates/nu-cmd-lang/src/core_commands/scope/command.rs index 98439226cf..cc52a8a16f 100644 --- a/crates/nu-cmd-lang/src/core_commands/scope/command.rs +++ b/crates/nu-cmd-lang/src/core_commands/scope/command.rs @@ -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, diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index 0b399e368a..f99825b88d 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -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; @@ -10,7 +10,7 @@ impl Command for Try { } fn usage(&self) -> &str { - "Try to run a block, if it fails optionally run a catch block." + "Try to run a block, if it fails optionally run a catch closure." } fn signature(&self) -> nu_protocol::Signature { @@ -18,7 +18,7 @@ impl Command for Try { .input_output_types(vec![(Type::Any, Type::Any)]) .required("try_block", SyntaxShape::Block, "Block to run.") .optional( - "catch_block", + "catch_closure", SyntaxShape::Keyword( b"catch".to_vec(), Box::new(SyntaxShape::OneOf(vec![ @@ -26,11 +26,20 @@ impl Command for Try { SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), ])), ), - "Block to run if try block fails.", + "Closure to run if try block fails.", ) .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, @@ -85,9 +94,14 @@ impl Command for Try { }, Example { description: "Try to run a missing command", - example: "try { asdfasdf } catch { 'missing' } ", + example: "try { asdfasdf } catch { 'missing' }", result: Some(Value::test_string("missing")), }, + Example { + description: "Try to run a missing command and report the message", + example: "try { asdfasdf } catch { |err| $err.msg }", + result: None, + }, ] } } diff --git a/crates/nu-cmd-lang/src/core_commands/version.rs b/crates/nu-cmd-lang/src/core_commands/version.rs index 22ef1f2a08..5491db65fc 100644 --- a/crates/nu-cmd-lang/src/core_commands/version.rs +++ b/crates/nu-cmd-lang/src/core_commands/version.rs @@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result>(); record.push( @@ -160,11 +167,6 @@ fn features_enabled() -> Vec { // NOTE: There should be another way to know features on. - #[cfg(feature = "which-support")] - { - names.push("which".to_string()); - } - #[cfg(feature = "trash-support")] { names.push("trash".to_string()); diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index bf9076aa0c..646b95c82e 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -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, diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index f461f6bf04..7d26fe5ddd 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.94.1" +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.1" } -nu-path = { path = "../nu-path", version = "0.94.1" } -nu-protocol = { path = "../nu-protocol", version = "0.94.1", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.94.1" } +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 } diff --git a/crates/nu-cmd-plugin/src/commands/mod.rs b/crates/nu-cmd-plugin/src/commands/mod.rs index 3e927747f1..17ff32faa1 100644 --- a/crates/nu-cmd-plugin/src/commands/mod.rs +++ b/crates/nu-cmd-plugin/src/commands/mod.rs @@ -1,5 +1,3 @@ mod plugin; -mod register; pub use plugin::*; -pub use register::Register; diff --git a/crates/nu-cmd-plugin/src/commands/plugin/add.rs b/crates/nu-cmd-plugin/src/commands/plugin/add.rs index 225941db01..14f3541168 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/add.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/add.rs @@ -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(()) })?; diff --git a/crates/nu-cmd-plugin/src/commands/plugin/list.rs b/crates/nu-cmd-plugin/src/commands/plugin/list.rs index 6b715a0001..030a3341d6 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/list.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/list.rs @@ -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), diff --git a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs index d74ee59a3f..f90eac8411 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs @@ -72,7 +72,7 @@ impl Command for PluginStop { error: format!("Failed to stop the `{}` plugin", name.item), msg: "couldn't find a plugin with this name".into(), span: Some(name.span), - help: Some("you may need to `register` the plugin first".into()), + help: Some("you may need to `plugin add` the plugin first".into()), inner: vec![], }) } diff --git a/crates/nu-cmd-plugin/src/commands/register.rs b/crates/nu-cmd-plugin/src/commands/register.rs deleted file mode 100644 index 2c10456db7..0000000000 --- a/crates/nu-cmd-plugin/src/commands/register.rs +++ /dev/null @@ -1,80 +0,0 @@ -use nu_engine::command_prelude::*; -use nu_protocol::engine::CommandType; - -#[derive(Clone)] -pub struct Register; - -impl Command for Register { - fn name(&self) -> &str { - "register" - } - - fn usage(&self) -> &str { - "Register a plugin." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("register") - .input_output_types(vec![(Type::Nothing, Type::Nothing)]) - .required( - "plugin", - SyntaxShape::Filepath, - "Path of executable for plugin.", - ) - .optional( - "signature", - SyntaxShape::Any, - "Block with signature description as json object.", - ) - .named( - "shell", - SyntaxShape::Filepath, - "path of shell used to run plugin (cmd, sh, python, etc)", - Some('s'), - ) - .category(Category::Plugin) - } - - fn extra_usage(&self) -> &str { - r#" -Deprecated in favor of `plugin add` and `plugin use`. - -This command is a parser keyword. For details, check: - https://www.nushell.sh/book/thinking_in_nu.html -"# - .trim() - } - - fn search_terms(&self) -> Vec<&str> { - vec!["add"] - } - - fn command_type(&self) -> CommandType { - CommandType::Keyword - } - - fn run( - &self, - _engine_state: &EngineState, - _stack: &mut Stack, - _call: &Call, - _input: PipelineData, - ) -> Result { - Ok(PipelineData::empty()) - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "Register `nu_plugin_query` plugin from ~/.cargo/bin/ dir", - example: r#"register ~/.cargo/bin/nu_plugin_query"#, - result: None, - }, - Example { - description: "Register `nu_plugin_query` plugin from `nu -c` (writes/updates $nu.plugin-path)", - example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#, - result: None, - }, - ] - } -} diff --git a/crates/nu-cmd-plugin/src/default_context.rs b/crates/nu-cmd-plugin/src/default_context.rs index 601dd52cfc..1ef6a905dd 100644 --- a/crates/nu-cmd-plugin/src/default_context.rs +++ b/crates/nu-cmd-plugin/src/default_context.rs @@ -18,7 +18,6 @@ pub fn add_plugin_command_context(mut engine_state: EngineState) -> EngineState PluginRm, PluginStop, PluginUse, - Register, ); working_set.render() diff --git a/crates/nu-cmd-plugin/src/util.rs b/crates/nu-cmd-plugin/src/util.rs index 312ab9e81a..7d5282235f 100644 --- a/crates/nu-cmd-plugin/src/util.rs +++ b/crates/nu-cmd-plugin/src/util.rs @@ -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), )?; diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 34f0ded963..23af09432c 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -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.1" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-json = { path = "../nu-json", version = "0.94.1" } +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.1" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } \ No newline at end of file diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 72cc955e0b..8da397e914 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -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(), diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 25665cfc33..9c49a69ff5 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -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.1" +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.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.94.1" } -nu-color-config = { path = "../nu-color-config", version = "0.94.1" } -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-glob = { path = "../nu-glob", version = "0.94.1" } -nu-json = { path = "../nu-json", version = "0.94.1" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-path = { path = "../nu-path", version = "0.94.1" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.94.1" } -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-system = { path = "../nu-system", version = "0.94.1" } -nu-table = { path = "../nu-table", version = "0.94.1" } -nu-term-grid = { path = "../nu-term-grid", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } +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.1" } +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 } @@ -86,7 +87,7 @@ sysinfo = { workspace = true } tabled = { workspace = true, features = ["color"], default-features = false } terminal_size = { workspace = true } titlecase = { workspace = true } -toml = { workspace = true } +toml = { workspace = true, features = ["preserve_order"]} unicode-segmentation = { workspace = true } ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] } url = { workspace = true } @@ -134,11 +135,10 @@ workspace = true plugin = ["nu-parser/plugin"] sqlite = ["rusqlite"] trash-support = ["trash"] -which-support = [] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.1" } -nu-test-support = { path = "../nu-test-support", version = "0.94.1" } +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 } diff --git a/crates/nu-command/src/bytes/at.rs b/crates/nu-command/src/bytes/at.rs index e90312603f..c5164c3550 100644 --- a/crates/nu-command/src/bytes/at.rs +++ b/crates/nu-command/src/bytes/at.rs @@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { let range = &args.indexes; match input { Value::Binary { val, .. } => { - use std::cmp::{self, Ordering}; let len = val.len() as isize; - let start = if range.0 < 0 { range.0 + len } else { range.0 }; + let end = if range.1 < 0 { range.1 + len } else { range.1 }; - let end = if range.1 < 0 { - cmp::max(range.1 + len, 0) - } else { - range.1 - }; - - if start < len && end >= 0 { - match start.cmp(&end) { - Ordering::Equal => Value::binary(vec![], head), - Ordering::Greater => Value::error( - ShellError::TypeMismatch { - err_message: "End must be greater than or equal to Start".to_string(), - span: head, - }, - head, - ), - Ordering::Less => Value::binary( - if end == isize::MAX { - val.iter() - .skip(start as usize) - .copied() - .collect::>() - } else { - val.iter() - .skip(start as usize) - .take((end - start) as usize) - .copied() - .collect() - }, - head, - ), - } - } else { + if start > end { Value::binary(vec![], head) + } else { + let val_iter = val.iter().skip(start as usize); + Value::binary( + if end == isize::MAX { + val_iter.copied().collect::>() + } else { + val_iter.take((end - start + 1) as usize).copied().collect() + }, + head, + ) } } - Value::Error { .. } => input.clone(), other => Value::error( diff --git a/crates/nu-command/src/charting/histogram.rs b/crates/nu-command/src/charting/histogram.rs index 52964b087d..29515c96c5 100755 --- a/crates/nu-command/src/charting/histogram.rs +++ b/crates/nu-command/src/charting/histogram.rs @@ -194,7 +194,7 @@ fn run_histogram( if inputs.is_empty() { return Err(ShellError::CantFindColumn { col_name: col_name.clone(), - span: head_span, + span: Some(head_span), src_span: list_span, }); } diff --git a/crates/nu-command/src/conversions/into/cell_path.rs b/crates/nu-command/src/conversions/into/cell_path.rs index c05dad57ba..1342fb14c0 100644 --- a/crates/nu-command/src/conversions/into/cell_path.rs +++ b/crates/nu-command/src/conversions/into/cell_path.rs @@ -154,7 +154,7 @@ fn record_to_path_member( let Some(value) = record.get("value") else { return Err(ShellError::CantFindColumn { col_name: "value".into(), - span: val_span, + span: Some(val_span), src_span: span, }); }; diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index af10732aea..b928bebe23 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -1,5 +1,5 @@ use crate::{generate_strftime_list, parse_date_from_string}; -use chrono::{DateTime, FixedOffset, Local, NaiveTime, TimeZone, Utc}; +use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, TimeZone, Utc}; use human_date_parser::{from_human_time, ParseResult}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; @@ -162,24 +162,37 @@ impl Command for SubCommand { }; vec![ Example { - description: "Convert any standard timestamp string to datetime", + description: "Convert timestamp string to datetime with timezone offset", example: "'27.02.2021 1:55 pm +0000' | into datetime", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434100_000000000), }, Example { - description: "Convert any standard timestamp string to datetime", + description: "Convert standard timestamp string to datetime with timezone offset", example: "'2021-02-27T13:55:40.2246+00:00' | into datetime", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434140_224600000), }, Example { description: - "Convert non-standard timestamp string to datetime using a custom format", + "Convert non-standard timestamp string, with timezone offset, to datetime using a custom format", example: "'20210227_135540+0000' | into datetime --format '%Y%m%d_%H%M%S%z'", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434140_000000000), }, + Example { + description: "Convert non-standard timestamp string, without timezone offset, to datetime with custom formatting", + example: "'16.11.1984 8:00 am' | into datetime --format '%d.%m.%Y %H:%M %P'", + #[allow(clippy::inconsistent_digit_grouping)] + result: Some(Value::date( + DateTime::from_naive_utc_and_offset( + NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P") + .expect("date calculation should not fail in test"), + *Local::now().offset(), + ), + Span::test_data(), + )), + }, Example { description: "Convert nanosecond-precision unix timestamp to a datetime with offset from UTC", @@ -372,10 +385,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { Some(dt) => match DateTime::parse_from_str(val, &dt.0) { Ok(d) => Value::date ( d, head ), Err(reason) => { - Value::error ( - ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }, - head, - ) + match NaiveDateTime::parse_from_str(val, &dt.0) { + Ok(d) => Value::date ( + DateTime::from_naive_utc_and_offset( + d, + *Local::now().offset(), + ), + head, + ), + Err(_) => { + Value::error ( + ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }, + head, + ) + } + } } }, @@ -457,7 +481,7 @@ mod tests { } #[test] - fn takes_a_date_format() { + fn takes_a_date_format_with_timezone() { let date_str = Value::test_string("16.11.1984 8:00 am +0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); let args = Arguments { @@ -473,6 +497,26 @@ mod tests { assert_eq!(actual, expected) } + #[test] + fn takes_a_date_format_without_timezone() { + let date_str = Value::test_string("16.11.1984 8:00 am"); + let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P".to_string())); + let args = Arguments { + zone_options: None, + format_options: fmt_options, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); + let expected = Value::date( + DateTime::from_naive_utc_and_offset( + NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P").unwrap(), + *Local::now().offset(), + ), + Span::test_data(), + ); + assert_eq!(actual, expected) + } + #[test] fn takes_iso8601_date_format() { let date_str = Value::test_string("2020-08-04T16:39:18+00:00"); diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index b3c1e65a3b..010c031b2a 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { Value::Filesize { .. } => input.clone(), Value::Int { val, .. } => Value::filesize(*val, value_span), Value::Float { val, .. } => Value::filesize(*val as i64, value_span), - Value::String { val, .. } => match int_from_string(val, value_span) { + Value::String { val, .. } => match i64_from_string(val, value_span) { Ok(val) => Value::filesize(val, value_span), Err(error) => Value::error(error, value_span), }, @@ -138,7 +138,8 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { ), } } -fn int_from_string(a_string: &str, span: Span) -> Result { + +fn i64_from_string(a_string: &str, span: Span) -> Result { // Get the Locale so we know what the thousands separator is let locale = get_system_locale(); @@ -148,29 +149,46 @@ fn int_from_string(a_string: &str, span: Span) -> Result { let clean_string = no_comma_string.trim(); // Hadle negative file size - if let Some(stripped_string) = clean_string.strip_prefix('-') { - match stripped_string.parse::() { - Ok(n) => Ok(-(n.as_u64() as i64)), - Err(_) => Err(ShellError::CantConvert { - to_type: "int".into(), - from_type: "string".into(), - span, - help: None, - }), + if let Some(stripped_negative_string) = clean_string.strip_prefix('-') { + match stripped_negative_string.parse::() { + Ok(n) => i64_from_byte_size(n, true, span), + Err(_) => Err(string_convert_error(span)), + } + } else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') { + match stripped_positive_string.parse::() { + Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => { + i64_from_byte_size(n, false, span) + } + _ => Err(string_convert_error(span)), } } else { match clean_string.parse::() { - Ok(n) => Ok(n.0 as i64), - Err(_) => Err(ShellError::CantConvert { - to_type: "int".into(), - from_type: "string".into(), - span, - help: None, - }), + Ok(n) => i64_from_byte_size(n, false, span), + Err(_) => Err(string_convert_error(span)), } } } +fn i64_from_byte_size( + byte_size: bytesize::ByteSize, + is_negative: bool, + span: Span, +) -> Result { + match i64::try_from(byte_size.as_u64()) { + Ok(n) => Ok(if is_negative { -n } else { n }), + Err(_) => Err(string_convert_error(span)), + } +} + +fn string_convert_error(span: Span) -> ShellError { + ShellError::CantConvert { + to_type: "filesize".into(), + from_type: "string".into(), + span, + help: None, + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/debug/metadata.rs b/crates/nu-command/src/debug/metadata.rs index 135047a3d9..543e598e28 100644 --- a/crates/nu-command/src/debug/metadata.rs +++ b/crates/nu-command/src/debug/metadata.rs @@ -80,16 +80,23 @@ impl Command for Metadata { match x { PipelineMetadata { data_source: DataSource::Ls, + .. } => record.push("source", Value::string("ls", head)), PipelineMetadata { data_source: DataSource::HtmlThemes, + .. } => record.push("source", Value::string("into html --list", head)), PipelineMetadata { data_source: DataSource::FilePath(path), + .. } => record.push( "source", Value::string(path.to_string_lossy().to_string(), head), ), + _ => {} + } + if let Some(ref content_type) = x.content_type { + record.push("content_type", Value::string(content_type, head)); } } @@ -133,16 +140,23 @@ fn build_metadata_record(arg: &Value, metadata: Option<&PipelineMetadata>, head: match x { PipelineMetadata { data_source: DataSource::Ls, + .. } => record.push("source", Value::string("ls", head)), PipelineMetadata { data_source: DataSource::HtmlThemes, + .. } => record.push("source", Value::string("into html --list", head)), PipelineMetadata { data_source: DataSource::FilePath(path), + .. } => record.push( "source", Value::string(path.to_string_lossy().to_string(), head), ), + _ => {} + } + if let Some(ref content_type) = x.content_type { + record.push("content_type", Value::string(content_type, head)); } } diff --git a/crates/nu-command/src/debug/metadata_set.rs b/crates/nu-command/src/debug/metadata_set.rs index 6608827fda..e4ee97a76b 100644 --- a/crates/nu-command/src/debug/metadata_set.rs +++ b/crates/nu-command/src/debug/metadata_set.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::{DataSource, PipelineMetadata}; +use nu_protocol::DataSource; #[derive(Clone)] pub struct MetadataSet; @@ -27,6 +27,12 @@ impl Command for MetadataSet { "Assign the DataSource::FilePath metadata to the input", Some('f'), ) + .named( + "content-type", + SyntaxShape::String, + "Assign content type metadata to the input", + Some('c'), + ) .allow_variants_without_examples(true) .category(Category::Debug) } @@ -41,33 +47,30 @@ impl Command for MetadataSet { let head = call.head; let ds_fp: Option = call.get_flag(engine_state, stack, "datasource-filepath")?; let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?; + let content_type: Option = call.get_flag(engine_state, stack, "content-type")?; + + let metadata = input + .metadata() + .clone() + .unwrap_or_default() + .with_content_type(content_type); match (ds_fp, ds_ls) { - (Some(path), false) => { - let metadata = PipelineMetadata { - data_source: DataSource::FilePath(path.into()), - }; - Ok(input.into_pipeline_data_with_metadata( - head, - engine_state.ctrlc.clone(), - metadata, - )) - } - (None, true) => { - let metadata = PipelineMetadata { - data_source: DataSource::Ls, - }; - Ok(input.into_pipeline_data_with_metadata( - head, - engine_state.ctrlc.clone(), - metadata, - )) - } - _ => Err(ShellError::IncorrectValue { - msg: "Expected either --datasource-ls(-l) or --datasource-filepath(-f)".to_string(), - val_span: head, - call_span: head, - }), + (Some(path), false) => Ok(input.into_pipeline_data_with_metadata( + head, + engine_state.ctrlc.clone(), + metadata.with_data_source(DataSource::FilePath(path.into())), + )), + (None, true) => Ok(input.into_pipeline_data_with_metadata( + head, + engine_state.ctrlc.clone(), + metadata.with_data_source(DataSource::Ls), + )), + _ => Ok(input.into_pipeline_data_with_metadata( + head, + engine_state.ctrlc.clone(), + metadata, + )), } } @@ -83,18 +86,23 @@ impl Command for MetadataSet { example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata", result: None, }, + Example { + description: "Set the metadata of a file path", + example: "'crates' | metadata set --content-type text/plain | metadata", + result: Some(Value::record(record!("content_type" => Value::string("text/plain", Span::test_data())), Span::test_data())), + }, ] } } #[cfg(test)] mod test { + use crate::{test_examples_with_commands, Metadata}; + use super::*; #[test] fn test_examples() { - use crate::test_examples; - - test_examples(MetadataSet {}) + test_examples_with_commands(MetadataSet {}, &[&Metadata {}]) } } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a23f9c4ef4..847d2349ed 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -127,7 +127,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { SysTemp, SysUsers, UName, - + Which, }; // Help @@ -172,9 +172,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ))] bind_command! { Ps }; - #[cfg(feature = "which-support")] - bind_command! { Which }; - // Strings bind_command! { Char, @@ -192,6 +189,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Str, StrCapitalize, StrContains, + StrDeunicode, StrDistance, StrDowncase, StrEndswith, diff --git a/crates/nu-command/src/env/export_env.rs b/crates/nu-command/src/env/export_env.rs index 20605a9bb5..00f2c73ef4 100644 --- a/crates/nu-command/src/env/export_env.rs +++ b/crates/nu-command/src/env/export_env.rs @@ -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, diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 71c1e6dc3f..0d8b118e8d 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -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, diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 57fe1c17e3..4e09d900d2 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -135,6 +135,11 @@ impl Command for Cd { example: r#"cd -"#, result: None, }, + Example { + description: "Changing directory with a custom command requires 'def --env'", + example: r#"def --env gohome [] { cd ~ }"#, + result: None, + }, ] } } diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index a89cf04ed6..a3ab1eca54 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -122,6 +122,7 @@ impl Command for Ls { ctrl_c, PipelineMetadata { data_source: DataSource::Ls, + content_type: None, }, )), Some(pattern) => { @@ -145,6 +146,7 @@ impl Command for Ls { ctrl_c, PipelineMetadata { data_source: DataSource::Ls, + content_type: None, }, )) } @@ -175,20 +177,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", diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 842eaa5f4c..9000359450 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -147,6 +147,7 @@ impl Command for Open { ByteStream::file(file, call_span, ctrlc.clone()), Some(PipelineMetadata { data_source: DataSource::FilePath(path.to_path_buf()), + content_type: None, }), ); diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index ab5257fb01..1cfbbc67b4 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -461,7 +461,7 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result }; file.map_err(|e| ShellError::GenericError { - error: "Permission denied".into(), + error: format!("Problem with [{}], Permission denied", path.display()), msg: e.to_string(), span: Some(span), help: None, diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs index 0253e7e317..2a1e128b04 100644 --- a/crates/nu-command/src/filesystem/touch.rs +++ b/crates/nu-command/src/filesystem/touch.rs @@ -35,12 +35,12 @@ impl Command for Touch { ) .switch( "modified", - "change the modification time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used", + "change the modification time of the file or directory. If no reference file/directory is given, the current time is used", Some('m'), ) .switch( "access", - "change the access time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used", + "change the access time of the file or directory. If no reference file/directory is given, the current time is used", Some('a'), ) .switch( @@ -189,11 +189,6 @@ impl Command for Touch { example: r#"touch -m -r fixture.json d e"#, result: None, }, - Example { - description: r#"Changes the last accessed time of "fixture.json" to a date"#, - example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#, - result: None, - }, ] } } diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index dfdef66969..af626c1e75 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -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 = 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, diff --git a/crates/nu-command/src/filters/split_by.rs b/crates/nu-command/src/filters/split_by.rs index 0d3bf1cd30..419119fe0c 100644 --- a/crates/nu-command/src/filters/split_by.rs +++ b/crates/nu-command/src/filters/split_by.rs @@ -130,7 +130,7 @@ pub fn split( Some(group_key) => Ok(group_key.coerce_string()?), None => Err(ShellError::CantFindColumn { col_name: column_name.item.to_string(), - span: column_name.span, + span: Some(column_name.span), src_span: row.span(), }), } diff --git a/crates/nu-command/src/filters/uniq_by.rs b/crates/nu-command/src/filters/uniq_by.rs index 7bbeb0afe2..e1a502b06b 100644 --- a/crates/nu-command/src/filters/uniq_by.rs +++ b/crates/nu-command/src/filters/uniq_by.rs @@ -123,7 +123,7 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr if let Some(nonexistent) = nonexistent_column(columns, record.columns()) { return Err(ShellError::CantFindColumn { col_name: nonexistent, - span, + span: Some(span), src_span: val_span, }); } diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index fe73de354f..922879d09b 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -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![ diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 853f3bd83e..8f84890645 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -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()) diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index e8b5d58e9e..a1b43abb0a 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -4,7 +4,7 @@ use std::{ }; use nu_engine::command_prelude::*; -use nu_protocol::ListStream; +use nu_protocol::{ListStream, PipelineMetadata}; #[derive(Clone)] pub struct FromJson; @@ -81,7 +81,7 @@ impl Command for FromJson { PipelineData::Value(Value::String { val, .. }, metadata) => { Ok(PipelineData::ListStream( read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()), - metadata, + update_metadata(metadata), )) } PipelineData::ByteStream(stream, metadata) @@ -90,7 +90,7 @@ impl Command for FromJson { if let Some(reader) = stream.reader() { Ok(PipelineData::ListStream( read_json_lines(reader, span, strict, None), - metadata, + update_metadata(metadata), )) } else { Ok(PipelineData::Empty) @@ -113,10 +113,10 @@ impl Command for FromJson { if strict { Ok(convert_string_to_value_strict(&string_input, span)? - .into_pipeline_data_with_metadata(metadata)) + .into_pipeline_data_with_metadata(update_metadata(metadata))) } else { Ok(convert_string_to_value(&string_input, span)? - .into_pipeline_data_with_metadata(metadata)) + .into_pipeline_data_with_metadata(update_metadata(metadata))) } } } @@ -263,6 +263,14 @@ fn convert_string_to_value_strict(string_input: &str, span: Span) -> Result) -> Option { + metadata + .map(|md| md.with_content_type(Some("application/json".into()))) + .or_else(|| { + Some(PipelineMetadata::default().with_content_type(Some("application/json".into()))) + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs index 5efb2a6c3b..1d17dfc69d 100644 --- a/crates/nu-command/src/formats/from/ssv.rs +++ b/crates/nu-command/src/formats/from/ssv.rs @@ -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::>(); 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") ], ] ); diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index e1ddca3164..266911f50a 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use std::str::FromStr; +use toml::value::{Datetime, Offset}; #[derive(Clone)] pub struct FromToml; @@ -56,6 +56,54 @@ b = [1, 2]' | from toml", } } +fn convert_toml_datetime_to_value(dt: &Datetime, span: Span) -> Value { + match &dt.clone() { + toml::value::Datetime { + date: Some(_), + time: _, + offset: _, + } => (), + _ => return Value::string(dt.to_string(), span), + } + + let date = match dt.date { + Some(date) => { + chrono::NaiveDate::from_ymd_opt(date.year.into(), date.month.into(), date.day.into()) + } + None => Some(chrono::NaiveDate::default()), + }; + + let time = match dt.time { + Some(time) => chrono::NaiveTime::from_hms_nano_opt( + time.hour.into(), + time.minute.into(), + time.second.into(), + time.nanosecond, + ), + None => Some(chrono::NaiveTime::default()), + }; + + let tz = match dt.offset { + Some(offset) => match offset { + Offset::Z => chrono::FixedOffset::east_opt(0), + Offset::Custom { minutes: min } => chrono::FixedOffset::east_opt(min as i32 * 60), + }, + None => chrono::FixedOffset::east_opt(0), + }; + + let datetime = match (date, time, tz) { + (Some(date), Some(time), Some(tz)) => chrono::NaiveDateTime::new(date, time) + .and_local_timezone(tz) + .earliest(), + _ => None, + }; + + match datetime { + Some(datetime) => Value::date(datetime, span), + None => Value::string(dt.to_string(), span), + } +} + fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { match value { toml::Value::Array(array) => { @@ -76,13 +124,7 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { span, ), toml::Value::String(s) => Value::string(s.clone(), span), - toml::Value::Datetime(d) => match chrono::DateTime::from_str(&d.to_string()) { - Ok(nushell_date) => Value::date(nushell_date, span), - // in the unlikely event that parsing goes wrong, this function still returns a valid - // nushell date (however the default one). This decision was made to make the output of - // this function uniform amongst all eventualities - Err(_) => Value::date(chrono::DateTime::default(), span), - }, + toml::Value::Datetime(dt) => convert_toml_datetime_to_value(dt, span), } } @@ -113,32 +155,6 @@ mod tests { test_examples(FromToml {}) } - #[test] - fn from_toml_creates_nushell_date() { - let toml_date = toml::Value::Datetime(Datetime { - date: Option::from(toml::value::Date { - year: 1980, - month: 10, - day: 12, - }), - time: Option::from(toml::value::Time { - hour: 10, - minute: 12, - second: 44, - nanosecond: 0, - }), - offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), - }); - - let span = Span::test_data(); - let reference_date = Value::date(Default::default(), Span::test_data()); - - let result = convert_toml_to_value(&toml_date, span); - - //positive test (from toml returns a nushell date) - assert_eq!(result.get_type(), reference_date.get_type()); - } - #[test] fn from_toml_creates_correct_date() { let toml_date = toml::Value::Datetime(Datetime { @@ -206,4 +222,113 @@ mod tests { assert!(result.is_err()); } + + #[test] + fn convert_toml_datetime_to_value_date_time_offset() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(60 * 120) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date_time() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: None, + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 0, 0, 0) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_only_time() { + let toml_date = Datetime { + date: None, + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::string(toml_date.to_string(), span); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } } diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs index c4c87f804f..5722387e2c 100644 --- a/crates/nu-command/src/formats/to/json.rs +++ b/crates/nu-command/src/formats/to/json.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::ast::PathMember; +use nu_protocol::{ast::PathMember, PipelineMetadata}; #[derive(Clone)] pub struct ToJson; @@ -61,7 +61,12 @@ impl Command for ToJson { match json_result { Ok(serde_json_string) => { - Ok(Value::string(serde_json_string, span).into_pipeline_data()) + let res = Value::string(serde_json_string, span); + let metadata = PipelineMetadata { + data_source: nu_protocol::DataSource::None, + content_type: Some("application/json".to_string()), + }; + Ok(PipelineData::Value(res, Some(metadata))) } _ => Ok(Value::error( ShellError::CantConvert { diff --git a/crates/nu-command/src/formats/to/msgpackz.rs b/crates/nu-command/src/formats/to/msgpackz.rs index 9168d05018..83d812cab6 100644 --- a/crates/nu-command/src/formats/to/msgpackz.rs +++ b/crates/nu-command/src/formats/to/msgpackz.rs @@ -5,7 +5,7 @@ use nu_engine::command_prelude::*; use super::msgpack::write_value; const BUFFER_SIZE: usize = 65536; -const DEFAULT_QUALITY: u32 = 1; +const DEFAULT_QUALITY: u32 = 3; // 1 can be very bad const DEFAULT_WINDOW_SIZE: u32 = 20; #[derive(Clone)] @@ -22,7 +22,7 @@ impl Command for ToMsgpackz { .named( "quality", SyntaxShape::Int, - "Quality of brotli compression (default 1)", + "Quality of brotli compression (default 3)", Some('q'), ) .named( diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index fb240654f6..1aa1114bce 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -1,6 +1,8 @@ use chrono_humanize::HumanTime; use nu_engine::command_prelude::*; -use nu_protocol::{format_duration, format_filesize_from_conf, ByteStream, Config}; +use nu_protocol::{ + format_duration, format_filesize_from_conf, ByteStream, Config, PipelineMetadata, +}; const LINE_ENDING: &str = if cfg!(target_os = "windows") { "\r\n" @@ -37,10 +39,14 @@ impl Command for ToText { let input = input.try_expand_range()?; match input { - PipelineData::Empty => Ok(Value::string(String::new(), span).into_pipeline_data()), + PipelineData::Empty => Ok(Value::string(String::new(), span) + .into_pipeline_data_with_metadata(update_metadata(None))), PipelineData::Value(value, ..) => { let str = local_into_string(value, LINE_ENDING, engine_state.get_config()); - Ok(Value::string(str, span).into_pipeline_data()) + Ok( + Value::string(str, span) + .into_pipeline_data_with_metadata(update_metadata(None)), + ) } PipelineData::ListStream(stream, meta) => { let span = stream.span(); @@ -57,10 +63,12 @@ impl Command for ToText { engine_state.ctrlc.clone(), ByteStreamType::String, ), - meta, + update_metadata(meta), )) } - PipelineData::ByteStream(stream, meta) => Ok(PipelineData::ByteStream(stream, meta)), + PipelineData::ByteStream(stream, meta) => { + Ok(PipelineData::ByteStream(stream, update_metadata(meta))) + } } } @@ -124,6 +132,14 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String { } } +fn update_metadata(metadata: Option) -> Option { + metadata + .map(|md| md.with_content_type(Some("text/plain".to_string()))) + .or_else(|| { + Some(PipelineMetadata::default().with_content_type(Some("text/plain".to_string()))) + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index 7423f147dd..ed1490231c 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -1,4 +1,4 @@ -use chrono::SecondsFormat; +use chrono::{DateTime, Datelike, FixedOffset, Timelike}; use nu_engine::command_prelude::*; use nu_protocol::ast::PathMember; @@ -24,7 +24,7 @@ impl Command for ToToml { vec![Example { description: "Outputs an TOML string representing the contents of this record", example: r#"{foo: 1 bar: 'qwe'} | to toml"#, - result: Some(Value::test_string("bar = \"qwe\"\nfoo = 1\n")), + result: Some(Value::test_string("foo = 1\nbar = \"qwe\"\n")), }] } @@ -49,9 +49,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result toml::Value::Integer(*val), Value::Filesize { val, .. } => toml::Value::Integer(*val), Value::Duration { val, .. } => toml::Value::String(val.to_string()), - Value::Date { val, .. } => { - toml::Value::String(val.to_rfc3339_opts(SecondsFormat::AutoSi, false)) - } + Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)), Value::Range { .. } => toml::Value::String("".to_string()), Value::Float { val, .. } => toml::Value::Float(*val), Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()), @@ -103,7 +101,7 @@ fn toml_into_pipeline_data( value_type: Type, span: Span, ) -> Result { - match toml::to_string(&toml_value) { + match toml::to_string_pretty(&toml_value) { Ok(serde_toml_string) => Ok(Value::string(serde_toml_string, span).into_pipeline_data()), _ => Ok(Value::error( ShellError::CantConvert { @@ -157,6 +155,43 @@ fn to_toml( } } +/// Convert chrono datetime into a toml::Value datetime. The latter uses its +/// own ad-hoc datetime types, which makes this somewhat convoluted. +fn to_toml_datetime(datetime: &DateTime) -> toml::value::Datetime { + let date = toml::value::Date { + // TODO: figure out what to do with BC dates, because the toml + // crate doesn't support them. Same for large years, which + // don't fit in u16. + year: datetime.year_ce().1 as u16, + // Panic: this is safe, because chrono guarantees that the month + // value will be between 1 and 12 and the day will be between 1 + // and 31 + month: datetime.month() as u8, + day: datetime.day() as u8, + }; + + let time = toml::value::Time { + // Panic: same as before, chorono guarantees that all of the following 3 + // methods return values less than 65'000 + hour: datetime.hour() as u8, + minute: datetime.minute() as u8, + second: datetime.second() as u8, + nanosecond: datetime.nanosecond(), + }; + + let offset = toml::value::Offset::Custom { + // Panic: minute timezone offset fits into i16 (that's more than + // 1000 hours) + minutes: (-datetime.timezone().utc_minus_local() / 60) as i16, + }; + + toml::value::Datetime { + date: Some(date), + time: Some(time), + offset: Some(offset), + } +} + #[cfg(test)] mod tests { use super::*; @@ -181,7 +216,20 @@ mod tests { Span::test_data(), ); - let reference_date = toml::Value::String(String::from("1980-10-12T10:12:44+02:00")); + let reference_date = toml::Value::Datetime(toml::value::Datetime { + date: Some(toml::value::Date { + year: 1980, + month: 10, + day: 12, + }), + time: Some(toml::value::Time { + hour: 10, + minute: 12, + second: 44, + nanosecond: 0, + }), + offset: Some(toml::value::Offset::Custom { minutes: 120 }), + }); let result = helper(&engine_state, &test_date); diff --git a/crates/nu-command/src/generators/cal.rs b/crates/nu-command/src/generators/cal.rs index e1ecc771de..a257f3ab7c 100644 --- a/crates/nu-command/src/generators/cal.rs +++ b/crates/nu-command/src/generators/cal.rs @@ -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>, week_start: Option>, + 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 { diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 3549667ff0..44bca993d0 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -1,4 +1,3 @@ -use itertools::unfold; use nu_engine::{command_prelude::*, ClosureEval}; use nu_protocol::engine::Closure; @@ -12,13 +11,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 +56,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, }, ] } @@ -100,7 +80,8 @@ used as the next argument to the closure, otherwise generation stops. // A type of Option is used to represent state. Invocation // will stop on None. Using Option allows functions to output // one final value before stopping. - let iter = unfold(Some(initial), move |state| { + let mut state = Some(initial); + let iter = std::iter::from_fn(move || { let arg = state.take()?; let (output, next_input) = match closure.run_with_value(arg) { @@ -179,7 +160,7 @@ used as the next argument to the closure, otherwise generation stops. // We use `state` to control when to stop, not `output`. By wrapping // it in a `Some`, we allow the generator to output `None` as a valid output // value. - *state = next_input; + state = next_input; Some(output) }); diff --git a/crates/nu-command/src/help/help_commands.rs b/crates/nu-command/src/help/help_commands.rs index f2440cc36f..f067a9fdcb 100644 --- a/crates/nu-command/src/help/help_commands.rs +++ b/crates/nu-command/src/help/help_commands.rs @@ -122,7 +122,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec { let usage = sig.usage; let search_terms = sig.search_terms; - let command_type = format!("{:?}", decl.command_type()).to_ascii_lowercase(); + let command_type = decl.command_type().to_string(); // Build table of parameters let param_table = { diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index a7beee3dd5..12803fad8e 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator}; #[derive(Clone)] pub struct HelpOperators; @@ -27,282 +28,148 @@ impl Command for HelpOperators { _input: PipelineData, ) -> Result { let head = call.head; - let op_info = generate_operator_info(); - let mut recs = vec![]; - - for op in op_info { - recs.push(Value::record( + let mut operators = [ + Operator::Assignment(Assignment::Assign), + Operator::Assignment(Assignment::PlusAssign), + Operator::Assignment(Assignment::AppendAssign), + Operator::Assignment(Assignment::MinusAssign), + Operator::Assignment(Assignment::MultiplyAssign), + Operator::Assignment(Assignment::DivideAssign), + Operator::Comparison(Comparison::Equal), + Operator::Comparison(Comparison::NotEqual), + Operator::Comparison(Comparison::LessThan), + Operator::Comparison(Comparison::GreaterThan), + Operator::Comparison(Comparison::LessThanOrEqual), + Operator::Comparison(Comparison::GreaterThanOrEqual), + Operator::Comparison(Comparison::RegexMatch), + Operator::Comparison(Comparison::NotRegexMatch), + Operator::Comparison(Comparison::In), + Operator::Comparison(Comparison::NotIn), + Operator::Comparison(Comparison::StartsWith), + Operator::Comparison(Comparison::EndsWith), + Operator::Math(Math::Plus), + Operator::Math(Math::Append), + Operator::Math(Math::Minus), + Operator::Math(Math::Multiply), + Operator::Math(Math::Divide), + Operator::Math(Math::Modulo), + Operator::Math(Math::FloorDivision), + Operator::Math(Math::Pow), + Operator::Bits(Bits::BitOr), + Operator::Bits(Bits::BitXor), + Operator::Bits(Bits::BitAnd), + Operator::Bits(Bits::ShiftLeft), + Operator::Bits(Bits::ShiftRight), + Operator::Boolean(Boolean::And), + Operator::Boolean(Boolean::Or), + Operator::Boolean(Boolean::Xor), + ] + .into_iter() + .map(|op| { + Value::record( record! { - "type" => Value::string(op.op_type, head), - "operator" => Value::string(op.operator, head), - "name" => Value::string(op.name, head), - "description" => Value::string(op.description, head), - "precedence" => Value::int(op.precedence, head), + "type" => Value::string(op_type(&op), head), + "operator" => Value::string(op.to_string(), head), + "name" => Value::string(name(&op), head), + "description" => Value::string(description(&op), head), + "precedence" => Value::int(op.precedence().into(), head), }, head, - )); - } + ) + }) + .collect::>(); - Ok(Value::list(recs, head).into_pipeline_data()) + operators.push(Value::record( + record! { + "type" => Value::string("Boolean", head), + "operator" => Value::string("not", head), + "name" => Value::string("Not", head), + "description" => Value::string("Negates a value or expression.", head), + "precedence" => Value::int(0, head), + }, + head, + )); + + Ok(Value::list(operators, head).into_pipeline_data()) } } -struct OperatorInfo { - op_type: String, - operator: String, - name: String, - description: String, - precedence: i64, +fn op_type(operator: &Operator) -> &'static str { + match operator { + Operator::Comparison(_) => "Comparison", + Operator::Math(_) => "Math", + Operator::Boolean(_) => "Boolean", + Operator::Bits(_) => "Bitwise", + Operator::Assignment(_) => "Assignment", + } } -fn generate_operator_info() -> Vec { - vec![ - OperatorInfo { - op_type: "Assignment".into(), - operator: "=".into(), - name: "Assign".into(), - description: "Assigns a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "+=".into(), - name: "PlusAssign".into(), - description: "Adds a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "++=".into(), - name: "AppendAssign".into(), - description: "Appends a list or a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "-=".into(), - name: "MinusAssign".into(), - description: "Subtracts a value from a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "*=".into(), - name: "MultiplyAssign".into(), - description: "Multiplies a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "/=".into(), - name: "DivideAssign".into(), - description: "Divides a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "==".into(), - name: "Equal".into(), - description: "Checks if two values are equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!=".into(), - name: "NotEqual".into(), - description: "Checks if two values are not equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<".into(), - name: "LessThan".into(), - description: "Checks if a value is less than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<=".into(), - name: "LessThanOrEqual".into(), - description: "Checks if a value is less than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">".into(), - name: "GreaterThan".into(), - description: "Checks if a value is greater than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">=".into(), - name: "GreaterThanOrEqual".into(), - description: "Checks if a value is greater than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "=~".into(), - name: "RegexMatch".into(), - description: "Checks if a value matches a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!~".into(), - name: "NotRegexMatch".into(), - description: "Checks if a value does not match a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "in".into(), - name: "In".into(), - description: "Checks if a value is in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not-in".into(), - name: "NotIn".into(), - description: "Checks if a value is not in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "starts-with".into(), - name: "StartsWith".into(), - description: "Checks if a string starts with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "ends-with".into(), - name: "EndsWith".into(), - description: "Checks if a string ends with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not".into(), - name: "UnaryNot".into(), - description: "Negates a value or expression.".into(), - precedence: 0, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "+".into(), - name: "Plus".into(), - description: "Adds two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "++".into(), - name: "Append".into(), - description: "Appends two lists or a list and a value.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "-".into(), - name: "Minus".into(), - description: "Subtracts two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "*".into(), - name: "Multiply".into(), - description: "Multiplies two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "/".into(), - name: "Divide".into(), - description: "Divides two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "//".into(), - name: "FloorDivision".into(), - description: "Divides two values and floors the result.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "mod".into(), - name: "Modulo".into(), - description: "Divides two values and returns the remainder.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "**".into(), - name: "Pow ".into(), - description: "Raises one value to the power of another.".into(), - precedence: 100, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-or".into(), - name: "BitOr".into(), - description: "Performs a bitwise OR on two values.".into(), - precedence: 60, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-xor".into(), - name: "BitXor".into(), - description: "Performs a bitwise XOR on two values.".into(), - precedence: 70, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-and".into(), - name: "BitAnd".into(), - description: "Performs a bitwise AND on two values.".into(), - precedence: 75, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shl".into(), - name: "ShiftLeft".into(), - description: "Shifts a value left by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shr".into(), - name: "ShiftRight".into(), - description: "Shifts a value right by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "and".into(), - name: "And".into(), - description: "Checks if two values are true.".into(), - precedence: 50, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "or".into(), - name: "Or".into(), - description: "Checks if either value is true.".into(), - precedence: 40, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "xor".into(), - name: "Xor".into(), - description: "Checks if one value is true and the other is false.".into(), - precedence: 45, - }, - ] +fn name(operator: &Operator) -> String { + match operator { + Operator::Comparison(op) => format!("{op:?}"), + Operator::Math(op) => format!("{op:?}"), + Operator::Boolean(op) => format!("{op:?}"), + Operator::Bits(op) => format!("{op:?}"), + Operator::Assignment(op) => format!("{op:?}"), + } +} + +fn description(operator: &Operator) -> &'static str { + // NOTE: if you have to add an operator here, also add it to the operator list above. + match operator { + Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.", + Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.", + Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.", + Operator::Comparison(Comparison::GreaterThan) => { + "Checks if a value is greater than another." + } + Operator::Comparison(Comparison::LessThanOrEqual) => { + "Checks if a value is less than or equal to another." + } + Operator::Comparison(Comparison::GreaterThanOrEqual) => { + "Checks if a value is greater than or equal to another." + } + Operator::Comparison(Comparison::RegexMatch) => { + "Checks if a value matches a regular expression." + } + Operator::Comparison(Comparison::NotRegexMatch) => { + "Checks if a value does not match a regular expression." + } + Operator::Comparison(Comparison::In) => { + "Checks if a value is in a list, is part of a string, or is a key in a record." + } + Operator::Comparison(Comparison::NotIn) => { + "Checks if a value is not in a list, is not part of a string, or is not a key in a record." + } + Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.", + Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.", + Operator::Math(Math::Plus) => "Adds two values.", + Operator::Math(Math::Append) => { + "Appends two lists, a list and a value, two strings, or two binary values." + } + Operator::Math(Math::Minus) => "Subtracts two values.", + Operator::Math(Math::Multiply) => "Multiplies two values.", + Operator::Math(Math::Divide) => "Divides two values.", + Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.", + Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.", + Operator::Math(Math::Pow) => "Raises one value to the power of another.", + Operator::Boolean(Boolean::And) => "Checks if both values are true.", + Operator::Boolean(Boolean::Or) => "Checks if either value is true.", + Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.", + Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.", + Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.", + Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.", + Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.", + Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.", + Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.", + Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.", + Operator::Assignment(Assignment::AppendAssign) => { + "Appends a list, a value, a string, or a binary value to a variable." + } + Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.", + Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.", + Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.", + } } #[cfg(test)] diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index 8317fb50bc..2c02d7be33 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -180,88 +180,113 @@ impl From for ShellErrorOrRequestError { } } +#[derive(Debug)] +pub enum HttpBody { + Value(Value), + ByteStream(ByteStream), + None, +} + +// remove once all commands have been migrated pub fn send_request( request: Request, - body: Option, + http_body: HttpBody, content_type: Option, ctrl_c: Option>, ) -> Result { let request_url = request.url().to_string(); - if body.is_none() { - return send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c); - } - let body = body.expect("Should never be none."); - let body_type = match content_type { - Some(it) if it == "application/json" => BodyType::Json, - Some(it) if it == "application/x-www-form-urlencoded" => BodyType::Form, - _ => BodyType::Unknown, - }; - match body { - Value::Binary { val, .. } => send_cancellable_request( - &request_url, - Box::new(move || request.send_bytes(&val)), - ctrl_c, - ), - Value::String { .. } if body_type == BodyType::Json => { - let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c) + match http_body { + HttpBody::None => { + send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c) } - Value::String { val, .. } => send_cancellable_request( - &request_url, - Box::new(move || request.send_string(&val)), - ctrl_c, - ), - Value::Record { .. } if body_type == BodyType::Json => { - let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c) - } - Value::Record { val, .. } if body_type == BodyType::Form => { - let mut data: Vec<(String, String)> = Vec::with_capacity(val.len()); - - for (col, val) in val.into_owned() { - data.push((col, val.coerce_into_string()?)) - } - - let request_fn = move || { - // coerce `data` into a shape that send_form() is happy with - let data = data - .iter() - .map(|(a, b)| (a.as_str(), b.as_str())) - .collect::>(); - request.send_form(&data) + HttpBody::ByteStream(byte_stream) => { + let req = if let Some(content_type) = content_type { + request.set("Content-Type", &content_type) + } else { + request }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + + send_cancellable_request_bytes(&request_url, req, byte_stream, ctrl_c) } - Value::List { vals, .. } if body_type == BodyType::Form => { - if vals.len() % 2 != 0 { - return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { + HttpBody::Value(body) => { + let (body_type, req) = match content_type { + Some(it) if it == "application/json" => (BodyType::Json, request), + Some(it) if it == "application/x-www-form-urlencoded" => (BodyType::Form, request), + Some(it) => { + let r = request.clone().set("Content-Type", &it); + (BodyType::Unknown, r) + } + _ => (BodyType::Unknown, request), + }; + + match body { + Value::Binary { val, .. } => send_cancellable_request( + &request_url, + Box::new(move || req.send_bytes(&val)), + ctrl_c, + ), + Value::String { .. } if body_type == BodyType::Json => { + let data = value_to_json_value(&body)?; + send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + } + Value::String { val, .. } => send_cancellable_request( + &request_url, + Box::new(move || req.send_string(&val)), + ctrl_c, + ), + Value::Record { .. } if body_type == BodyType::Json => { + let data = value_to_json_value(&body)?; + send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + } + Value::Record { val, .. } if body_type == BodyType::Form => { + let mut data: Vec<(String, String)> = Vec::with_capacity(val.len()); + + for (col, val) in val.into_owned() { + data.push((col, val.coerce_into_string()?)) + } + + let request_fn = move || { + // coerce `data` into a shape that send_form() is happy with + let data = data + .iter() + .map(|(a, b)| (a.as_str(), b.as_str())) + .collect::>(); + req.send_form(&data) + }; + send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + } + Value::List { vals, .. } if body_type == BodyType::Form => { + if vals.len() % 2 != 0 { + return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { + msg: "unsupported body input".into(), + })); + } + + let data = vals + .chunks(2) + .map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?))) + .collect::, ShellErrorOrRequestError>>()?; + + let request_fn = move || { + // coerce `data` into a shape that send_form() is happy with + let data = data + .iter() + .map(|(a, b)| (a.as_str(), b.as_str())) + .collect::>(); + req.send_form(&data) + }; + send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + } + Value::List { .. } if body_type == BodyType::Json => { + let data = value_to_json_value(&body)?; + send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + } + _ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { msg: "unsupported body input".into(), - })); + })), } - - let data = vals - .chunks(2) - .map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?))) - .collect::, ShellErrorOrRequestError>>()?; - - let request_fn = move || { - // coerce `data` into a shape that send_form() is happy with - let data = data - .iter() - .map(|(a, b)| (a.as_str(), b.as_str())) - .collect::>(); - request.send_form(&data) - }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) } - Value::List { .. } if body_type == BodyType::Json => { - let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c) - } - _ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { - msg: "unsupported body input".into(), - })), } } @@ -305,6 +330,61 @@ fn send_cancellable_request( } } +// Helper method used to make blocking HTTP request calls cancellable with ctrl+c +// ureq functions can block for a long time (default 30s?) while attempting to make an HTTP connection +fn send_cancellable_request_bytes( + request_url: &str, + request: Request, + byte_stream: ByteStream, + ctrl_c: Option>, +) -> Result { + let (tx, rx) = mpsc::channel::>(); + let request_url_string = request_url.to_string(); + + // Make the blocking request on a background thread... + std::thread::Builder::new() + .name("HTTP requester".to_string()) + .spawn(move || { + let ret = byte_stream + .reader() + .ok_or_else(|| { + ShellErrorOrRequestError::ShellError(ShellError::GenericError { + error: "Could not read byte stream".to_string(), + msg: "".into(), + span: None, + help: None, + inner: vec![], + }) + }) + .and_then(|reader| { + request.send(reader).map_err(|e| { + ShellErrorOrRequestError::RequestError(request_url_string, Box::new(e)) + }) + }); + + // may fail if the user has cancelled the operation + let _ = tx.send(ret); + }) + .map_err(ShellError::from)?; + + // ...and poll the channel for responses + loop { + if nu_utils::ctrl_c::was_pressed(&ctrl_c) { + // Return early and give up on the background thread. The connection will either time out or be disconnected + return Err(ShellErrorOrRequestError::ShellError( + ShellError::InterruptedByUser { span: None }, + )); + } + + // 100ms wait time chosen arbitrarily + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(result) => return result, + Err(RecvTimeoutError::Timeout) => continue, + Err(RecvTimeoutError::Disconnected) => panic!("http response channel disconnected"), + } + } +} + pub fn request_set_timeout( timeout: Option, mut request: Request, diff --git a/crates/nu-command/src/network/http/delete.rs b/crates/nu-command/src/network/http/delete.rs index 77cfa70fb8..c2ef774a01 100644 --- a/crates/nu-command/src/network/http/delete.rs +++ b/crates/nu-command/src/network/http/delete.rs @@ -1,7 +1,7 @@ use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response, - request_set_timeout, send_request, RequestFlags, + request_set_timeout, send_request, HttpBody, RequestFlags, }; use nu_engine::command_prelude::*; @@ -15,7 +15,7 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("http delete") - .input_output_types(vec![(Type::Nothing, Type::Any)]) + .input_output_types(vec![(Type::Any, Type::Any)]) .allow_variants_without_examples(true) .required( "URL", @@ -132,6 +132,11 @@ impl Command for SubCommand { "http delete --content-type application/json --data { field: value } https://www.example.com", result: None, }, + Example { + description: "Perform an HTTP delete with JSON content from a pipeline to example.com", + example: "open foo.json | http delete https://www.example.com", + result: None, + }, ] } } @@ -139,7 +144,7 @@ impl Command for SubCommand { struct Arguments { url: Value, headers: Option, - data: Option, + data: HttpBody, content_type: Option, raw: bool, insecure: bool, @@ -155,13 +160,27 @@ fn run_delete( engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { + let (data, maybe_metadata) = call + .get_flag::(engine_state, stack, "data")? + .map(|v| (HttpBody::Value(v), None)) + .unwrap_or_else(|| match input { + PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata), + PipelineData::ByteStream(byte_stream, metadata) => { + (HttpBody::ByteStream(byte_stream), metadata) + } + _ => (HttpBody::None, None), + }); + let content_type = call + .get_flag(engine_state, stack, "content-type")? + .or_else(|| maybe_metadata.and_then(|m| m.content_type)); + let args = Arguments { url: call.req(engine_state, stack, 0)?, headers: call.get_flag(engine_state, stack, "headers")?, - data: call.get_flag(engine_state, stack, "data")?, - content_type: call.get_flag(engine_state, stack, "content-type")?, + data, + content_type, raw: call.has_flag(engine_state, stack, "raw")?, insecure: call.has_flag(engine_state, stack, "insecure")?, user: call.get_flag(engine_state, stack, "user")?, diff --git a/crates/nu-command/src/network/http/get.rs b/crates/nu-command/src/network/http/get.rs index e86c44e0a1..04582d5f00 100644 --- a/crates/nu-command/src/network/http/get.rs +++ b/crates/nu-command/src/network/http/get.rs @@ -5,6 +5,8 @@ use crate::network::http::client::{ }; use nu_engine::command_prelude::*; +use super::client::HttpBody; + #[derive(Clone)] pub struct SubCommand; @@ -180,7 +182,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), None, None, ctrl_c); + let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/head.rs b/crates/nu-command/src/network/http/head.rs index 875cdc8d8e..57dfafcba2 100644 --- a/crates/nu-command/src/network/http/head.rs +++ b/crates/nu-command/src/network/http/head.rs @@ -7,6 +7,8 @@ use nu_engine::command_prelude::*; use std::sync::{atomic::AtomicBool, Arc}; +use super::client::HttpBody; + #[derive(Clone)] pub struct SubCommand; @@ -156,7 +158,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request, None, None, ctrlc); + let response = send_request(request, HttpBody::None, None, ctrlc); check_response_redirection(redirect_mode, span, &response)?; request_handle_response_headers(span, response) } diff --git a/crates/nu-command/src/network/http/options.rs b/crates/nu-command/src/network/http/options.rs index fe4b7dcb1a..91ecac02d5 100644 --- a/crates/nu-command/src/network/http/options.rs +++ b/crates/nu-command/src/network/http/options.rs @@ -4,6 +4,8 @@ use crate::network::http::client::{ }; use nu_engine::command_prelude::*; +use super::client::HttpBody; + #[derive(Clone)] pub struct SubCommand; @@ -159,7 +161,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), None, None, ctrl_c); + let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); // http options' response always showed in header, so we set full to true. // And `raw` is useless too because options method doesn't return body, here we set to true diff --git a/crates/nu-command/src/network/http/patch.rs b/crates/nu-command/src/network/http/patch.rs index ca302702ef..2cf66a2a82 100644 --- a/crates/nu-command/src/network/http/patch.rs +++ b/crates/nu-command/src/network/http/patch.rs @@ -1,7 +1,7 @@ use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response, - request_set_timeout, send_request, RequestFlags, + request_set_timeout, send_request, HttpBody, RequestFlags, }; use nu_engine::command_prelude::*; @@ -15,10 +15,10 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("http patch") - .input_output_types(vec![(Type::Nothing, Type::Any)]) + .input_output_types(vec![(Type::Any, Type::Any)]) .allow_variants_without_examples(true) .required("URL", SyntaxShape::String, "The URL to post to.") - .required("data", SyntaxShape::Any, "The contents of the post body.") + .optional("data", SyntaxShape::Any, "The contents of the post body.") .named( "user", SyntaxShape::Any, @@ -124,6 +124,11 @@ impl Command for SubCommand { example: "http patch --content-type application/json https://www.example.com { field: value }", result: None, }, + Example { + description: "Patch JSON content from a pipeline to example.com", + example: "open foo.json | http patch https://www.example.com", + result: None, + }, ] } } @@ -131,7 +136,7 @@ impl Command for SubCommand { struct Arguments { url: Value, headers: Option, - data: Value, + data: HttpBody, content_type: Option, raw: bool, insecure: bool, @@ -147,13 +152,37 @@ fn run_patch( engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { + let (data, maybe_metadata) = call + .opt::(engine_state, stack, 1)? + .map(|v| (HttpBody::Value(v), None)) + .unwrap_or_else(|| match input { + PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata), + PipelineData::ByteStream(byte_stream, metadata) => { + (HttpBody::ByteStream(byte_stream), metadata) + } + _ => (HttpBody::None, None), + }); + let content_type = call + .get_flag(engine_state, stack, "content-type")? + .or_else(|| maybe_metadata.and_then(|m| m.content_type)); + + if let HttpBody::None = data { + return Err(ShellError::GenericError { + error: "Data must be provided either through pipeline or positional argument".into(), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + }); + } + let args = Arguments { url: call.req(engine_state, stack, 0)?, headers: call.get_flag(engine_state, stack, "headers")?, - data: call.req(engine_state, stack, 1)?, - content_type: call.get_flag(engine_state, stack, "content-type")?, + data, + content_type, raw: call.has_flag(engine_state, stack, "raw")?, insecure: call.has_flag(engine_state, stack, "insecure")?, user: call.get_flag(engine_state, stack, "user")?, @@ -187,7 +216,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), Some(args.data), args.content_type, ctrl_c); + let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/post.rs b/crates/nu-command/src/network/http/post.rs index 1f0d7f81b0..d48bbcc9fe 100644 --- a/crates/nu-command/src/network/http/post.rs +++ b/crates/nu-command/src/network/http/post.rs @@ -1,7 +1,7 @@ use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response, - request_set_timeout, send_request, RequestFlags, + request_set_timeout, send_request, HttpBody, RequestFlags, }; use nu_engine::command_prelude::*; @@ -15,10 +15,10 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("http post") - .input_output_types(vec![(Type::Nothing, Type::Any)]) + .input_output_types(vec![(Type::Any, Type::Any)]) .allow_variants_without_examples(true) .required("URL", SyntaxShape::String, "The URL to post to.") - .required("data", SyntaxShape::Any, "The contents of the post body.") + .optional("data", SyntaxShape::Any, "The contents of the post body. Required unless part of a pipeline.") .named( "user", SyntaxShape::Any, @@ -122,6 +122,11 @@ impl Command for SubCommand { example: "http post --content-type application/json https://www.example.com { field: value }", result: None, }, + Example { + description: "Post JSON content from a pipeline to example.com", + example: "open foo.json | http post https://www.example.com", + result: None, + }, ] } } @@ -129,7 +134,7 @@ impl Command for SubCommand { struct Arguments { url: Value, headers: Option, - data: Value, + data: HttpBody, content_type: Option, raw: bool, insecure: bool, @@ -145,13 +150,37 @@ fn run_post( engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { + let (data, maybe_metadata) = call + .opt::(engine_state, stack, 1)? + .map(|v| (HttpBody::Value(v), None)) + .unwrap_or_else(|| match input { + PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata), + PipelineData::ByteStream(byte_stream, metadata) => { + (HttpBody::ByteStream(byte_stream), metadata) + } + _ => (HttpBody::None, None), + }); + let content_type = call + .get_flag(engine_state, stack, "content-type")? + .or_else(|| maybe_metadata.and_then(|m| m.content_type)); + + if let HttpBody::None = data { + return Err(ShellError::GenericError { + error: "Data must be provided either through pipeline or positional argument".into(), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + }); + } + let args = Arguments { url: call.req(engine_state, stack, 0)?, headers: call.get_flag(engine_state, stack, "headers")?, - data: call.req(engine_state, stack, 1)?, - content_type: call.get_flag(engine_state, stack, "content-type")?, + data, + content_type, raw: call.has_flag(engine_state, stack, "raw")?, insecure: call.has_flag(engine_state, stack, "insecure")?, user: call.get_flag(engine_state, stack, "user")?, @@ -185,7 +214,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), Some(args.data), args.content_type, ctrl_c); + let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/put.rs b/crates/nu-command/src/network/http/put.rs index 8abedd2b4c..dbd3b245cb 100644 --- a/crates/nu-command/src/network/http/put.rs +++ b/crates/nu-command/src/network/http/put.rs @@ -1,7 +1,7 @@ use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response, - request_set_timeout, send_request, RequestFlags, + request_set_timeout, send_request, HttpBody, RequestFlags, }; use nu_engine::command_prelude::*; @@ -15,10 +15,10 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("http put") - .input_output_types(vec![(Type::Nothing, Type::Any)]) + .input_output_types(vec![(Type::Any, Type::Any)]) .allow_variants_without_examples(true) .required("URL", SyntaxShape::String, "The URL to post to.") - .required("data", SyntaxShape::Any, "The contents of the post body.") + .optional("data", SyntaxShape::Any, "The contents of the post body. Required unless part of a pipeline.") .named( "user", SyntaxShape::Any, @@ -122,6 +122,11 @@ impl Command for SubCommand { example: "http put --content-type application/json https://www.example.com { field: value }", result: None, }, + Example { + description: "Put JSON content from a pipeline to example.com", + example: "open foo.json | http put https://www.example.com", + result: None, + }, ] } } @@ -129,7 +134,7 @@ impl Command for SubCommand { struct Arguments { url: Value, headers: Option, - data: Value, + data: HttpBody, content_type: Option, raw: bool, insecure: bool, @@ -145,13 +150,38 @@ fn run_put( engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { + let (data, maybe_metadata) = call + .opt::(engine_state, stack, 1)? + .map(|v| (HttpBody::Value(v), None)) + .unwrap_or_else(|| match input { + PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata), + PipelineData::ByteStream(byte_stream, metadata) => { + (HttpBody::ByteStream(byte_stream), metadata) + } + _ => (HttpBody::None, None), + }); + + if let HttpBody::None = data { + return Err(ShellError::GenericError { + error: "Data must be provided either through pipeline or positional argument".into(), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + }); + } + + let content_type = call + .get_flag(engine_state, stack, "content-type")? + .or_else(|| maybe_metadata.and_then(|m| m.content_type)); + let args = Arguments { url: call.req(engine_state, stack, 0)?, headers: call.get_flag(engine_state, stack, "headers")?, - data: call.req(engine_state, stack, 1)?, - content_type: call.get_flag(engine_state, stack, "content-type")?, + data, + content_type, raw: call.has_flag(engine_state, stack, "raw")?, insecure: call.has_flag(engine_state, stack, "insecure")?, user: call.get_flag(engine_state, stack, "user")?, @@ -185,7 +215,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), Some(args.data), args.content_type, ctrl_c); + let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index 0897593109..e58e697604 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -1,7 +1,10 @@ use super::PathSubcommandArguments; use nu_engine::command_prelude::*; use nu_protocol::engine::StateWorkingSet; -use std::path::{Path, PathBuf}; +use std::{ + io, + path::{Path, PathBuf}, +}; struct Arguments { pwd: PathBuf, @@ -36,7 +39,7 @@ impl Command for SubCommand { fn extra_usage(&self) -> &str { r#"This checks the file system to confirm the path's object type. -If nothing is found, an empty string will be returned."# +If the path does not exist, null will be returned."# } fn is_const(&self) -> bool { @@ -104,9 +107,11 @@ If nothing is found, an empty string will be returned."# fn path_type(path: &Path, span: Span, args: &Arguments) -> Value { let path = nu_path::expand_path_with(path, &args.pwd, true); - let meta = path.symlink_metadata(); - let ty = meta.as_ref().map(get_file_type).unwrap_or(""); - Value::string(ty, span) + match path.symlink_metadata() { + Ok(metadata) => Value::string(get_file_type(&metadata), span), + Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span), + Err(err) => Value::error(err.into_spanned(span).into(), span), + } } fn get_file_type(md: &std::fs::Metadata) -> &str { diff --git a/crates/nu-command/src/sort_utils.rs b/crates/nu-command/src/sort_utils.rs index 50d8256353..01bb604eac 100644 --- a/crates/nu-command/src/sort_utils.rs +++ b/crates/nu-command/src/sort_utils.rs @@ -81,7 +81,7 @@ pub fn sort( if let Some(nonexistent) = nonexistent_column(&sort_columns, record.columns()) { return Err(ShellError::CantFindColumn { col_name: nonexistent, - span, + span: Some(span), src_span: val_span, }); } diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index e0c0ad4d28..4b9677941b 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -12,14 +12,17 @@ impl Command for StorInsert { fn signature(&self) -> Signature { Signature::build("stor insert") - .input_output_types(vec![(Type::Nothing, Type::table())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::record(), Type::table()), + ]) .required_named( "table-name", SyntaxShape::String, "name of the table you want to insert into", Some('t'), ) - .required_named( + .named( "data-record", SyntaxShape::Record(vec![]), "a record of column names and column values to insert into the specified table", @@ -39,10 +42,16 @@ impl Command for StorInsert { fn examples(&self) -> Vec { vec![Example { - description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs", - example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}", - result: None, - }] + description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs", + example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}", + result: None, + }, + Example { + description: "Insert data through pipeline input as a record of column-name and column-value pairs", + example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb", + result: None, + }, + ] } fn run( @@ -50,25 +59,79 @@ impl Command for StorInsert { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; - let columns: Option = call.get_flag(engine_state, stack, "data-record")?; + let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; // let config = engine_state.get_config(); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + // Check if the record is being passed as input or using the data record parameter + let columns = handle(span, data_record, input)?; + process(table_name, span, &db, columns)?; Ok(Value::custom(db, span).into_pipeline_data()) } } +fn handle( + span: Span, + data_record: Option, + input: PipelineData, +) -> Result { + match input { + PipelineData::Empty => data_record.ok_or_else(|| ShellError::MissingParameter { + param_name: "requires a record".into(), + span, + }), + PipelineData::Value(value, ..) => { + // Since input is being used, check if the data record parameter is used too + if data_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--data-record' parameter".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + match value { + Value::Record { val, .. } => Ok(val.into_owned()), + val => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "record".into(), + wrong_type: val.get_type().to_string(), + dst_span: Span::unknown(), + src_span: val.span(), + }), + } + } + _ => { + if data_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--data-record' parameter".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "record".into(), + wrong_type: "".into(), + dst_span: span, + src_span: span, + }) + } + } +} + fn process( table_name: Option, span: Span, db: &SQLiteDatabase, - columns: Option, + record: Record, ) -> Result<(), ShellError> { if table_name.is_none() { return Err(ShellError::MissingParameter { @@ -77,54 +140,45 @@ fn process( }); } let new_table_name = table_name.unwrap_or("table".into()); + if let Ok(conn) = db.open_connection() { - match columns { - Some(record) => { - let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); - let cols = record.columns(); - cols.for_each(|col| { - create_stmt.push_str(&format!("{}, ", col)); - }); - if create_stmt.ends_with(", ") { - create_stmt.pop(); - create_stmt.pop(); - } + let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); + let cols = record.columns(); + cols.for_each(|col| { + create_stmt.push_str(&format!("{}, ", col)); + }); + if create_stmt.ends_with(", ") { + create_stmt.pop(); + create_stmt.pop(); + } - // Values are set as placeholders. - create_stmt.push_str(") VALUES ( "); - for (index, _) in record.columns().enumerate() { - create_stmt.push_str(&format!("?{}, ", index + 1)); - } + // Values are set as placeholders. + create_stmt.push_str(") VALUES ( "); + for (index, _) in record.columns().enumerate() { + create_stmt.push_str(&format!("?{}, ", index + 1)); + } - if create_stmt.ends_with(", ") { - create_stmt.pop(); - create_stmt.pop(); - } + if create_stmt.ends_with(", ") { + create_stmt.pop(); + create_stmt.pop(); + } - create_stmt.push(')'); + create_stmt.push(')'); - // dbg!(&create_stmt); + // dbg!(&create_stmt); - // Get the params from the passed values - let params = values_to_sql(record.values().cloned())?; + // Get the params from the passed values + let params = values_to_sql(record.values().cloned())?; - conn.execute(&create_stmt, params_from_iter(params)) - .map_err(|err| ShellError::GenericError { - error: "Failed to open SQLite connection in memory from insert".into(), - msg: err.to_string(), - span: Some(Span::test_data()), - help: None, - inner: vec![], - })?; - } - None => { - return Err(ShellError::MissingParameter { - param_name: "requires at least one column".into(), - span, - }); - } - }; - } + conn.execute(&create_stmt, params_from_iter(params)) + .map_err(|err| ShellError::GenericError { + error: "Failed to open SQLite connection in memory from insert".into(), + msg: err.to_string(), + span: Some(Span::test_data()), + help: None, + inner: vec![], + })?; + }; // dbg!(db.clone()); Ok(()) } @@ -176,7 +230,7 @@ mod test { ), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_ok()); } @@ -201,7 +255,7 @@ mod test { Value::test_string("String With Spaces".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_ok()); } @@ -226,7 +280,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); // SQLite uses dynamic typing, making any length acceptable for a varchar column assert!(result.is_ok()); } @@ -251,7 +305,7 @@ mod test { Value::test_string("ThisIsTheWrongType".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); // SQLite uses dynamic typing, making any type acceptable for a column assert!(result.is_ok()); } @@ -276,7 +330,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_err()); } @@ -293,7 +347,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_err()); } diff --git a/crates/nu-command/src/stor/update.rs b/crates/nu-command/src/stor/update.rs index d50614d67f..d731207a3f 100644 --- a/crates/nu-command/src/stor/update.rs +++ b/crates/nu-command/src/stor/update.rs @@ -11,14 +11,17 @@ impl Command for StorUpdate { fn signature(&self) -> Signature { Signature::build("stor update") - .input_output_types(vec![(Type::Nothing, Type::table())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::record(), Type::table()), + ]) .required_named( "table-name", SyntaxShape::String, "name of the table you want to insert into", Some('t'), ) - .required_named( + .named( "update-record", SyntaxShape::Record(vec![]), "a record of column names and column values to update in the specified table", @@ -54,6 +57,11 @@ impl Command for StorUpdate { example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"", result: None, }, + Example { + description: "Update the in-memory sqlite database through pipeline input", + example: "{str1: nushell datetime1: 2020-04-17} | stor update --table-name nudb", + result: None, + }, ] } @@ -62,91 +70,147 @@ impl Command for StorUpdate { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; - let columns: Option = call.get_flag(engine_state, stack, "update-record")?; + let update_record: Option = call.get_flag(engine_state, stack, "update-record")?; let where_clause_opt: Option> = call.get_flag(engine_state, stack, "where-clause")?; // Open the in-mem database let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); - if table_name.is_none() { - return Err(ShellError::MissingParameter { - param_name: "requires at table name".into(), - span, - }); - } - let new_table_name = table_name.unwrap_or("table".into()); - if let Ok(conn) = db.open_connection() { - match columns { - Some(record) => { - let mut update_stmt = format!("UPDATE {} ", new_table_name); + // Check if the record is being passed as input or using the update record parameter + let columns = handle(span, update_record, input)?; - update_stmt.push_str("SET "); - let vals = record.iter(); - vals.for_each(|(key, val)| match val { - Value::Int { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - Value::Float { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - Value::String { val, .. } => { - update_stmt.push_str(&format!("{} = '{}', ", key, val)); - } - Value::Date { val, .. } => { - update_stmt.push_str(&format!("{} = '{}', ", key, val)); - } - Value::Bool { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - _ => { - // return Err(ShellError::UnsupportedInput { - // msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item), - // input: "value originates from here".to_string(), - // msg_span: span, - // input_span: val.span(), - // }); - } - }); - if update_stmt.ends_with(", ") { - update_stmt.pop(); - update_stmt.pop(); - } + process(table_name, span, &db, columns, where_clause_opt)?; - // Yup, this is a bit janky, but I'm not sure a better way to do this without having - // --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc. - // and other sql syntax. So, for now, just type a sql where clause as a string. - if let Some(where_clause) = where_clause_opt { - update_stmt.push_str(&format!(" WHERE {}", where_clause.item)); - } - // dbg!(&update_stmt); - - conn.execute(&update_stmt, []) - .map_err(|err| ShellError::GenericError { - error: "Failed to open SQLite connection in memory from update".into(), - msg: err.to_string(), - span: Some(Span::test_data()), - help: None, - inner: vec![], - })?; - } - None => { - return Err(ShellError::MissingParameter { - param_name: "requires at least one column".into(), - span: call.head, - }); - } - }; - } - // dbg!(db.clone()); Ok(Value::custom(db, span).into_pipeline_data()) } } +fn handle( + span: Span, + update_record: Option, + input: PipelineData, +) -> Result { + match input { + PipelineData::Empty => update_record.ok_or_else(|| ShellError::MissingParameter { + param_name: "requires a record".into(), + span, + }), + PipelineData::Value(value, ..) => { + // Since input is being used, check if the data record parameter is used too + if update_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--update-record' parameter".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + match value { + Value::Record { val, .. } => Ok(val.into_owned()), + val => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "record".into(), + wrong_type: val.get_type().to_string(), + dst_span: Span::unknown(), + src_span: val.span(), + }), + } + } + _ => { + if update_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--update-record' parameter".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "record".into(), + wrong_type: "".into(), + dst_span: span, + src_span: span, + }) + } + } +} + +fn process( + table_name: Option, + span: Span, + db: &SQLiteDatabase, + record: Record, + where_clause_opt: Option>, +) -> Result<(), ShellError> { + if table_name.is_none() { + return Err(ShellError::MissingParameter { + param_name: "requires at table name".into(), + span, + }); + } + let new_table_name = table_name.unwrap_or("table".into()); + if let Ok(conn) = db.open_connection() { + let mut update_stmt = format!("UPDATE {} ", new_table_name); + + update_stmt.push_str("SET "); + let vals = record.iter(); + vals.for_each(|(key, val)| match val { + Value::Int { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + Value::Float { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + Value::String { val, .. } => { + update_stmt.push_str(&format!("{} = '{}', ", key, val)); + } + Value::Date { val, .. } => { + update_stmt.push_str(&format!("{} = '{}', ", key, val)); + } + Value::Bool { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + _ => { + // return Err(ShellError::UnsupportedInput { + // msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item), + // input: "value originates from here".to_string(), + // msg_span: span, + // input_span: val.span(), + // }); + } + }); + if update_stmt.ends_with(", ") { + update_stmt.pop(); + update_stmt.pop(); + } + + // Yup, this is a bit janky, but I'm not sure a better way to do this without having + // --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc. + // and other sql syntax. So, for now, just type a sql where clause as a string. + if let Some(where_clause) = where_clause_opt { + update_stmt.push_str(&format!(" WHERE {}", where_clause.item)); + } + // dbg!(&update_stmt); + + conn.execute(&update_stmt, []) + .map_err(|err| ShellError::GenericError { + error: "Failed to open SQLite connection in memory from update".into(), + msg: err.to_string(), + span: Some(Span::test_data()), + help: None, + inner: vec![], + })?; + } + // dbg!(db.clone()); + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 7d8d152b56..6834134b26 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -1,6 +1,6 @@ use indexmap::{indexmap, IndexMap}; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; + use once_cell::sync::Lazy; use std::sync::{atomic::AtomicBool, Arc}; @@ -19,6 +19,9 @@ static CHAR_MAP: Lazy> = 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(), diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 74bcf07820..62a2d8bbcc 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -45,20 +45,6 @@ impl Command for DetectColumns { vec!["split", "tabular"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - if call.has_flag(engine_state, stack, "guess")? { - guess_width(engine_state, stack, call, input) - } else { - detect_columns(engine_state, stack, call, input) - } - } - fn examples(&self) -> Vec { vec![ Example { @@ -109,33 +95,87 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; + let noheader = call.has_flag(engine_state, stack, "no-headers")?; + let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; + + let args = Arguments { + noheader, + num_rows_to_skip, + range, + }; + + if call.has_flag(engine_state, stack, "guess")? { + guess_width(engine_state, call, input, args) + } else { + detect_columns(engine_state, call, input, args) + } + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let num_rows_to_skip: Option = call.get_flag_const(working_set, "skip")?; + let noheader = call.has_flag_const(working_set, "no-headers")?; + let range: Option = call.get_flag_const(working_set, "combine-columns")?; + + let args = Arguments { + noheader, + num_rows_to_skip, + range, + }; + + if call.has_flag_const(working_set, "guess")? { + guess_width(working_set.permanent(), call, input, args) + } else { + detect_columns(working_set.permanent(), call, input, args) + } + } +} + +struct Arguments { + num_rows_to_skip: Option, + noheader: bool, + range: Option, } fn guess_width( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { use super::guess_width::GuessWidth; let input_span = input.span().unwrap_or(call.head); let mut input = input.collect_string("", engine_state.get_config())?; - let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; - if let Some(rows) = num_rows_to_skip { + if let Some(rows) = args.num_rows_to_skip { input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); } let mut guess_width = GuessWidth::new_reader(Box::new(Cursor::new(input))); - let noheader = call.has_flag(engine_state, stack, "no-headers")?; let result = guess_width.read_all(); if result.is_empty() { return Ok(Value::nothing(input_span).into_pipeline_data()); } - let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; - if !noheader { + if !args.noheader { let columns = result[0].clone(); Ok(result .into_iter() @@ -152,7 +192,7 @@ fn guess_width( let record = Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); match record { - Ok(r) => match &range { + Ok(r) => match &args.range { Some(range) => merge_record(r, range, input_span), None => Value::record(r, input_span), }, @@ -177,7 +217,7 @@ fn guess_width( let record = Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); match record { - Ok(r) => match &range { + Ok(r) => match &args.range { Some(range) => merge_record(r, range, input_span), None => Value::record(r, input_span), }, @@ -190,21 +230,18 @@ fn guess_width( fn detect_columns( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; - let noheader = call.has_flag(engine_state, stack, "no-headers")?; - let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let input = input.collect_string("", config)?; let input: Vec<_> = input .lines() - .skip(num_rows_to_skip.unwrap_or_default()) + .skip(args.num_rows_to_skip.unwrap_or_default()) .map(|x| x.to_string()) .collect(); @@ -214,13 +251,14 @@ fn detect_columns( if let Some(orig_headers) = headers { let mut headers = find_columns(&orig_headers); - if noheader { + if args.noheader { for header in headers.iter_mut().enumerate() { header.1.item = format!("column{}", header.0); } } - Ok(noheader + Ok(args + .noheader .then_some(orig_headers) .into_iter() .chain(input) @@ -273,7 +311,7 @@ fn detect_columns( } } - match &range { + match &args.range { Some(range) => merge_record(record, range, name_span), None => Value::record(record, name_span), } diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index 1c257e1200..8050193212 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -7,10 +7,9 @@ use base64::{ Engine, }; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; -use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, - engine::{EngineState, Stack}, + engine::EngineState, PipelineData, ShellError, Span, Spanned, Value, }; @@ -42,22 +41,24 @@ impl CmdArgument for Arguments { } } +pub(super) struct Base64CommandArguments { + pub(super) character_set: Option>, + pub(super) action_type: ActionType, + pub(super) binary: bool, +} + pub fn operate( - action_type: ActionType, engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + cell_paths: Vec, + args: Base64CommandArguments, ) -> Result { let head = call.head; - let character_set: Option> = - call.get_flag(engine_state, stack, "character-set")?; - let binary = call.has_flag(engine_state, stack, "binary")?; - let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); // Default the character set to standard if the argument is not specified. - let character_set = match character_set { + let character_set = match args.character_set { Some(inner_tag) => inner_tag, None => Spanned { item: "standard".to_string(), @@ -68,9 +69,9 @@ pub fn operate( let args = Arguments { encoding_config: Base64Config { character_set, - action_type, + action_type: args.action_type, }, - binary, + binary: args.binary, cell_paths, }; diff --git a/crates/nu-command/src/strings/encode_decode/decode.rs b/crates/nu-command/src/strings/encode_decode/decode.rs index 9b13fad202..20385612f3 100644 --- a/crates/nu-command/src/strings/encode_decode/decode.rs +++ b/crates/nu-command/src/strings/encode_decode/decode.rs @@ -46,6 +46,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -53,49 +57,67 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# call: &Call, input: PipelineData, ) -> Result { - let head = call.head; let encoding: Option> = call.opt(engine_state, stack, 0)?; + run(call, input, encoding) + } - match input { - PipelineData::ByteStream(stream, ..) => { - let span = stream.span(); - let bytes = stream.into_bytes()?; - match encoding { + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let encoding: Option> = call.opt_const(working_set, 0)?; + run(call, input, encoding) + } +} + +fn run( + call: &Call, + input: PipelineData, + encoding: Option>, +) -> Result { + let head = call.head; + + match input { + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + let bytes = stream.into_bytes()?; + match encoding { + Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), + None => super::encoding::detect_encoding_name(head, span, &bytes) + .map(|encoding| encoding.decode(&bytes).0.into_owned()) + .map(|s| Value::string(s, head)), + } + .map(|val| val.into_pipeline_data()) + } + PipelineData::Value(v, ..) => { + let input_span = v.span(); + match v { + Value::Binary { val: bytes, .. } => match encoding { Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), - None => super::encoding::detect_encoding_name(head, span, &bytes) + None => super::encoding::detect_encoding_name(head, input_span, &bytes) .map(|encoding| encoding.decode(&bytes).0.into_owned()) .map(|s| Value::string(s, head)), } - .map(|val| val.into_pipeline_data()) + .map(|val| val.into_pipeline_data()), + Value::Error { error, .. } => Err(*error), + _ => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "binary".into(), + wrong_type: v.get_type().to_string(), + dst_span: head, + src_span: v.span(), + }), } - PipelineData::Value(v, ..) => { - let input_span = v.span(); - match v { - Value::Binary { val: bytes, .. } => match encoding { - Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), - None => super::encoding::detect_encoding_name(head, input_span, &bytes) - .map(|encoding| encoding.decode(&bytes).0.into_owned()) - .map(|s| Value::string(s, head)), - } - .map(|val| val.into_pipeline_data()), - Value::Error { error, .. } => Err(*error), - _ => Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "binary".into(), - wrong_type: v.get_type().to_string(), - dst_span: head, - src_span: v.span(), - }), - } - } - // This should be more precise, but due to difficulties in getting spans - // from PipelineData::ListData, this is as it is. - _ => Err(ShellError::UnsupportedInput { - msg: "non-binary input".into(), - input: "value originates from here".into(), - msg_span: head, - input_span: input.span().unwrap_or(head), - }), } + // This should be more precise, but due to difficulties in getting spans + // from PipelineData::ListData, this is as it is. + _ => Err(ShellError::UnsupportedInput { + msg: "non-binary input".into(), + input: "value originates from here".into(), + msg_span: head, + input_span: input.span().unwrap_or(head), + }), } } diff --git a/crates/nu-command/src/strings/encode_decode/decode_base64.rs b/crates/nu-command/src/strings/encode_decode/decode_base64.rs index 242a99bb88..cda9de4be8 100644 --- a/crates/nu-command/src/strings/encode_decode/decode_base64.rs +++ b/crates/nu-command/src/strings/encode_decode/decode_base64.rs @@ -1,4 +1,4 @@ -use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; +use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC}; use nu_engine::command_prelude::*; #[derive(Clone)] @@ -66,6 +66,10 @@ impl Command for DecodeBase64 { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -73,7 +77,34 @@ impl Command for DecodeBase64 { call: &Call, input: PipelineData, ) -> Result { - operate(ActionType::Decode, engine_state, stack, call, input) + let character_set: Option> = + call.get_flag(engine_state, stack, "character-set")?; + let binary = call.has_flag(engine_state, stack, "binary")?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Decode, + binary, + character_set, + }; + operate(engine_state, call, input, cell_paths, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character_set: Option> = + call.get_flag_const(working_set, "character-set")?; + let binary = call.has_flag_const(working_set, "binary")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Decode, + binary, + character_set, + }; + operate(working_set.permanent(), call, input, cell_paths, args) } } diff --git a/crates/nu-command/src/strings/encode_decode/encode.rs b/crates/nu-command/src/strings/encode_decode/encode.rs index 113c0fe548..b7bebdba80 100644 --- a/crates/nu-command/src/strings/encode_decode/encode.rs +++ b/crates/nu-command/src/strings/encode_decode/encode.rs @@ -69,6 +69,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -76,42 +80,62 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# call: &Call, input: PipelineData, ) -> Result { - let head = call.head; let encoding: Spanned = call.req(engine_state, stack, 0)?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; + run(call, input, encoding, ignore_errors) + } - match input { - PipelineData::ByteStream(stream, ..) => { - let span = stream.span(); - let s = stream.into_string()?; - super::encoding::encode(head, encoding, &s, span, ignore_errors) - .map(|val| val.into_pipeline_data()) - } - PipelineData::Value(v, ..) => { - let span = v.span(); - match v { - Value::String { val: s, .. } => { - super::encoding::encode(head, encoding, &s, span, ignore_errors) - .map(|val| val.into_pipeline_data()) - } - Value::Error { error, .. } => Err(*error), - _ => Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "string".into(), - wrong_type: v.get_type().to_string(), - dst_span: head, - src_span: v.span(), - }), - } - } - // This should be more precise, but due to difficulties in getting spans - // from PipelineData::ListStream, this is as it is. - _ => Err(ShellError::UnsupportedInput { - msg: "non-string input".into(), - input: "value originates from here".into(), - msg_span: head, - input_span: input.span().unwrap_or(head), - }), + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let encoding: Spanned = call.req_const(working_set, 0)?; + let ignore_errors = call.has_flag_const(working_set, "ignore-errors")?; + run(call, input, encoding, ignore_errors) + } +} + +fn run( + call: &Call, + input: PipelineData, + encoding: Spanned, + ignore_errors: bool, +) -> Result { + let head = call.head; + + match input { + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + let s = stream.into_string()?; + super::encoding::encode(head, encoding, &s, span, ignore_errors) + .map(|val| val.into_pipeline_data()) } + PipelineData::Value(v, ..) => { + let span = v.span(); + match v { + Value::String { val: s, .. } => { + super::encoding::encode(head, encoding, &s, span, ignore_errors) + .map(|val| val.into_pipeline_data()) + } + Value::Error { error, .. } => Err(*error), + _ => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "string".into(), + wrong_type: v.get_type().to_string(), + dst_span: head, + src_span: v.span(), + }), + } + } + // This should be more precise, but due to difficulties in getting spans + // from PipelineData::ListStream, this is as it is. + _ => Err(ShellError::UnsupportedInput { + msg: "non-string input".into(), + input: "value originates from here".into(), + msg_span: head, + input_span: input.span().unwrap_or(head), + }), } } diff --git a/crates/nu-command/src/strings/encode_decode/encode_base64.rs b/crates/nu-command/src/strings/encode_decode/encode_base64.rs index 090941b76b..b01530b107 100644 --- a/crates/nu-command/src/strings/encode_decode/encode_base64.rs +++ b/crates/nu-command/src/strings/encode_decode/encode_base64.rs @@ -1,4 +1,4 @@ -use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; +use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC}; use nu_engine::command_prelude::*; #[derive(Clone)] @@ -70,6 +70,10 @@ impl Command for EncodeBase64 { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -77,7 +81,34 @@ impl Command for EncodeBase64 { call: &Call, input: PipelineData, ) -> Result { - operate(ActionType::Encode, engine_state, stack, call, input) + let character_set: Option> = + call.get_flag(engine_state, stack, "character-set")?; + let binary = call.has_flag(engine_state, stack, "binary")?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Encode, + binary, + character_set, + }; + operate(engine_state, call, input, cell_paths, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character_set: Option> = + call.get_flag_const(working_set, "character-set")?; + let binary = call.has_flag_const(working_set, "binary")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Encode, + binary, + character_set, + }; + operate(working_set.permanent(), call, input, cell_paths, args) } } diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index 2c82eb7541..1aaf1fb851 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -27,7 +27,7 @@ impl Command for FormatDate { SyntaxShape::String, "The desired format date.", ) - .category(Category::Date) + .category(Category::Strings) } fn usage(&self) -> &str { @@ -38,36 +38,6 @@ impl Command for FormatDate { vec!["fmt", "strftime"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let head = call.head; - if call.has_flag(engine_state, stack, "list")? { - return Ok(PipelineData::Value( - generate_strftime_list(head, false), - None, - )); - } - - let format = call.opt::>(engine_state, stack, 0)?; - - // This doesn't match explicit nulls - if matches!(input, PipelineData::Empty) { - return Err(ShellError::PipelineEmpty { dst_span: head }); - } - input.map( - move |value| match &format { - Some(format) => format_helper(value, format.item.as_str(), format.span, head), - None => format_helper_rfc2822(value, head), - }, - engine_state.ctrlc.clone(), - ) - } - fn examples(&self) -> Vec { vec![ Example { @@ -104,6 +74,61 @@ impl Command for FormatDate { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let list = call.has_flag(engine_state, stack, "list")?; + let format = call.opt::>(engine_state, stack, 0)?; + run(engine_state, call, input, list, format) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let list = call.has_flag_const(working_set, "list")?; + let format = call.opt_const::>(working_set, 0)?; + run(working_set.permanent(), call, input, list, format) + } +} + +fn run( + engine_state: &EngineState, + call: &Call, + input: PipelineData, + list: bool, + format: Option>, +) -> Result { + let head = call.head; + if list { + return Ok(PipelineData::Value( + generate_strftime_list(head, false), + None, + )); + } + + // This doesn't match explicit nulls + if matches!(input, PipelineData::Empty) { + return Err(ShellError::PipelineEmpty { dst_span: head }); + } + input.map( + move |value| match &format { + Some(format) => format_helper(value, format.item.as_str(), format.span, head), + None => format_helper_rfc2822(value, head), + }, + engine_state.ctrlc.clone(), + ) } fn format_from(date_time: DateTime, formatter: &str, span: Span) -> Value diff --git a/crates/nu-command/src/strings/format/duration.rs b/crates/nu-command/src/strings/format/duration.rs index ad6583cec0..281542f49a 100644 --- a/crates/nu-command/src/strings/format/duration.rs +++ b/crates/nu-command/src/strings/format/duration.rs @@ -53,6 +53,10 @@ impl Command for FormatDuration { vec!["convert", "display", "pattern", "human readable"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -81,6 +85,33 @@ impl Command for FormatDuration { ) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let format_value = call + .req_const::(working_set, 0)? + .coerce_into_string()? + .to_ascii_lowercase(); + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let float_precision = working_set.permanent().config.float_precision as usize; + let arg = Arguments { + format_value, + float_precision, + cell_paths, + }; + operate( + format_value_impl, + arg, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/format/filesize.rs b/crates/nu-command/src/strings/format/filesize.rs index b54dc92f6d..ebd43d90b1 100644 --- a/crates/nu-command/src/strings/format/filesize.rs +++ b/crates/nu-command/src/strings/format/filesize.rs @@ -1,6 +1,6 @@ use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::format_filesize; +use nu_protocol::{engine::StateWorkingSet, format_filesize}; struct Arguments { format_value: String, @@ -50,6 +50,10 @@ impl Command for FormatFilesize { vec!["convert", "display", "pattern", "human readable"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -76,6 +80,31 @@ impl Command for FormatFilesize { ) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let format_value = call + .req_const::(working_set, 0)? + .coerce_into_string()? + .to_ascii_lowercase(); + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let arg = Arguments { + format_value, + cell_paths, + }; + operate( + format_value_impl, + arg, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/guess_width.rs b/crates/nu-command/src/strings/guess_width.rs index 59cfbcb2cf..8c758516bd 100644 --- a/crates/nu-command/src/strings/guess_width.rs +++ b/crates/nu-command/src/strings/guess_width.rs @@ -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 { 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 = line.chars().collect(); + let (line_char_boundaries, line_chars): (Vec, Vec) = 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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![ diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index bc70d4679c..4318b3da8b 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -1,6 +1,6 @@ use fancy_regex::{Captures, Regex}; use nu_engine::command_prelude::*; -use nu_protocol::ListStream; +use nu_protocol::{engine::StateWorkingSet, ListStream}; use std::{ collections::VecDeque, sync::{atomic::AtomicBool, Arc}, @@ -99,6 +99,10 @@ impl Command for Parse { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -106,19 +110,31 @@ impl Command for Parse { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let pattern: Spanned = call.req(engine_state, stack, 0)?; + let regex: bool = call.has_flag(engine_state, stack, "regex")?; + operate(engine_state, pattern, regex, call, input) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let pattern: Spanned = call.req_const(working_set, 0)?; + let regex: bool = call.has_flag_const(working_set, "regex")?; + operate(working_set.permanent(), pattern, regex, call, input) } } fn operate( engine_state: &EngineState, - stack: &mut Stack, + pattern: Spanned, + regex: bool, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - let pattern: Spanned = call.req(engine_state, stack, 0)?; - let regex: bool = call.has_flag(engine_state, stack, "regex")?; let pattern_item = pattern.item; let pattern_span = pattern.span; diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index 625915e76d..08e73b9830 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -1,5 +1,6 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_engine::command_prelude::*; + use unicode_segmentation::UnicodeSegmentation; #[derive(Clone)] @@ -88,6 +89,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -95,19 +100,28 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - split_chars(engine_state, stack, call, input) + let graphemes = grapheme_flags(engine_state, stack, call)?; + split_chars(engine_state, call, input, graphemes) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let graphemes = grapheme_flags_const(working_set, call)?; + split_chars(working_set.permanent(), call, input, graphemes) } } fn split_chars( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + graphemes: bool, ) -> Result { let span = call.head; - - let graphemes = grapheme_flags(engine_state, stack, call)?; input.map( move |x| split_chars_helper(&x, span, graphemes), engine_state.ctrlc.clone(), diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index d73243322d..02eff7845f 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -43,16 +43,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_column(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -103,35 +93,83 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req(engine_state, stack, 0)?; + let rest: Vec> = call.rest(engine_state, stack, 1)?; + let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; + let has_regex = call.has_flag(engine_state, stack, "regex")?; + + let args = Arguments { + separator, + rest, + collapse_empty, + has_regex, + }; + split_column(engine_state, call, input, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req_const(working_set, 0)?; + let rest: Vec> = call.rest_const(working_set, 1)?; + let collapse_empty = call.has_flag_const(working_set, "collapse-empty")?; + let has_regex = call.has_flag_const(working_set, "regex")?; + + let args = Arguments { + separator, + rest, + collapse_empty, + has_regex, + }; + split_column(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + separator: Spanned, + rest: Vec>, + collapse_empty: bool, + has_regex: bool, } fn split_column( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let separator: Spanned = call.req(engine_state, stack, 0)?; - let rest: Vec> = call.rest(engine_state, stack, 1)?; - let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; - - let regex = if call.has_flag(engine_state, stack, "regex")? { - Regex::new(&separator.item) + let regex = if args.has_regex { + Regex::new(&args.separator.item) } else { - let escaped = regex::escape(&separator.item); + let escaped = regex::escape(&args.separator.item); Regex::new(&escaped) } .map_err(|e| ShellError::GenericError { error: "Error with regular expression".into(), msg: e.to_string(), - span: Some(separator.span), + span: Some(args.separator.span), help: None, inner: vec![], })?; input.flat_map( - move |x| split_column_helper(&x, ®ex, &rest, collapse_empty, name_span), + move |x| split_column_helper(&x, ®ex, &args.rest, args.collapse_empty, name_span), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/split/list.rs b/crates/nu-command/src/strings/split/list.rs index 470ba12ec4..d52bb5401a 100644 --- a/crates/nu-command/src/strings/split/list.rs +++ b/crates/nu-command/src/strings/split/list.rs @@ -36,16 +36,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_list(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -145,6 +135,33 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let has_regex = call.has_flag(engine_state, stack, "regex")?; + let separator: Value = call.req(engine_state, stack, 0)?; + split_list(engine_state, call, input, has_regex, separator) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let has_regex = call.has_flag_const(working_set, "regex")?; + let separator: Value = call.req_const(working_set, 0)?; + split_list(working_set.permanent(), call, input, has_regex, separator) + } } enum Matcher { @@ -188,15 +205,15 @@ impl Matcher { fn split_list( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + has_regex: bool, + separator: Value, ) -> Result { - let separator: Value = call.req(engine_state, stack, 0)?; let mut temp_list = Vec::new(); let mut returned_list = Vec::new(); - let matcher = Matcher::new(call.has_flag(engine_state, stack, "regex")?, separator)?; + let matcher = Matcher::new(has_regex, separator)?; for val in input { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { break; diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs index 8ee22b213c..8bc0003cb6 100644 --- a/crates/nu-command/src/strings/split/row.rs +++ b/crates/nu-command/src/strings/split/row.rs @@ -43,16 +43,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_row(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -109,32 +99,77 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req(engine_state, stack, 0)?; + let max_split: Option = call.get_flag(engine_state, stack, "number")?; + let has_regex = call.has_flag(engine_state, stack, "regex")?; + + let args = Arguments { + separator, + max_split, + has_regex, + }; + split_row(engine_state, call, input, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req_const(working_set, 0)?; + let max_split: Option = call.get_flag_const(working_set, "number")?; + let has_regex = call.has_flag_const(working_set, "regex")?; + + let args = Arguments { + separator, + max_split, + has_regex, + }; + split_row(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + has_regex: bool, + separator: Spanned, + max_split: Option, } fn split_row( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let separator: Spanned = call.req(engine_state, stack, 0)?; - let regex = if call.has_flag(engine_state, stack, "regex")? { - Regex::new(&separator.item) + let regex = if args.has_regex { + Regex::new(&args.separator.item) } else { - let escaped = regex::escape(&separator.item); + let escaped = regex::escape(&args.separator.item); Regex::new(&escaped) } .map_err(|e| ShellError::GenericError { error: "Error with regular expression".into(), msg: e.to_string(), - span: Some(separator.span), + span: Some(args.separator.span), help: None, inner: vec![], })?; - let max_split: Option = call.get_flag(engine_state, stack, "number")?; input.flat_map( - move |x| split_row_helper(&x, ®ex, max_split, name_span), + move |x| split_row_helper(&x, ®ex, args.max_split, name_span), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/split/words.rs b/crates/nu-command/src/strings/split/words.rs index 17b68bd44f..0dd5dc9383 100644 --- a/crates/nu-command/src/strings/split/words.rs +++ b/crates/nu-command/src/strings/split/words.rs @@ -1,4 +1,4 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use fancy_regex::Regex; use nu_engine::command_prelude::*; @@ -96,6 +96,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -103,40 +107,76 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - split_words(engine_state, stack, call, input) + let word_length: Option = call.get_flag(engine_state, stack, "min-word-length")?; + let has_grapheme = call.has_flag(engine_state, stack, "grapheme-clusters")?; + let has_utf8 = call.has_flag(engine_state, stack, "utf-8-bytes")?; + let graphemes = grapheme_flags(engine_state, stack, call)?; + + let args = Arguments { + word_length, + has_grapheme, + has_utf8, + graphemes, + }; + split_words(engine_state, call, input, args) } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let word_length: Option = call.get_flag_const(working_set, "min-word-length")?; + let has_grapheme = call.has_flag_const(working_set, "grapheme-clusters")?; + let has_utf8 = call.has_flag_const(working_set, "utf-8-bytes")?; + let graphemes = grapheme_flags_const(working_set, call)?; + + let args = Arguments { + word_length, + has_grapheme, + has_utf8, + graphemes, + }; + split_words(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + word_length: Option, + has_grapheme: bool, + has_utf8: bool, + graphemes: bool, } fn split_words( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let span = call.head; // let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?; // let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?; // let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?; - let word_length: Option = call.get_flag(engine_state, stack, "min-word-length")?; - if word_length.is_none() { - if call.has_flag(engine_state, stack, "grapheme-clusters")? { + if args.word_length.is_none() { + if args.has_grapheme { return Err(ShellError::IncompatibleParametersSingle { msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(), span, }); } - if call.has_flag(engine_state, stack, "utf-8-bytes")? { + if args.has_utf8 { return Err(ShellError::IncompatibleParametersSingle { msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(), span, }); } } - let graphemes = grapheme_flags(engine_state, stack, call)?; input.map( - move |x| split_words_helper(&x, word_length, span, graphemes), + move |x| split_words_helper(&x, args.word_length, span, args.graphemes), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/str_/case/capitalize.rs b/crates/nu-command/src/strings/str_/case/capitalize.rs index 82f0d102e6..20f334976c 100644 --- a/crates/nu-command/src/strings/str_/case/capitalize.rs +++ b/crates/nu-command/src/strings/str_/case/capitalize.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["convert", "style", "caps", "upper"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -72,12 +87,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/case/downcase.rs b/crates/nu-command/src/strings/str_/case/downcase.rs index 7fa4785499..316050d501 100644 --- a/crates/nu-command/src/strings/str_/case/downcase.rs +++ b/crates/nu-command/src/strings/str_/case/downcase.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["lower case", "lowercase"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -80,12 +95,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/case/upcase.rs b/crates/nu-command/src/strings/str_/case/upcase.rs index 222c9eeab4..9e55f25f64 100644 --- a/crates/nu-command/src/strings/str_/case/upcase.rs +++ b/crates/nu-command/src/strings/str_/case/upcase.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["uppercase", "upper case"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -57,12 +72,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index bc1d5bcf11..ff9cb85fcb 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -10,7 +10,6 @@ struct Arguments { substring: String, cell_paths: Option>, case_insensitive: bool, - not_contain: bool, } impl CmdArgument for Arguments { @@ -40,7 +39,6 @@ impl Command for SubCommand { "For a data structure input, check strings at the given cell paths, and replace with result.", ) .switch("ignore-case", "search is case insensitive", Some('i')) - .switch("not", "DEPRECATED OPTION: does not contain", Some('n')) .category(Category::Strings) } @@ -52,6 +50,10 @@ impl Command for SubCommand { vec!["substring", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -59,9 +61,25 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - if call.has_flag(engine_state, stack, "not")? { + let cell_paths: Vec = call.rest(engine_state, stack, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: call.req::(engine_state, stack, 0)?, + cell_paths, + case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, + }; + operate(action, args, input, call.head, engine_state.ctrlc.clone()) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + if call.has_flag_const(working_set, "not")? { nu_protocol::report_error_new( - engine_state, + working_set.permanent(), &ShellError::GenericError { error: "Deprecated option".into(), msg: "`str contains --not {string}` is deprecated and will be removed in 0.95." @@ -73,15 +91,20 @@ impl Command for SubCommand { ); } - let cell_paths: Vec = call.rest(engine_state, stack, 1)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let args = Arguments { - substring: call.req::(engine_state, stack, 0)?, + substring: call.req_const::(working_set, 0)?, cell_paths, - case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, - not_contain: call.has_flag(engine_state, stack, "not")?, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) } fn examples(&self) -> Vec { @@ -142,7 +165,6 @@ fn action( input: &Value, Arguments { case_insensitive, - not_contain, substring, .. }: &Arguments, @@ -150,23 +172,11 @@ fn action( ) -> Value { match input { Value::String { val, .. } => Value::bool( - match case_insensitive { - true => { - if *not_contain { - !val.to_folded_case() - .contains(substring.to_folded_case().as_str()) - } else { - val.to_folded_case() - .contains(substring.to_folded_case().as_str()) - } - } - false => { - if *not_contain { - !val.contains(substring) - } else { - val.contains(substring) - } - } + if *case_insensitive { + val.to_folded_case() + .contains(substring.to_folded_case().as_str()) + } else { + val.contains(substring) }, head, ), diff --git a/crates/nu-command/src/strings/str_/deunicode.rs b/crates/nu-command/src/strings/str_/deunicode.rs new file mode 100644 index 0000000000..0b70cf2003 --- /dev/null +++ b/crates/nu-command/src/strings/str_/deunicode.rs @@ -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 { + let cell_paths: Vec = 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 { + let cell_paths: Vec = 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 { + 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 {}) + } +} diff --git a/crates/nu-command/src/strings/str_/distance.rs b/crates/nu-command/src/strings/str_/distance.rs index aa45ec5c25..bd666e53f5 100644 --- a/crates/nu-command/src/strings/str_/distance.rs +++ b/crates/nu-command/src/strings/str_/distance.rs @@ -1,6 +1,6 @@ use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::levenshtein_distance; +use nu_protocol::{engine::StateWorkingSet, levenshtein_distance}; #[derive(Clone)] pub struct SubCommand; @@ -49,6 +49,10 @@ impl Command for SubCommand { vec!["edit", "levenshtein"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -66,6 +70,28 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let compare_string: String = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + compare_string, + cell_paths, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![Example { description: "get the edit distance between two strings", diff --git a/crates/nu-command/src/strings/str_/ends_with.rs b/crates/nu-command/src/strings/str_/ends_with.rs index 1b06acd880..40f643ea8e 100644 --- a/crates/nu-command/src/strings/str_/ends_with.rs +++ b/crates/nu-command/src/strings/str_/ends_with.rs @@ -50,6 +50,10 @@ impl Command for SubCommand { vec!["suffix", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -67,6 +71,28 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: call.req_const::(working_set, 0)?, + cell_paths, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/expand.rs b/crates/nu-command/src/strings/str_/expand.rs index 2eca970403..70fb51ec4c 100644 --- a/crates/nu-command/src/strings/str_/expand.rs +++ b/crates/nu-command/src/strings/str_/expand.rs @@ -179,6 +179,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -186,32 +190,51 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - let span = call.head; - if matches!(input, PipelineData::Empty) { - return Err(ShellError::PipelineEmpty { dst_span: span }); - } let is_path = call.has_flag(engine_state, stack, "path")?; - input.map( - move |v| { - let value_span = v.span(); - match v.coerce_into_string() { - Ok(s) => { - let contents = if is_path { s.replace('\\', "\\\\") } else { s }; - str_expand(&contents, span, value_span) - } - Err(_) => Value::error( - ShellError::PipelineMismatch { - exp_input_type: "string".into(), - dst_span: span, - src_span: value_span, - }, - span, - ), - } - }, - engine_state.ctrlc.clone(), - ) + run(call, input, is_path, engine_state) } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let is_path = call.has_flag_const(working_set, "path")?; + run(call, input, is_path, working_set.permanent()) + } +} + +fn run( + call: &Call, + input: PipelineData, + is_path: bool, + engine_state: &EngineState, +) -> Result { + let span = call.head; + if matches!(input, PipelineData::Empty) { + return Err(ShellError::PipelineEmpty { dst_span: span }); + } + input.map( + move |v| { + let value_span = v.span(); + match v.coerce_into_string() { + Ok(s) => { + let contents = if is_path { s.replace('\\', "\\\\") } else { s }; + str_expand(&contents, span, value_span) + } + Err(_) => Value::error( + ShellError::PipelineMismatch { + exp_input_type: "string".into(), + dst_span: span, + src_span: value_span, + }, + span, + ), + } + }, + engine_state.ctrlc.clone(), + ) } fn str_expand(contents: &str, span: Span, value_span: Span) -> Value { diff --git a/crates/nu-command/src/strings/str_/index_of.rs b/crates/nu-command/src/strings/str_/index_of.rs index df26df7493..5b40f80d3d 100644 --- a/crates/nu-command/src/strings/str_/index_of.rs +++ b/crates/nu-command/src/strings/str_/index_of.rs @@ -1,10 +1,10 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::{ input_handler::{operate, CmdArgument}, util, }; use nu_engine::command_prelude::*; -use nu_protocol::Range; +use nu_protocol::{engine::StateWorkingSet, Range}; use unicode_segmentation::UnicodeSegmentation; struct Arguments { @@ -72,6 +72,10 @@ impl Command for SubCommand { vec!["match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -92,6 +96,31 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let substring: Spanned = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: substring.item, + range: call.get_flag_const(working_set, "range")?, + end: call.has_flag_const(working_set, "end")?, + cell_paths, + graphemes: grapheme_flags_const(working_set, call)?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/join.rs b/crates/nu-command/src/strings/str_/join.rs index dd3a87dd61..1d36f216d0 100644 --- a/crates/nu-command/src/strings/str_/join.rs +++ b/crates/nu-command/src/strings/str_/join.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; + use std::io::Write; #[derive(Clone)] @@ -32,6 +33,10 @@ impl Command for StrJoin { vec!["collect", "concatenate"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -40,41 +45,17 @@ impl Command for StrJoin { input: PipelineData, ) -> Result { let separator: Option = call.opt(engine_state, stack, 0)?; + run(engine_state, call, input, separator) + } - let config = engine_state.config.clone(); - - let span = call.head; - - let metadata = input.metadata(); - let mut iter = input.into_iter(); - let mut first = true; - - let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Write each input to the buffer - if let Some(value) = iter.next() { - // Write the separator if this is not the first - if first { - first = false; - } else if let Some(separator) = &separator { - write!(buffer, "{}", separator)?; - } - - match value { - Value::Error { error, .. } => { - return Err(*error); - } - // Hmm, not sure what we actually want. - // `to_expanded_string` formats dates as human readable which feels funny. - Value::Date { val, .. } => write!(buffer, "{val:?}")?, - value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, - } - Ok(true) - } else { - Ok(false) - } - }); - - Ok(PipelineData::ByteStream(output, metadata)) + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Option = call.opt_const(working_set, 0)?; + run(working_set.permanent(), call, input, separator) } fn examples(&self) -> Vec { @@ -93,6 +74,48 @@ impl Command for StrJoin { } } +fn run( + engine_state: &EngineState, + call: &Call, + input: PipelineData, + separator: Option, +) -> Result { + let config = engine_state.config.clone(); + + let span = call.head; + + let metadata = input.metadata(); + let mut iter = input.into_iter(); + let mut first = true; + + let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { + // Write each input to the buffer + if let Some(value) = iter.next() { + // Write the separator if this is not the first + if first { + first = false; + } else if let Some(separator) = &separator { + write!(buffer, "{}", separator)?; + } + + match value { + Value::Error { error, .. } => { + return Err(*error); + } + // Hmm, not sure what we actually want. + // `to_expanded_string` formats dates as human readable which feels funny. + Value::Date { val, .. } => write!(buffer, "{val:?}")?, + value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + } + Ok(true) + } else { + Ok(false) + } + }); + + Ok(PipelineData::ByteStream(output, metadata)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index 6e2ae4182b..f456fe9467 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -1,7 +1,7 @@ use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; + use unicode_segmentation::UnicodeSegmentation; struct Arguments { diff --git a/crates/nu-command/src/strings/str_/mod.rs b/crates/nu-command/src/strings/str_/mod.rs index fb34ace833..b9290ada2c 100644 --- a/crates/nu-command/src/strings/str_/mod.rs +++ b/crates/nu-command/src/strings/str_/mod.rs @@ -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; diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index 5d5863e70a..58e2574681 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -73,6 +73,10 @@ impl Command for SubCommand { vec!["search", "shift", "switch", "regex"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -101,6 +105,39 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let find: Spanned = call.req_const(working_set, 0)?; + let replace: Spanned = call.req_const(working_set, 1)?; + let cell_paths: Vec = call.rest_const(working_set, 2)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let literal_replace = call.has_flag_const(working_set, "no-expand")?; + let no_regex = !call.has_flag_const(working_set, "regex")? + && !call.has_flag_const(working_set, "multiline")?; + let multiline = call.has_flag_const(working_set, "multiline")?; + + let args = Arguments { + all: call.has_flag_const(working_set, "all")?, + find, + replace, + cell_paths, + literal_replace, + no_regex, + multiline, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index becfd9be50..cc3772db7c 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -37,6 +37,10 @@ impl Command for SubCommand { vec!["convert", "inverse", "flip"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -49,6 +53,23 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = 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 { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/starts_with.rs b/crates/nu-command/src/strings/str_/starts_with.rs index 73396911e2..aec12f6f77 100644 --- a/crates/nu-command/src/strings/str_/starts_with.rs +++ b/crates/nu-command/src/strings/str_/starts_with.rs @@ -51,6 +51,10 @@ impl Command for SubCommand { vec!["prefix", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -69,6 +73,29 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let substring: Spanned = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: substring.item, + cell_paths, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/stats.rs b/crates/nu-command/src/strings/str_/stats.rs index 20ef35c51f..28c65afe2b 100644 --- a/crates/nu-command/src/strings/str_/stats.rs +++ b/crates/nu-command/src/strings/str_/stats.rs @@ -1,5 +1,6 @@ use fancy_regex::Regex; use nu_engine::command_prelude::*; + use std::collections::BTreeMap; use std::{fmt, str}; use unicode_segmentation::UnicodeSegmentation; @@ -29,6 +30,10 @@ impl Command for SubCommand { vec!["count", "word", "character", "unicode", "wc"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -39,6 +44,15 @@ impl Command for SubCommand { stats(engine_state, call, input) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + stats(working_set.permanent(), call, input) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index 4f5c953dda..5ad7a967da 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -1,11 +1,10 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::{ input_handler::{operate, CmdArgument}, util, }; use nu_engine::command_prelude::*; -use nu_protocol::Range; -use std::cmp::Ordering; +use nu_protocol::{engine::StateWorkingSet, Range}; use unicode_segmentation::UnicodeSegmentation; #[derive(Clone)] @@ -77,6 +76,10 @@ impl Command for SubCommand { vec!["slice"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -103,6 +106,37 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let range: Range = call.req_const(working_set, 0)?; + + let indexes = match util::process_range(&range) { + Ok(idxs) => idxs.into(), + Err(processing_error) => { + return Err(processing_error("could not perform substring", call.head)) + } + }; + + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + indexes, + cell_paths, + graphemes: grapheme_flags_const(working_set, call)?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { @@ -116,6 +150,11 @@ impl Command for SubCommand { example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5", result: Some(Value::test_string("ふが")), }, + Example { + description: "sub string by negative index", + example: " 'good nushell' | str substring 5..-2", + result: Some(Value::test_string("nushel")), + }, ] } } @@ -132,56 +171,46 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { options.0 }; let end: isize = if options.1 < 0 { - std::cmp::max(len + options.1, 0) + options.1 + len } else { options.1 }; - if start < len && end >= 0 { - match start.cmp(&end) { - Ordering::Equal => Value::string("", head), - Ordering::Greater => Value::error( - ShellError::TypeMismatch { - err_message: "End must be greater than or equal to Start".to_string(), - span: head, - }, - head, - ), - Ordering::Less => Value::string( - { - if end == isize::MAX { - if args.graphemes { - s.graphemes(true) - .skip(start as usize) - .collect::>() - .join("") - } else { - String::from_utf8_lossy( - &s.bytes().skip(start as usize).collect::>(), - ) - .to_string() - } - } else if args.graphemes { + if start > end { + Value::string("", head) + } else { + Value::string( + { + if end == isize::MAX { + if args.graphemes { s.graphemes(true) .skip(start as usize) - .take((end - start) as usize) .collect::>() .join("") } else { String::from_utf8_lossy( - &s.bytes() - .skip(start as usize) - .take((end - start) as usize) - .collect::>(), + &s.bytes().skip(start as usize).collect::>(), ) .to_string() } - }, - head, - ), - } - } else { - Value::string("", head) + } else if args.graphemes { + s.graphemes(true) + .skip(start as usize) + .take((end - start + 1) as usize) + .collect::>() + .join("") + } else { + String::from_utf8_lossy( + &s.bytes() + .skip(start as usize) + .take((end - start + 1) as usize) + .collect::>(), + ) + .to_string() + } + }, + head, + ) } } // Propagate errors by explicitly matching them before the final case. @@ -208,6 +237,7 @@ mod tests { test_examples(SubCommand {}) } + #[derive(Debug)] struct Expectation<'a> { options: (isize, isize), expected: &'a str, @@ -231,18 +261,19 @@ mod tests { let word = Value::test_string("andres"); let cases = vec![ - expectation("a", (0, 1)), - expectation("an", (0, 2)), - expectation("and", (0, 3)), - expectation("andr", (0, 4)), - expectation("andre", (0, 5)), + expectation("a", (0, 0)), + expectation("an", (0, 1)), + expectation("and", (0, 2)), + expectation("andr", (0, 3)), + expectation("andre", (0, 4)), + expectation("andres", (0, 5)), expectation("andres", (0, 6)), - expectation("", (0, -6)), - expectation("a", (0, -5)), - expectation("an", (0, -4)), - expectation("and", (0, -3)), - expectation("andr", (0, -2)), - expectation("andre", (0, -1)), + expectation("a", (0, -6)), + expectation("an", (0, -5)), + expectation("and", (0, -4)), + expectation("andr", (0, -3)), + expectation("andre", (0, -2)), + expectation("andres", (0, -1)), // str substring [ -4 , _ ] // str substring -4 , expectation("dres", (-4, isize::MAX)), @@ -257,6 +288,7 @@ mod tests { ]; for expectation in &cases { + println!("{:?}", expectation); let expected = expectation.expected; let actual = action( &word, diff --git a/crates/nu-command/src/strings/str_/trim/trim_.rs b/crates/nu-command/src/strings/str_/trim/trim_.rs index ee414d0da6..52c4017cda 100644 --- a/crates/nu-command/src/strings/str_/trim/trim_.rs +++ b/crates/nu-command/src/strings/str_/trim/trim_.rs @@ -71,6 +71,10 @@ impl Command for SubCommand { vec!["whitespace", "strip", "lstrip", "rstrip"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -79,44 +83,37 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let character = call.get_flag::>(engine_state, stack, "char")?; - let to_trim = match character.as_ref() { - Some(v) => { - if v.item.chars().count() > 1 { - return Err(ShellError::GenericError { - error: "Trim only works with single character".into(), - msg: "needs single character".into(), - span: Some(v.span), - help: None, - inner: vec![], - }); - } - v.item.chars().next() - } - None => None, - }; let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - let mode = match cell_paths { - None => ActionMode::Global, - Some(_) => ActionMode::Local, - }; - let left = call.has_flag(engine_state, stack, "left")?; let right = call.has_flag(engine_state, stack, "right")?; - let trim_side = match (left, right) { - (true, true) => TrimSide::Both, - (true, false) => TrimSide::Left, - (false, true) => TrimSide::Right, - (false, false) => TrimSide::Both, - }; - - let args = Arguments { - to_trim, - trim_side, + run( + character, cell_paths, - mode, - }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + (left, right), + call, + input, + engine_state, + ) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character = call.get_flag_const::>(working_set, "char")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let left = call.has_flag_const(working_set, "left")?; + let right = call.has_flag_const(working_set, "right")?; + run( + character, + cell_paths, + (left, right), + call, + input, + working_set.permanent(), + ) } fn examples(&self) -> Vec { @@ -150,6 +147,52 @@ impl Command for SubCommand { } } +fn run( + character: Option>, + cell_paths: Vec, + (left, right): (bool, bool), + call: &Call, + input: PipelineData, + engine_state: &EngineState, +) -> Result { + let to_trim = match character.as_ref() { + Some(v) => { + if v.item.chars().count() > 1 { + return Err(ShellError::GenericError { + error: "Trim only works with single character".into(), + msg: "needs single character".into(), + span: Some(v.span), + help: None, + inner: vec![], + }); + } + v.item.chars().next() + } + None => None, + }; + + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let mode = match cell_paths { + None => ActionMode::Global, + Some(_) => ActionMode::Local, + }; + + let trim_side = match (left, right) { + (true, true) => TrimSide::Both, + (true, false) => TrimSide::Left, + (false, true) => TrimSide::Right, + (false, false) => TrimSide::Both, + }; + + let args = Arguments { + to_trim, + trim_side, + cell_paths, + mode, + }; + operate(action, args, input, call.head, engine_state.ctrlc.clone()) +} + #[derive(Debug, Copy, Clone)] pub enum ActionMode { Local, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index a200ce75de..f82c23a0e0 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,19 +1,19 @@ 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, 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, - sync::Arc, + sync::{atomic::AtomicBool, Arc}, thread, }; @@ -32,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) } @@ -46,42 +54,33 @@ impl Command for External { ) -> Result { 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 = 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 }; @@ -100,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)); @@ -155,6 +154,9 @@ impl Command for External { } }; + // Log the command we're about to run in case it's useful for debugging purposes. + log::trace!("run-external spawning: {command:?}"); + // Spawn the child process. On Unix, also put the child process to // foreground if we're in an interactive session. #[cfg(windows)] @@ -213,73 +215,54 @@ impl Command for External { } } -/// Removes surrounding quotes from a string. Doesn't remove quotes from raw -/// strings. Returns the original string if it doesn't have matching quotes. -fn remove_quotes(s: &str) -> &str { - let quoted_by_double_quotes = s.len() >= 2 && s.starts_with('"') && s.ends_with('"'); - let quoted_by_single_quotes = s.len() >= 2 && s.starts_with('\'') && s.ends_with('\''); - let quoted_by_backticks = s.len() >= 2 && s.starts_with('`') && s.ends_with('`'); - if quoted_by_double_quotes || quoted_by_single_quotes || quoted_by_backticks { - &s[1..s.len() - 1] - } else { - s - } -} - /// Evaluate all arguments from a call, performing expansions when necessary. pub fn eval_arguments_from_call( engine_state: &EngineState, stack: &mut Stack, call: &Call, -) -> Result>, ShellError> { +) -> Result>, ShellError> { + let ctrlc = &engine_state.ctrlc; let cwd = engine_state.cwd(Some(stack))?; - let mut args: Vec> = vec![]; + let mut args: Vec> = 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)? { - 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 { + 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, 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, 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(), @@ -291,88 +274,81 @@ 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. /// /// Note: This matches the default behavior of Bash, but is known to be /// error-prone. We might want to change this behavior in the future. -fn expand_glob(arg: &str, cwd: &Path, span: Span) -> Result, ShellError> { - let Ok(paths) = nu_glob::glob_with_parent(arg, nu_glob::MatchOptions::default(), cwd) else { - return Ok(vec![arg.into()]); - }; +fn expand_glob( + arg: &str, + cwd: &Path, + span: Span, + interrupt: &Option>, +) -> Result, ShellError> { + const GLOB_CHARS: &[char] = &['*', '?', '[']; - let mut result = vec![]; - for path in paths { - let path = path.map_err(|err| ShellError::IOErrorSpanned { - msg: format!("{}: {:?}", err.path().display(), err.error()), - span, - })?; - // Strip PWD from the resulting paths if possible. - let path_stripped = if let Ok(remainder) = path.strip_prefix(cwd) { - // If stripping PWD results in an empty path, return `.` instead. - if remainder.components().next().is_none() { - Path::new(".") - } else { - remainder + // For an argument that doesn't include the GLOB_CHARS, just do the `expand_tilde` + // and `expand_ndots` expansion + if !arg.contains(GLOB_CHARS) { + 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 = vec![]; + + for m in matches { + if nu_utils::ctrl_c::was_pressed(interrupt) { + return Err(ShellError::InterruptedByUser { span: Some(span) }); } - } else { - &path - }; - let path_string = path_stripped.to_string_lossy().to_string(); - result.push(path_string); - } + if let Ok(arg) = m { + let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd); + result.push(arg.into()); + } else { + result.push(arg.into()); + } + } - if result.is_empty() { - result.push(arg.to_string()); - } + // FIXME: do we want to special-case this further? We might accidentally expand when they don't + // intend to + if result.is_empty() { + result.push(arg.into()); + } - Ok(result) + Ok(result) + } else { + Ok(vec![arg.into()]) + } } -/// 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> { - // Check that `arg` is a long option. - if !arg.starts_with("--") { - return Cow::Borrowed(arg); +fn resolve_globbed_path_to_cwd_relative( + path: PathBuf, + prefix: Option<&PathBuf>, + cwd: &Path, +) -> PathBuf { + if let Some(prefix) = prefix { + if let Ok(remainder) = path.strip_prefix(prefix) { + let new_prefix = if let Some(pfx) = diff_paths(prefix, cwd) { + pfx + } else { + prefix.to_path_buf() + }; + new_prefix.join(remainder) + } else { + path + } + } else { + path } - // 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 @@ -543,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 { +pub fn which(name: impl AsRef, paths: &str, cwd: &Path) -> Option { #[cfg(windows)] let paths = format!("{};{}", cwd.display(), paths); which::which_in(name, Some(paths), cwd).ok() @@ -559,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) -> Result, ShellError> { +#[cfg_attr(not(windows), allow(dead_code))] +fn escape_cmd_argument(arg: &Spanned) -> Result, 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: @@ -578,12 +555,12 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, 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 { @@ -594,120 +571,74 @@ fn escape_cmd_argument(arg: &Spanned) -> Result, 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) -> 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; - - #[test] - fn test_remove_quotes() { - assert_eq!(remove_quotes(r#""#), r#""#); - assert_eq!(remove_quotes(r#"'"#), r#"'"#); - assert_eq!(remove_quotes(r#"''"#), r#""#); - assert_eq!(remove_quotes(r#""foo""#), r#"foo"#); - assert_eq!(remove_quotes(r#"`foo '"' bar`"#), r#"foo '"' bar"#); - assert_eq!(remove_quotes(r#"'foo' bar"#), r#"'foo' bar"#); - assert_eq!(remove_quotes(r#"r#'foo'#"#), r#"r#'foo'#"#); - } - - #[test] - fn test_eval_argument() { - fn expression(expr: Expr) -> Expression { - Expression { - expr, - span: Span::unknown(), - ty: Type::Any, - custom_completion: None, - } - } - - fn eval(expr: Expr, spread: bool) -> Result, 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(); - } + use nu_test_support::{fs::Stub, playground::Playground}; #[test] fn test_expand_glob() { - let tempdir = tempfile::tempdir().unwrap(); - let cwd = tempdir.path(); - std::fs::File::create(cwd.join("a.txt")).unwrap(); - std::fs::File::create(cwd.join("b.txt")).unwrap(); + Playground::setup("test_expand_glob", |dirs, play| { + play.with_files(&[Stub::EmptyFile("a.txt"), Stub::EmptyFile("b.txt")]); - let actual = expand_glob("*.txt", cwd, Span::unknown()).unwrap(); - let expected = &["a.txt", "b.txt"]; - assert_eq!(actual, expected); + let cwd = dirs.test(); - let actual = expand_glob("'*.txt'", cwd, Span::unknown()).unwrap(); - let expected = &["'*.txt'"]; - assert_eq!(actual, expected); + let actual = expand_glob("*.txt", cwd, Span::unknown(), &None).unwrap(); + let expected = &["a.txt", "b.txt"]; + assert_eq!(actual, expected); - let actual = expand_glob(cwd.to_str().unwrap(), cwd, Span::unknown()).unwrap(); - let expected = &["."]; - assert_eq!(actual, expected); + let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); + assert_eq!(actual, expected); - let actual = expand_glob("[*.txt", cwd, Span::unknown()).unwrap(); - let expected = &["[*.txt"]; - assert_eq!(actual, expected); - } + let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); + let expected = &["'*.txt'"]; + 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 = expand_glob(".", cwd, Span::unknown(), &None).unwrap(); + let expected = &["."]; + assert_eq!(actual, expected); - let actual = remove_inner_quotes(r#"--option="value""#); - let expected = r#"--option=value"#; - assert_eq!(actual, expected); + let actual = expand_glob("./a.txt", cwd, Span::unknown(), &None).unwrap(); + let expected = &["./a.txt"]; + assert_eq!(actual, expected); - let actual = remove_inner_quotes(r#"--option='value'"#); - let expected = r#"--option=value"#; - assert_eq!(actual, expected); + let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); + let expected = &["[*.txt"]; + assert_eq!(actual, expected); - let actual = remove_inner_quotes(r#"--option "value""#); - let expected = r#"--option "value""#; - 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 = vec![home.join("foo.txt").into()]; + assert_eq!(actual, expected); + }) } #[test] diff --git a/crates/nu-command/src/system/sys/cpu.rs b/crates/nu-command/src/system/sys/cpu.rs index b2f6d6afa6..d24197bf70 100644 --- a/crates/nu-command/src/system/sys/cpu.rs +++ b/crates/nu-command/src/system/sys/cpu.rs @@ -1,4 +1,6 @@ +use super::trim_cstyle_null; use nu_engine::command_prelude::*; +use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL}; #[derive(Clone)] pub struct SysCpu; @@ -26,7 +28,7 @@ impl Command for SysCpu { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::cpu(call.head).into_pipeline_data()) + Ok(cpu(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -37,3 +39,42 @@ impl Command for SysCpu { }] } } + +fn cpu(span: Span) -> Value { + let mut sys = System::new(); + sys.refresh_cpu_specifics(CpuRefreshKind::everything()); + // We must refresh the CPU twice a while apart to get valid usage data. + // In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that + // that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily + std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2); + sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); + + let cpus = sys + .cpus() + .iter() + .map(|cpu| { + // sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes. + // Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats. + let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0; + + let load_avg = System::load_average(); + let load_avg = format!( + "{:.2}, {:.2}, {:.2}", + load_avg.one, load_avg.five, load_avg.fifteen + ); + + let record = record! { + "name" => Value::string(trim_cstyle_null(cpu.name()), span), + "brand" => Value::string(trim_cstyle_null(cpu.brand()), span), + "freq" => Value::int(cpu.frequency() as i64, span), + "cpu_usage" => Value::float(rounded_usage.into(), span), + "load_average" => Value::string(load_avg, span), + "vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span), + }; + + Value::record(record, span) + }) + .collect(); + + Value::list(cpus, span) +} diff --git a/crates/nu-command/src/system/sys/disks.rs b/crates/nu-command/src/system/sys/disks.rs index 66991c9087..0d9ce09db3 100644 --- a/crates/nu-command/src/system/sys/disks.rs +++ b/crates/nu-command/src/system/sys/disks.rs @@ -1,4 +1,6 @@ +use super::trim_cstyle_null; use nu_engine::command_prelude::*; +use sysinfo::Disks; #[derive(Clone)] pub struct SysDisks; @@ -26,7 +28,7 @@ impl Command for SysDisks { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::disks(call.head).into_pipeline_data()) + Ok(disks(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -37,3 +39,27 @@ impl Command for SysDisks { }] } } + +fn disks(span: Span) -> Value { + let disks = Disks::new_with_refreshed_list() + .iter() + .map(|disk| { + let device = trim_cstyle_null(disk.name().to_string_lossy()); + let typ = trim_cstyle_null(disk.file_system().to_string_lossy()); + + let record = record! { + "device" => Value::string(device, span), + "type" => Value::string(typ, span), + "mount" => Value::string(disk.mount_point().to_string_lossy(), span), + "total" => Value::filesize(disk.total_space() as i64, span), + "free" => Value::filesize(disk.available_space() as i64, span), + "removable" => Value::bool(disk.is_removable(), span), + "kind" => Value::string(disk.kind().to_string(), span), + }; + + Value::record(record, span) + }) + .collect(); + + Value::list(disks, span) +} diff --git a/crates/nu-command/src/system/sys/host.rs b/crates/nu-command/src/system/sys/host.rs index 969f59ef99..64dd43424d 100644 --- a/crates/nu-command/src/system/sys/host.rs +++ b/crates/nu-command/src/system/sys/host.rs @@ -1,4 +1,7 @@ +use super::trim_cstyle_null; +use chrono::{DateTime, FixedOffset, Local}; use nu_engine::command_prelude::*; +use sysinfo::System; #[derive(Clone)] pub struct SysHost; @@ -26,8 +29,7 @@ impl Command for SysHost { call: &Call, _input: PipelineData, ) -> Result { - let host = super::host(call.head); - Ok(Value::record(host, call.head).into_pipeline_data()) + Ok(host(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -38,3 +40,53 @@ impl Command for SysHost { }] } } + +fn host(span: Span) -> Value { + let mut record = Record::new(); + + if let Some(name) = System::name() { + record.push("name", Value::string(trim_cstyle_null(name), span)); + } + if let Some(version) = System::os_version() { + record.push("os_version", Value::string(trim_cstyle_null(version), span)); + } + if let Some(long_version) = System::long_os_version() { + record.push( + "long_os_version", + Value::string(trim_cstyle_null(long_version), span), + ); + } + if let Some(version) = System::kernel_version() { + record.push( + "kernel_version", + Value::string(trim_cstyle_null(version), span), + ); + } + if let Some(hostname) = System::host_name() { + record.push("hostname", Value::string(trim_cstyle_null(hostname), span)); + } + + let uptime = System::uptime() + .saturating_mul(1_000_000_000) + .try_into() + .unwrap_or(i64::MAX); + + record.push("uptime", Value::duration(uptime, span)); + + let boot_time = boot_time() + .map(|time| Value::date(time, span)) + .unwrap_or(Value::nothing(span)); + + record.push("boot_time", boot_time); + + Value::record(record, span) +} + +fn boot_time() -> Option> { + // Broken systems can apparently return really high values. + // See: https://github.com/nushell/nushell/issues/10155 + // First, try to convert u64 to i64, and then try to create a `DateTime`. + let secs = System::boot_time().try_into().ok()?; + let time = DateTime::from_timestamp(secs, 0)?; + Some(time.with_timezone(&Local).fixed_offset()) +} diff --git a/crates/nu-command/src/system/sys/mem.rs b/crates/nu-command/src/system/sys/mem.rs index e5dc36e1b7..89527807d7 100644 --- a/crates/nu-command/src/system/sys/mem.rs +++ b/crates/nu-command/src/system/sys/mem.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use sysinfo::System; #[derive(Clone)] pub struct SysMem; @@ -26,7 +27,7 @@ impl Command for SysMem { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::mem(call.head).into_pipeline_data()) + Ok(mem(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -37,3 +38,20 @@ impl Command for SysMem { }] } } + +fn mem(span: Span) -> Value { + let mut sys = System::new(); + sys.refresh_memory(); + + let record = record! { + "total" => Value::filesize(sys.total_memory() as i64, span), + "free" => Value::filesize(sys.free_memory() as i64, span), + "used" => Value::filesize(sys.used_memory() as i64, span), + "available" => Value::filesize(sys.available_memory() as i64, span), + "swap total" => Value::filesize(sys.total_swap() as i64, span), + "swap free" => Value::filesize(sys.free_swap() as i64, span), + "swap used" => Value::filesize(sys.used_swap() as i64, span), + }; + + Value::record(record, span) +} diff --git a/crates/nu-command/src/system/sys/mod.rs b/crates/nu-command/src/system/sys/mod.rs index fcb40fd37d..02d271537e 100644 --- a/crates/nu-command/src/system/sys/mod.rs +++ b/crates/nu-command/src/system/sys/mod.rs @@ -16,202 +16,6 @@ pub use sys_::Sys; pub use temp::SysTemp; pub use users::SysUsers; -use chrono::{DateTime, FixedOffset, Local}; -use nu_protocol::{record, Record, Span, Value}; -use sysinfo::{ - Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL, -}; - -pub fn trim_cstyle_null(s: impl AsRef) -> String { +fn trim_cstyle_null(s: impl AsRef) -> String { s.as_ref().trim_matches('\0').into() } - -pub fn disks(span: Span) -> Value { - let disks = Disks::new_with_refreshed_list() - .iter() - .map(|disk| { - let device = trim_cstyle_null(disk.name().to_string_lossy()); - let typ = trim_cstyle_null(disk.file_system().to_string_lossy()); - - let record = record! { - "device" => Value::string(device, span), - "type" => Value::string(typ, span), - "mount" => Value::string(disk.mount_point().to_string_lossy(), span), - "total" => Value::filesize(disk.total_space() as i64, span), - "free" => Value::filesize(disk.available_space() as i64, span), - "removable" => Value::bool(disk.is_removable(), span), - "kind" => Value::string(disk.kind().to_string(), span), - }; - - Value::record(record, span) - }) - .collect(); - - Value::list(disks, span) -} - -pub fn net(span: Span) -> Value { - let networks = Networks::new_with_refreshed_list() - .iter() - .map(|(iface, data)| { - let record = record! { - "name" => Value::string(trim_cstyle_null(iface), span), - "sent" => Value::filesize(data.total_transmitted() as i64, span), - "recv" => Value::filesize(data.total_received() as i64, span), - }; - - Value::record(record, span) - }) - .collect(); - - Value::list(networks, span) -} - -pub fn cpu(span: Span) -> Value { - let mut sys = System::new(); - sys.refresh_cpu_specifics(CpuRefreshKind::everything()); - // We must refresh the CPU twice a while apart to get valid usage data. - // In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that - // that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily - std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2); - sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); - - let cpus = sys - .cpus() - .iter() - .map(|cpu| { - // sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes. - // Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats. - let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0; - - let load_avg = System::load_average(); - let load_avg = format!( - "{:.2}, {:.2}, {:.2}", - load_avg.one, load_avg.five, load_avg.fifteen - ); - - let record = record! { - "name" => Value::string(trim_cstyle_null(cpu.name()), span), - "brand" => Value::string(trim_cstyle_null(cpu.brand()), span), - "freq" => Value::int(cpu.frequency() as i64, span), - "cpu_usage" => Value::float(rounded_usage.into(), span), - "load_average" => Value::string(load_avg, span), - "vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span), - }; - - Value::record(record, span) - }) - .collect(); - - Value::list(cpus, span) -} - -pub fn mem(span: Span) -> Value { - let mut sys = System::new(); - sys.refresh_memory(); - - let record = record! { - "total" => Value::filesize(sys.total_memory() as i64, span), - "free" => Value::filesize(sys.free_memory() as i64, span), - "used" => Value::filesize(sys.used_memory() as i64, span), - "available" => Value::filesize(sys.available_memory() as i64, span), - "swap total" => Value::filesize(sys.total_swap() as i64, span), - "swap free" => Value::filesize(sys.free_swap() as i64, span), - "swap used" => Value::filesize(sys.used_swap() as i64, span), - }; - - Value::record(record, span) -} - -pub fn users(span: Span) -> Value { - let users = Users::new_with_refreshed_list() - .iter() - .map(|user| { - let groups = user - .groups() - .iter() - .map(|group| Value::string(trim_cstyle_null(group.name()), span)) - .collect(); - - let record = record! { - "name" => Value::string(trim_cstyle_null(user.name()), span), - "groups" => Value::list(groups, span), - }; - - Value::record(record, span) - }) - .collect(); - - Value::list(users, span) -} - -pub fn host(span: Span) -> Record { - let mut record = Record::new(); - - if let Some(name) = System::name() { - record.push("name", Value::string(trim_cstyle_null(name), span)); - } - if let Some(version) = System::os_version() { - record.push("os_version", Value::string(trim_cstyle_null(version), span)); - } - if let Some(long_version) = System::long_os_version() { - record.push( - "long_os_version", - Value::string(trim_cstyle_null(long_version), span), - ); - } - if let Some(version) = System::kernel_version() { - record.push( - "kernel_version", - Value::string(trim_cstyle_null(version), span), - ); - } - if let Some(hostname) = System::host_name() { - record.push("hostname", Value::string(trim_cstyle_null(hostname), span)); - } - - let uptime = System::uptime() - .saturating_mul(1_000_000_000) - .try_into() - .unwrap_or(i64::MAX); - - record.push("uptime", Value::duration(uptime, span)); - - let boot_time = boot_time() - .map(|time| Value::date(time, span)) - .unwrap_or(Value::nothing(span)); - - record.push("boot_time", boot_time); - - record -} - -fn boot_time() -> Option> { - // Broken systems can apparently return really high values. - // See: https://github.com/nushell/nushell/issues/10155 - // First, try to convert u64 to i64, and then try to create a `DateTime`. - let secs = System::boot_time().try_into().ok()?; - let time = DateTime::from_timestamp(secs, 0)?; - Some(time.with_timezone(&Local).fixed_offset()) -} - -pub fn temp(span: Span) -> Value { - let components = Components::new_with_refreshed_list() - .iter() - .map(|component| { - let mut record = record! { - "unit" => Value::string(component.label(), span), - "temp" => Value::float(component.temperature().into(), span), - "high" => Value::float(component.max().into(), span), - }; - - if let Some(critical) = component.critical() { - record.push("critical", Value::float(critical.into(), span)); - } - - Value::record(record, span) - }) - .collect(); - - Value::list(components, span) -} diff --git a/crates/nu-command/src/system/sys/net.rs b/crates/nu-command/src/system/sys/net.rs index 98dca1bc2f..ef1c595800 100644 --- a/crates/nu-command/src/system/sys/net.rs +++ b/crates/nu-command/src/system/sys/net.rs @@ -1,4 +1,6 @@ +use super::trim_cstyle_null; use nu_engine::command_prelude::*; +use sysinfo::Networks; #[derive(Clone)] pub struct SysNet; @@ -26,7 +28,7 @@ impl Command for SysNet { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::net(call.head).into_pipeline_data()) + Ok(net(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -37,3 +39,20 @@ impl Command for SysNet { }] } } + +fn net(span: Span) -> Value { + let networks = Networks::new_with_refreshed_list() + .iter() + .map(|(iface, data)| { + let record = record! { + "name" => Value::string(trim_cstyle_null(iface), span), + "sent" => Value::filesize(data.total_transmitted() as i64, span), + "recv" => Value::filesize(data.total_received() as i64, span), + }; + + Value::record(record, span) + }) + .collect(); + + Value::list(networks, span) +} diff --git a/crates/nu-command/src/system/sys/sys_.rs b/crates/nu-command/src/system/sys/sys_.rs index 2886836be9..5369064f50 100644 --- a/crates/nu-command/src/system/sys/sys_.rs +++ b/crates/nu-command/src/system/sys/sys_.rs @@ -1,4 +1,4 @@ -use nu_engine::command_prelude::*; +use nu_engine::{command_prelude::*, get_full_help}; #[derive(Clone)] pub struct Sys; @@ -20,41 +20,17 @@ impl Command for Sys { } fn extra_usage(&self) -> &str { - "Note that this command may take a noticeable amount of time to run. To reduce the time taken, you can use the various `sys` sub commands to get the subset of information you are interested in." + "You must use one of the following subcommands. Using this command as-is will only produce this help message." } fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { - nu_protocol::report_error_new( - engine_state, - &ShellError::GenericError { - error: "Deprecated command".into(), - msg: "the `sys` command is deprecated, please use the new subcommands (`sys host`, `sys mem`, etc.)." - .into(), - span: Some(call.head), - help: None, - inner: vec![], - }, - ); - - let head = call.head; - - let mut host = super::host(head); - host.push("sessions", super::users(head)); - let record = record! { - "host" => Value::record(host, head), - "cpu" => super::cpu(head), - "disks" => super::disks(head), - "mem" => super::mem(head), - "temp" => super::temp(head), - "net" => super::net(head), - }; - Ok(Value::record(record, head).into_pipeline_data()) + Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/system/sys/temp.rs b/crates/nu-command/src/system/sys/temp.rs index eaf1ed05db..088471f0fd 100644 --- a/crates/nu-command/src/system/sys/temp.rs +++ b/crates/nu-command/src/system/sys/temp.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use sysinfo::Components; #[derive(Clone)] pub struct SysTemp; @@ -30,7 +31,7 @@ impl Command for SysTemp { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::temp(call.head).into_pipeline_data()) + Ok(temp(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -41,3 +42,24 @@ impl Command for SysTemp { }] } } + +fn temp(span: Span) -> Value { + let components = Components::new_with_refreshed_list() + .iter() + .map(|component| { + let mut record = record! { + "unit" => Value::string(component.label(), span), + "temp" => Value::float(component.temperature().into(), span), + "high" => Value::float(component.max().into(), span), + }; + + if let Some(critical) = component.critical() { + record.push("critical", Value::float(critical.into(), span)); + } + + Value::record(record, span) + }) + .collect(); + + Value::list(components, span) +} diff --git a/crates/nu-command/src/system/sys/users.rs b/crates/nu-command/src/system/sys/users.rs index 9aab2b9b7b..04d9b9db91 100644 --- a/crates/nu-command/src/system/sys/users.rs +++ b/crates/nu-command/src/system/sys/users.rs @@ -1,4 +1,6 @@ +use super::trim_cstyle_null; use nu_engine::command_prelude::*; +use sysinfo::Users; #[derive(Clone)] pub struct SysUsers; @@ -11,7 +13,7 @@ impl Command for SysUsers { fn signature(&self) -> Signature { Signature::build("sys users") .category(Category::System) - .input_output_types(vec![(Type::Nothing, Type::record())]) + .input_output_types(vec![(Type::Nothing, Type::table())]) } fn usage(&self) -> &str { @@ -25,7 +27,7 @@ impl Command for SysUsers { call: &Call, _input: PipelineData, ) -> Result { - Ok(super::users(call.head).into_pipeline_data()) + Ok(users(call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -36,3 +38,25 @@ impl Command for SysUsers { }] } } + +fn users(span: Span) -> Value { + let users = Users::new_with_refreshed_list() + .iter() + .map(|user| { + let groups = user + .groups() + .iter() + .map(|group| Value::string(trim_cstyle_null(group.name()), span)) + .collect(); + + let record = record! { + "name" => Value::string(trim_cstyle_null(user.name()), span), + "groups" => Value::list(groups, span), + }; + + Value::record(record, span) + }) + .collect(); + + Value::list(users, span) +} diff --git a/crates/nu-command/src/system/which_.rs b/crates/nu-command/src/system/which_.rs index 81b6057864..f0cf4ece39 100644 --- a/crates/nu-command/src/system/which_.rs +++ b/crates/nu-command/src/system/which_.rs @@ -92,7 +92,6 @@ fn get_entries_in_nu( all_entries } -#[cfg(feature = "which-support")] fn get_first_entry_in_path( item: &str, span: Span, @@ -104,17 +103,6 @@ fn get_first_entry_in_path( .ok() } -#[cfg(not(feature = "which-support"))] -fn get_first_entry_in_path( - _item: &str, - _span: Span, - _cwd: impl AsRef, - _paths: impl AsRef, -) -> Option { - None -} - -#[cfg(feature = "which-support")] fn get_all_entries_in_path( item: &str, span: Span, @@ -129,16 +117,6 @@ fn get_all_entries_in_path( .unwrap_or_default() } -#[cfg(not(feature = "which-support"))] -fn get_all_entries_in_path( - _item: &str, - _span: Span, - _cwd: impl AsRef, - _paths: impl AsRef, -) -> Vec { - vec![] -} - #[derive(Debug)] struct WhichArgs { applications: Vec>, diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2fe9319821..f0cc90fa9f 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -170,7 +170,10 @@ impl Command for Table { }), Value::test_record(record! { "a" => Value::test_int(3), - "b" => Value::test_int(4), + "b" => Value::test_list(vec![ + Value::test_int(4), + Value::test_int(4), + ]) }), ])), }, @@ -184,7 +187,10 @@ impl Command for Table { }), Value::test_record(record! { "a" => Value::test_int(3), - "b" => Value::test_int(4), + "b" => Value::test_list(vec![ + Value::test_int(4), + Value::test_int(4), + ]) }), ])), }, @@ -599,6 +605,7 @@ fn handle_row_stream( // First, `ls` sources: Some(PipelineMetadata { data_source: DataSource::Ls, + .. }) => { let config = get_config(input.engine_state, input.stack); let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") { @@ -630,6 +637,7 @@ fn handle_row_stream( // Next, `to html -l` sources: Some(PipelineMetadata { data_source: DataSource::HtmlThemes, + .. }) => { stream.map(|mut value| { if let Value::Record { val: record, .. } = &mut value { diff --git a/crates/nu-command/tests/commands/cal.rs b/crates/nu-command/tests/commands/cal.rs index 651ab8d3cd..65d6306845 100644 --- a/crates/nu-command/tests/commands/cal.rs +++ b/crates/nu-command/tests/commands/cal.rs @@ -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"); +} diff --git a/crates/nu-command/tests/commands/conversions/into/binary.rs b/crates/nu-command/tests/commands/conversions/into/binary.rs new file mode 100644 index 0000000000..c8fd24df77 --- /dev/null +++ b/crates/nu-command/tests/commands/conversions/into/binary.rs @@ -0,0 +1,13 @@ +use nu_test_support::nu; + +#[test] +fn sets_stream_from_internal_command_as_binary() { + let result = nu!("seq 1 10 | to text | into binary | describe"); + assert_eq!("binary (stream)", result.out); +} + +#[test] +fn sets_stream_from_external_command_as_binary() { + let result = nu!("^nu --testbin cococo | into binary | describe"); + assert_eq!("binary (stream)", result.out); +} diff --git a/crates/nu-command/tests/commands/conversions/into/mod.rs b/crates/nu-command/tests/commands/conversions/into/mod.rs index a7a829445a..ad10199b5c 100644 --- a/crates/nu-command/tests/commands/conversions/into/mod.rs +++ b/crates/nu-command/tests/commands/conversions/into/mod.rs @@ -1 +1,2 @@ +mod binary; mod int; diff --git a/crates/nu-command/tests/commands/detect_columns.rs b/crates/nu-command/tests/commands/detect_columns.rs index 5bc057fa18..296d039b02 100644 --- a/crates/nu-command/tests/commands/detect_columns.rs +++ b/crates/nu-command/tests/commands/detect_columns.rs @@ -31,12 +31,12 @@ fn detect_columns_with_legacy_and_flag_c() { ( "$\"c1 c2 c3 c4 c5(char nl)a b c d e\"", "[[c1,c3,c4,c5]; ['a b',c,d,e]]", - "0..0", + "0..1", ), ( "$\"c1 c2 c3 c4 c5(char nl)a b c d e\"", "[[c1,c2,c3,c4]; [a,b,c,'d e']]", - "(-2)..(-2)", + "(-2)..(-1)", ), ( "$\"c1 c2 c3 c4 c5(char nl)a b c d e\"", @@ -72,10 +72,10 @@ drwxr-xr-x 2 root root 4.0K Mar 20 08:28 =(char nl) drwxr-xr-x 4 root root 4.0K Mar 20 08:18 ~(char nl) -rw-r--r-- 1 root root 3.0K Mar 20 07:23 ~asdf(char nl)\""; let expected = "[ -['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column8']; -['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20 08:28', '='], -['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20 08:18', '~'], -['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20 07:23', '~asdf'] +['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column7', 'column8']; +['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20', '08:28', '='], +['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20', '08:18', '~'], +['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20', '07:23', '~asdf'] ]"; let range = "5..6"; let cmd = format!( diff --git a/crates/nu-command/tests/commands/error_make.rs b/crates/nu-command/tests/commands/error_make.rs index 0c6908d2aa..f5714c88ac 100644 --- a/crates/nu-command/tests/commands/error_make.rs +++ b/crates/nu-command/tests/commands/error_make.rs @@ -4,8 +4,9 @@ use nu_test_support::nu; fn error_label_works() { let actual = nu!("error make {msg:foo label:{text:unseen}}"); - assert!(actual.err.contains("unseen")); - assert!(actual.err.contains("╰──")); + assert!(actual + .err + .contains("label at line 1, columns 1 to 10: unseen")); } #[test] diff --git a/crates/nu-command/tests/commands/find.rs b/crates/nu-command/tests/commands/find.rs index ed811f2a57..89bf2d9f39 100644 --- a/crates/nu-command/tests/commands/find.rs +++ b/crates/nu-command/tests/commands/find.rs @@ -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"); diff --git a/crates/nu-command/tests/commands/format.rs b/crates/nu-command/tests/commands/format.rs index 6b1649c8b5..6084faf1e4 100644 --- a/crates/nu-command/tests/commands/format.rs +++ b/crates/nu-command/tests/commands/format.rs @@ -37,7 +37,7 @@ fn given_fields_can_be_column_paths() { } #[test] -fn can_use_variables() { +fn cant_use_variables() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" @@ -46,7 +46,8 @@ fn can_use_variables() { "# )); - assert_eq!(actual.out, "nu is a new type of shell"); + // TODO SPAN: This has been removed during SpanId refactor + assert!(actual.err.contains("Removed functionality")); } #[test] @@ -55,7 +56,7 @@ fn error_unmatched_brace() { cwd: "tests/fixtures/formats", pipeline( r#" open cargo_sample.toml - | format pattern "{$it.package.name" + | format pattern "{package.name" "# )); diff --git a/crates/nu-command/tests/commands/into_filesize.rs b/crates/nu-command/tests/commands/into_filesize.rs index 2917e141db..6a72c76b5d 100644 --- a/crates/nu-command/tests/commands/into_filesize.rs +++ b/crates/nu-command/tests/commands/into_filesize.rs @@ -63,8 +63,71 @@ fn into_filesize_negative_filesize() { } #[test] -fn into_negative_filesize() { +fn into_filesize_negative_str_filesize() { + let actual = nu!("'-3kib' | into filesize"); + + assert!(actual.out.contains("-3.0 KiB")); +} + +#[test] +fn into_filesize_wrong_negative_str_filesize() { + let actual = nu!("'--3kib' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} + +#[test] +fn into_filesize_large_negative_str_filesize() { + let actual = nu!("'-10000PiB' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} + +#[test] +fn into_filesize_negative_str() { let actual = nu!("'-1' | into filesize"); assert!(actual.out.contains("-1 B")); } + +#[test] +fn into_filesize_wrong_negative_str() { + let actual = nu!("'--1' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} + +#[test] +fn into_filesize_positive_str_filesize() { + let actual = nu!("'+1Kib' | into filesize"); + + assert!(actual.out.contains("1.0 KiB")); +} + +#[test] +fn into_filesize_wrong_positive_str_filesize() { + let actual = nu!("'++1Kib' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} + +#[test] +fn into_filesize_large_positive_str_filesize() { + let actual = nu!("'+10000PiB' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} + +#[test] +fn into_filesize_positive_str() { + let actual = nu!("'+1' | into filesize"); + + assert!(actual.out.contains("1 B")); +} + +#[test] +fn into_filesize_wrong_positive_str() { + let actual = nu!("'++1' | into filesize"); + + assert!(actual.err.contains("can't convert string to filesize")); +} diff --git a/crates/nu-command/tests/commands/length.rs b/crates/nu-command/tests/commands/length.rs index 63318e65db..b67ac452e8 100644 --- a/crates/nu-command/tests/commands/length.rs +++ b/crates/nu-command/tests/commands/length.rs @@ -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"); } diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 922e804405..a1cb82a4b4 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -125,7 +125,6 @@ mod upsert; mod url; mod use_; mod where_; -#[cfg(feature = "which-support")] mod which; mod while_; mod with_env; diff --git a/crates/nu-command/tests/commands/network/http/delete.rs b/crates/nu-command/tests/commands/network/http/delete.rs index bc71ed3945..c284c2ccfd 100644 --- a/crates/nu-command/tests/commands/network/http/delete.rs +++ b/crates/nu-command/tests/commands/network/http/delete.rs @@ -20,6 +20,25 @@ fn http_delete_is_success() { assert!(actual.out.is_empty()) } +#[test] +fn http_delete_is_success_pipeline() { + let mut server = Server::new(); + + let _mock = server.mock("DELETE", "/").create(); + + let actual = nu!(pipeline( + format!( + r#" + "foo" | http delete {url} + "#, + url = server.url() + ) + .as_str() + )); + + assert!(actual.out.is_empty()) +} + #[test] fn http_delete_failed_due_to_server_error() { let mut server = Server::new(); diff --git a/crates/nu-command/tests/commands/network/http/patch.rs b/crates/nu-command/tests/commands/network/http/patch.rs index 4196b304c6..dc3a755baa 100644 --- a/crates/nu-command/tests/commands/network/http/patch.rs +++ b/crates/nu-command/tests/commands/network/http/patch.rs @@ -20,6 +20,25 @@ fn http_patch_is_success() { assert!(actual.out.is_empty()) } +#[test] +fn http_patch_is_success_pipeline() { + let mut server = Server::new(); + + let _mock = server.mock("PATCH", "/").match_body("foo").create(); + + let actual = nu!(pipeline( + format!( + r#" + "foo" | http patch {url} + "#, + url = server.url() + ) + .as_str() + )); + + assert!(actual.out.is_empty()) +} + #[test] fn http_patch_failed_due_to_server_error() { let mut server = Server::new(); @@ -55,7 +74,9 @@ fn http_patch_failed_due_to_missing_body() { .as_str() )); - assert!(actual.err.contains("Usage: http patch")) + assert!(actual + .err + .contains("Data must be provided either through pipeline or positional argument")) } #[test] diff --git a/crates/nu-command/tests/commands/network/http/post.rs b/crates/nu-command/tests/commands/network/http/post.rs index bd13542482..02aa8df23f 100644 --- a/crates/nu-command/tests/commands/network/http/post.rs +++ b/crates/nu-command/tests/commands/network/http/post.rs @@ -19,6 +19,24 @@ fn http_post_is_success() { assert!(actual.out.is_empty()) } +#[test] +fn http_post_is_success_pipeline() { + let mut server = Server::new(); + + let _mock = server.mock("POST", "/").match_body("foo").create(); + + let actual = nu!(pipeline( + format!( + r#" + "foo" | http post {url} + "#, + url = server.url() + ) + .as_str() + )); + + assert!(actual.out.is_empty()) +} #[test] fn http_post_failed_due_to_server_error() { @@ -55,7 +73,9 @@ fn http_post_failed_due_to_missing_body() { .as_str() )); - assert!(actual.err.contains("Usage: http post")) + assert!(actual + .err + .contains("Data must be provided either through pipeline or positional argument")) } #[test] diff --git a/crates/nu-command/tests/commands/network/http/put.rs b/crates/nu-command/tests/commands/network/http/put.rs index 002b1f8632..43251cd21a 100644 --- a/crates/nu-command/tests/commands/network/http/put.rs +++ b/crates/nu-command/tests/commands/network/http/put.rs @@ -20,6 +20,25 @@ fn http_put_is_success() { assert!(actual.out.is_empty()) } +#[test] +fn http_put_is_success_pipeline() { + let mut server = Server::new(); + + let _mock = server.mock("PUT", "/").match_body("foo").create(); + + let actual = nu!(pipeline( + format!( + r#" + "foo" | http put {url} + "#, + url = server.url() + ) + .as_str() + )); + + assert!(actual.out.is_empty()) +} + #[test] fn http_put_failed_due_to_server_error() { let mut server = Server::new(); @@ -55,7 +74,9 @@ fn http_put_failed_due_to_missing_body() { .as_str() )); - assert!(actual.err.contains("Usage: http put")) + assert!(actual + .err + .contains("Data must be provided either through pipeline or positional argument")) } #[test] diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 14428edcbe..154c31b71c 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -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() { diff --git a/crates/nu-command/tests/commands/str_/mod.rs b/crates/nu-command/tests/commands/str_/mod.rs index 43f59b3e10..2aeabfd04a 100644 --- a/crates/nu-command/tests/commands/str_/mod.rs +++ b/crates/nu-command/tests/commands/str_/mod.rs @@ -255,7 +255,7 @@ fn substrings_the_input() { } #[test] -fn substring_errors_if_start_index_is_greater_than_end_index() { +fn substring_empty_if_start_index_is_greater_than_end_index() { Playground::setup("str_test_9", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( "sample.toml", @@ -270,12 +270,10 @@ fn substring_errors_if_start_index_is_greater_than_end_index() { r#" open sample.toml | str substring 6..4 fortune.teller.phone + | get fortune.teller.phone "# )); - - assert!(actual - .err - .contains("End must be greater than or equal to Start")) + assert_eq!(actual.out, "") }) } @@ -375,6 +373,21 @@ fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given( }) } +#[test] +fn substring_by_negative_index() { + Playground::setup("str_test_13", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), "'apples' | str substring 0..-1", + ); + assert_eq!(actual.out, "apples"); + + let actual = nu!( + cwd: dirs.test(), "'apples' | str substring 0..<-1", + ); + assert_eq!(actual.out, "apple"); + }) +} + #[test] fn str_reverse() { let actual = nu!(r#" diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index a986c7b15b..9c4f34ebfd 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -667,106 +667,6 @@ fn test_expand_big_0() { let expected = join_lines([ "╭──────────────────┬───────────────────────────────────────────────────────────╮", - "│ │ ╭───┬─────────┬────────────╮ │", - "│ bench │ │ # │ harness │ name │ │", - "│ │ ├───┼─────────┼────────────┤ │", - "│ │ │ 0 │ false │ benchmarks │ │", - "│ │ ╰───┴─────────┴────────────╯ │", - "│ │ ╭───┬──────┬─────────────╮ │", - "│ bin │ │ # │ name │ path │ │", - "│ │ ├───┼──────┼─────────────┤ │", - "│ │ │ 0 │ nu │ src/main.rs │ │", - "│ │ ╰───┴──────┴─────────────╯ │", - "│ │ ╭───────────────┬───────────────────────────────────────╮ │", - "│ dependencies │ │ │ ╭──────────┬───────────────╮ │ │", - "│ │ │ chrono │ │ │ ╭───┬───────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ serde │ │ │ │", - "│ │ │ │ │ │ ╰───┴───────╯ │ │ │", - "│ │ │ │ │ version │ 0.4.23 │ │ │", - "│ │ │ │ ╰──────────┴───────────────╯ │ │", - "│ │ │ crossterm │ 0.24.0 │ │", - "│ │ │ ctrlc │ 3.2.1 │ │", - "│ │ │ is_executable │ 1.0.1 │ │", - "│ │ │ log │ 0.4 │ │", - "│ │ │ │ ╭──────────┬────────────────────────╮ │ │", - "│ │ │ miette │ │ │ ╭───┬────────────────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ fancy-no-backt │ │ │ │", - "│ │ │ │ │ │ │ │ race │ │ │ │", - "│ │ │ │ │ │ ╰───┴────────────────╯ │ │ │", - "│ │ │ │ │ version │ 5.5.0 │ │ │", - "│ │ │ │ ╰──────────┴────────────────────────╯ │ │", - "│ │ │ nu-ansi-term │ 0.46.0 │ │", - "│ │ │ │ ╭─────────┬─────────────────╮ │ │", - "│ │ │ nu-cli │ │ path │ ./crates/nu-cli │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰─────────┴─────────────────╯ │ │", - "│ │ │ │ ╭────────────┬──────────────────────╮ │ │", - "│ │ │ nu-engine │ │ path │ ./crates/nu-engine │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰────────────┴──────────────────────╯ │ │", - "│ │ │ rayon │ 1.6.1 │ │", - "│ │ │ │ ╭─────────────┬─────────────────────╮ │ │", - "│ │ │ reedline │ │ │ ╭───┬──────────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ bashisms │ │ │ │", - "│ │ │ │ │ │ │ 1 │ sqlite │ │ │ │", - "│ │ │ │ │ │ ╰───┴──────────╯ │ │ │", - "│ │ │ │ │ version │ 0.14.0 │ │ │", - "│ │ │ │ ╰─────────────┴─────────────────────╯ │ │", - "│ │ │ simplelog │ 0.12.0 │ │", - "│ │ │ time │ 0.3.12 │ │", - "│ │ ╰───────────────┴───────────────────────────────────────╯ │", - "│ │ ╭───────────────────┬───────────────────────────────────╮ │", - "│ dev-dependencies │ │ assert_cmd │ 2.0.2 │ │", - "│ │ │ criterion │ 0.4 │ │", - "│ │ │ hamcrest2 │ 0.3.0 │ │", - "│ │ │ itertools │ 0.10.3 │ │", - "│ │ │ │ ╭─────────┬─────────────────────╮ │ │", - "│ │ │ nu-test-support │ │ path │ ./crates/nu-test-su │ │ │", - "│ │ │ │ │ │ pport │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰─────────┴─────────────────────╯ │ │", - "│ │ │ pretty_assertions │ 1.0.0 │ │", - "│ │ │ │ ╭────────────────────┬──────────╮ │ │", - "│ │ │ rstest │ │ default-features │ false │ │ │", - "│ │ │ │ │ version │ 0.15.0 │ │ │", - "│ │ │ │ ╰────────────────────┴──────────╯ │ │", - "│ │ │ serial_test │ 0.10.0 │ │", - "│ │ │ tempfile │ 3.2.0 │ │", - "│ │ ╰───────────────────┴───────────────────────────────────╯ │", - "│ │ ╭─────────────────────┬─────────────────────────────────╮ │", - "│ features │ │ │ ╭───┬───────────────╮ │ │", - "│ │ │ default │ │ 0 │ plugin │ │ │", - "│ │ │ │ │ 1 │ which-support │ │ │", - "│ │ │ │ │ 2 │ trash-support │ │ │", - "│ │ │ │ │ 3 │ sqlite │ │ │", - "│ │ │ │ ╰───┴───────────────╯ │ │", - "│ │ │ │ ╭───┬─────────╮ │ │", - "│ │ │ extra │ │ 0 │ default │ │ │", - "│ │ │ │ ╰───┴─────────╯ │ │", - "│ │ │ │ ╭───┬────────────────────╮ │ │", - "│ │ │ plugin │ │ 0 │ nu-plugin │ │ │", - "│ │ │ │ │ 1 │ nu-cli/plugin │ │ │", - "│ │ │ │ │ 2 │ nu-parser/plugin │ │ │", - "│ │ │ │ │ 3 │ nu-command/plugin │ │ │", - "│ │ │ │ │ 4 │ nu-protocol/plugin │ │ │", - "│ │ │ │ │ 5 │ nu-engine/plugin │ │ │", - "│ │ │ │ ╰───┴────────────────────╯ │ │", - "│ │ │ │ ╭───┬─────────╮ │ │", - "│ │ │ stable │ │ 0 │ default │ │ │", - "│ │ │ │ ╰───┴─────────╯ │ │", - "│ │ │ │ ╭───┬─────────────╮ │ │", - "│ │ │ static-link-openssl │ │ 0 │ dep:openssl │ │ │", - "│ │ │ │ ╰───┴─────────────╯ │ │", - "│ │ │ │ ╭───┬─────────────────────────╮ │ │", - "│ │ │ trash-support │ │ 0 │ nu-command/trash-suppor │ │ │", - "│ │ │ │ │ │ t │ │ │", - "│ │ │ │ ╰───┴─────────────────────────╯ │ │", - "│ │ │ wasi │ [list 0 items] │ │", - "│ │ │ │ ╭───┬─────────────────────────╮ │ │", - "│ │ │ which-support │ │ 0 │ nu-command/which-suppor │ │ │", - "│ │ │ │ │ │ t │ │ │", - "│ │ │ │ ╰───┴─────────────────────────╯ │ │", - "│ │ ╰─────────────────────┴─────────────────────────────────╯ │", "│ │ ╭───────────────┬───────────────────────────────────────╮ │", "│ package │ │ │ ╭───┬───────────────────────────────╮ │ │", "│ │ │ authors │ │ 0 │ The Nushell Project │ │ │", @@ -781,13 +681,13 @@ fn test_expand_big_0() { "│ │ │ │ ╰───┴────────╯ │ │", "│ │ │ homepage │ https://www.nushell.sh │ │", "│ │ │ license │ MIT │ │", + "│ │ │ name │ nu │ │", + "│ │ │ repository │ https://github.com/nushell/nushell │ │", + "│ │ │ rust-version │ 1.60 │ │", + "│ │ │ version │ 0.74.1 │ │", "│ │ │ │ ╭──────────┬────────────────────────╮ │ │", "│ │ │ metadata │ │ │ ╭───────────┬────────╮ │ │ │", - "│ │ │ │ │ binstall │ │ overrides │ {recor │ │ │ │", - "│ │ │ │ │ │ │ │ d 1 │ │ │ │", - "│ │ │ │ │ │ │ │ field} │ │ │ │", - "│ │ │ │ │ │ │ pkg-fmt │ tgz │ │ │ │", - "│ │ │ │ │ │ │ pkg-url │ { repo │ │ │ │", + "│ │ │ │ │ binstall │ │ pkg-url │ { repo │ │ │ │", "│ │ │ │ │ │ │ │ }/rel │ │ │ │", "│ │ │ │ │ │ │ │ eases/ │ │ │ │", "│ │ │ │ │ │ │ │ downlo │ │ │ │", @@ -803,33 +703,14 @@ fn test_expand_big_0() { "│ │ │ │ │ │ │ │ rchive │ │ │ │", "│ │ │ │ │ │ │ │ -forma │ │ │ │", "│ │ │ │ │ │ │ │ t } │ │ │ │", + "│ │ │ │ │ │ │ pkg-fmt │ tgz │ │ │ │", + "│ │ │ │ │ │ │ overrides │ {recor │ │ │ │", + "│ │ │ │ │ │ │ │ d 1 │ │ │ │", + "│ │ │ │ │ │ │ │ field} │ │ │ │", "│ │ │ │ │ │ ╰───────────┴────────╯ │ │ │", "│ │ │ │ ╰──────────┴────────────────────────╯ │ │", - "│ │ │ name │ nu │ │", - "│ │ │ repository │ https://github.com/nushell/nushell │ │", - "│ │ │ rust-version │ 1.60 │ │", - "│ │ │ version │ 0.74.1 │ │", "│ │ ╰───────────────┴───────────────────────────────────────╯ │", "│ │ ╭───────────┬───────────────────────────────────────────╮ │", - "│ patch │ │ │ ╭──────────┬────────────────────────────╮ │ │", - "│ │ │ crates-io │ │ │ ╭────────┬───────────────╮ │ │ │", - "│ │ │ │ │ reedline │ │ branch │ main │ │ │ │", - "│ │ │ │ │ │ │ git │ https://githu │ │ │ │", - "│ │ │ │ │ │ │ │ b.com/nushell │ │ │ │", - "│ │ │ │ │ │ │ │ /reedline.git │ │ │ │", - "│ │ │ │ │ │ ╰────────┴───────────────╯ │ │ │", - "│ │ │ │ ╰──────────┴────────────────────────────╯ │ │", - "│ │ ╰───────────┴───────────────────────────────────────────╯ │", - "│ │ ╭─────────────────────────────────┬─────────────────────╮ │", - "│ target │ │ │ ╭──────────────┬──╮ │ │", - "│ │ │ cfg(not(target_os = \"windows\")) │ │ dependencies │ │ │ │", - "│ │ │ │ ╰──────────────┴──╯ │ │", - "│ │ │ │ ╭──────────────┬──╮ │ │", - "│ │ │ cfg(target_family = \"unix\") │ │ dependencies │ │ │ │", - "│ │ │ │ ╰──────────────┴──╯ │ │", - "│ │ │ cfg(windows) │ {record 1 field} │ │", - "│ │ ╰─────────────────────────────────┴─────────────────────╯ │", - "│ │ ╭───────────┬───────────────────────────────────────────╮ │", "│ workspace │ │ │ ╭────┬────────────────────────────────╮ │ │", "│ │ │ members │ │ 0 │ crates/nu-cli │ │ │", "│ │ │ │ │ 1 │ crates/nu-engine │ │ │", @@ -846,6 +727,125 @@ fn test_expand_big_0() { "│ │ │ │ │ 12 │ crates/nu-utils │ │ │", "│ │ │ │ ╰────┴────────────────────────────────╯ │ │", "│ │ ╰───────────┴───────────────────────────────────────────╯ │", + "│ │ ╭───────────────┬───────────────────────────────────────╮ │", + "│ dependencies │ │ │ ╭──────────┬───────────────╮ │ │", + "│ │ │ chrono │ │ version │ 0.4.23 │ │ │", + "│ │ │ │ │ │ ╭───┬───────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ serde │ │ │ │", + "│ │ │ │ │ │ ╰───┴───────╯ │ │ │", + "│ │ │ │ ╰──────────┴───────────────╯ │ │", + "│ │ │ crossterm │ 0.24.0 │ │", + "│ │ │ ctrlc │ 3.2.1 │ │", + "│ │ │ log │ 0.4 │ │", + "│ │ │ │ ╭──────────┬────────────────────────╮ │ │", + "│ │ │ miette │ │ version │ 5.5.0 │ │ │", + "│ │ │ │ │ │ ╭───┬────────────────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ fancy-no-backt │ │ │ │", + "│ │ │ │ │ │ │ │ race │ │ │ │", + "│ │ │ │ │ │ ╰───┴────────────────╯ │ │ │", + "│ │ │ │ ╰──────────┴────────────────────────╯ │ │", + "│ │ │ nu-ansi-term │ 0.46.0 │ │", + "│ │ │ │ ╭─────────┬─────────────────╮ │ │", + "│ │ │ nu-cli │ │ path │ ./crates/nu-cli │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰─────────┴─────────────────╯ │ │", + "│ │ │ │ ╭────────────┬──────────────────────╮ │ │", + "│ │ │ nu-engine │ │ path │ ./crates/nu-engine │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰────────────┴──────────────────────╯ │ │", + "│ │ │ │ ╭─────────────┬─────────────────────╮ │ │", + "│ │ │ reedline │ │ version │ 0.14.0 │ │ │", + "│ │ │ │ │ │ ╭───┬──────────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ bashisms │ │ │ │", + "│ │ │ │ │ │ │ 1 │ sqlite │ │ │ │", + "│ │ │ │ │ │ ╰───┴──────────╯ │ │ │", + "│ │ │ │ ╰─────────────┴─────────────────────╯ │ │", + "│ │ │ rayon │ 1.6.1 │ │", + "│ │ │ is_executable │ 1.0.1 │ │", + "│ │ │ simplelog │ 0.12.0 │ │", + "│ │ │ time │ 0.3.12 │ │", + "│ │ ╰───────────────┴───────────────────────────────────────╯ │", + "│ │ ╭─────────────────────────────────┬─────────────────────╮ │", + "│ target │ │ │ ╭──────────────┬──╮ │ │", + "│ │ │ cfg(not(target_os = \"windows\")) │ │ dependencies │ │ │ │", + "│ │ │ │ ╰──────────────┴──╯ │ │", + "│ │ │ cfg(windows) │ {record 1 field} │ │", + "│ │ │ │ ╭──────────────┬──╮ │ │", + "│ │ │ cfg(target_family = \"unix\") │ │ dependencies │ │ │ │", + "│ │ │ │ ╰──────────────┴──╯ │ │", + "│ │ ╰─────────────────────────────────┴─────────────────────╯ │", + "│ │ ╭───────────────────┬───────────────────────────────────╮ │", + "│ dev-dependencies │ │ │ ╭─────────┬─────────────────────╮ │ │", + "│ │ │ nu-test-support │ │ path │ ./crates/nu-test-su │ │ │", + "│ │ │ │ │ │ pport │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰─────────┴─────────────────────╯ │ │", + "│ │ │ tempfile │ 3.2.0 │ │", + "│ │ │ assert_cmd │ 2.0.2 │ │", + "│ │ │ criterion │ 0.4 │ │", + "│ │ │ pretty_assertions │ 1.0.0 │ │", + "│ │ │ serial_test │ 0.10.0 │ │", + "│ │ │ hamcrest2 │ 0.3.0 │ │", + "│ │ │ │ ╭────────────────────┬──────────╮ │ │", + "│ │ │ rstest │ │ version │ 0.15.0 │ │ │", + "│ │ │ │ │ default-features │ false │ │ │", + "│ │ │ │ ╰────────────────────┴──────────╯ │ │", + "│ │ │ itertools │ 0.10.3 │ │", + "│ │ ╰───────────────────┴───────────────────────────────────╯ │", + "│ │ ╭─────────────────────┬─────────────────────────────────╮ │", + "│ features │ │ │ ╭───┬────────────────────╮ │ │", + "│ │ │ plugin │ │ 0 │ nu-plugin │ │ │", + "│ │ │ │ │ 1 │ nu-cli/plugin │ │ │", + "│ │ │ │ │ 2 │ nu-parser/plugin │ │ │", + "│ │ │ │ │ 3 │ nu-command/plugin │ │ │", + "│ │ │ │ │ 4 │ nu-protocol/plugin │ │ │", + "│ │ │ │ │ 5 │ nu-engine/plugin │ │ │", + "│ │ │ │ ╰───┴────────────────────╯ │ │", + "│ │ │ │ ╭───┬─────────╮ │ │", + "│ │ │ extra │ │ 0 │ default │ │ │", + "│ │ │ │ ╰───┴─────────╯ │ │", + "│ │ │ │ ╭───┬───────────────╮ │ │", + "│ │ │ default │ │ 0 │ plugin │ │ │", + "│ │ │ │ │ 1 │ which-support │ │ │", + "│ │ │ │ │ 2 │ trash-support │ │ │", + "│ │ │ │ │ 3 │ sqlite │ │ │", + "│ │ │ │ ╰───┴───────────────╯ │ │", + "│ │ │ │ ╭───┬─────────╮ │ │", + "│ │ │ stable │ │ 0 │ default │ │ │", + "│ │ │ │ ╰───┴─────────╯ │ │", + "│ │ │ wasi │ [list 0 items] │ │", + "│ │ │ │ ╭───┬─────────────╮ │ │", + "│ │ │ static-link-openssl │ │ 0 │ dep:openssl │ │ │", + "│ │ │ │ ╰───┴─────────────╯ │ │", + "│ │ │ │ ╭───┬─────────────────────────╮ │ │", + "│ │ │ which-support │ │ 0 │ nu-command/which-suppor │ │ │", + "│ │ │ │ │ │ t │ │ │", + "│ │ │ │ ╰───┴─────────────────────────╯ │ │", + "│ │ │ │ ╭───┬─────────────────────────╮ │ │", + "│ │ │ trash-support │ │ 0 │ nu-command/trash-suppor │ │ │", + "│ │ │ │ │ │ t │ │ │", + "│ │ │ │ ╰───┴─────────────────────────╯ │ │", + "│ │ ╰─────────────────────┴─────────────────────────────────╯ │", + "│ │ ╭───┬──────┬─────────────╮ │", + "│ bin │ │ # │ name │ path │ │", + "│ │ ├───┼──────┼─────────────┤ │", + "│ │ │ 0 │ nu │ src/main.rs │ │", + "│ │ ╰───┴──────┴─────────────╯ │", + "│ │ ╭───────────┬───────────────────────────────────────────╮ │", + "│ patch │ │ │ ╭──────────┬────────────────────────────╮ │ │", + "│ │ │ crates-io │ │ │ ╭────────┬───────────────╮ │ │ │", + "│ │ │ │ │ reedline │ │ git │ https://githu │ │ │ │", + "│ │ │ │ │ │ │ │ b.com/nushell │ │ │ │", + "│ │ │ │ │ │ │ │ /reedline.git │ │ │ │", + "│ │ │ │ │ │ │ branch │ main │ │ │ │", + "│ │ │ │ │ │ ╰────────┴───────────────╯ │ │ │", + "│ │ │ │ ╰──────────┴────────────────────────────╯ │ │", + "│ │ ╰───────────┴───────────────────────────────────────────╯ │", + "│ │ ╭───┬────────────┬─────────╮ │", + "│ bench │ │ # │ name │ harness │ │", + "│ │ ├───┼────────────┼─────────┤ │", + "│ │ │ 0 │ benchmarks │ false │ │", + "│ │ ╰───┴────────────┴─────────╯ │", "╰──────────────────┴───────────────────────────────────────────────────────────╯", ]); @@ -858,102 +858,6 @@ fn test_expand_big_0() { let expected = join_lines([ "╭──────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────╮", - "│ │ ╭───┬─────────┬────────────╮ │", - "│ bench │ │ # │ harness │ name │ │", - "│ │ ├───┼─────────┼────────────┤ │", - "│ │ │ 0 │ false │ benchmarks │ │", - "│ │ ╰───┴─────────┴────────────╯ │", - "│ │ ╭───┬──────┬─────────────╮ │", - "│ bin │ │ # │ name │ path │ │", - "│ │ ├───┼──────┼─────────────┤ │", - "│ │ │ 0 │ nu │ src/main.rs │ │", - "│ │ ╰───┴──────┴─────────────╯ │", - "│ │ ╭───────────────┬───────────────────────────────────────────╮ │", - "│ dependencies │ │ │ ╭──────────┬───────────────╮ │ │", - "│ │ │ chrono │ │ │ ╭───┬───────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ serde │ │ │ │", - "│ │ │ │ │ │ ╰───┴───────╯ │ │ │", - "│ │ │ │ │ version │ 0.4.23 │ │ │", - "│ │ │ │ ╰──────────┴───────────────╯ │ │", - "│ │ │ crossterm │ 0.24.0 │ │", - "│ │ │ ctrlc │ 3.2.1 │ │", - "│ │ │ is_executable │ 1.0.1 │ │", - "│ │ │ log │ 0.4 │ │", - "│ │ │ │ ╭──────────┬────────────────────────────╮ │ │", - "│ │ │ miette │ │ │ ╭───┬────────────────────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ fancy-no-backtrace │ │ │ │", - "│ │ │ │ │ │ ╰───┴────────────────────╯ │ │ │", - "│ │ │ │ │ version │ 5.5.0 │ │ │", - "│ │ │ │ ╰──────────┴────────────────────────────╯ │ │", - "│ │ │ nu-ansi-term │ 0.46.0 │ │", - "│ │ │ │ ╭─────────┬─────────────────╮ │ │", - "│ │ │ nu-cli │ │ path │ ./crates/nu-cli │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰─────────┴─────────────────╯ │ │", - "│ │ │ │ ╭─────────┬────────────────────╮ │ │", - "│ │ │ nu-engine │ │ path │ ./crates/nu-engine │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰─────────┴────────────────────╯ │ │", - "│ │ │ rayon │ 1.6.1 │ │", - "│ │ │ │ ╭──────────┬──────────────────╮ │ │", - "│ │ │ reedline │ │ │ ╭───┬──────────╮ │ │ │", - "│ │ │ │ │ features │ │ 0 │ bashisms │ │ │ │", - "│ │ │ │ │ │ │ 1 │ sqlite │ │ │ │", - "│ │ │ │ │ │ ╰───┴──────────╯ │ │ │", - "│ │ │ │ │ version │ 0.14.0 │ │ │", - "│ │ │ │ ╰──────────┴──────────────────╯ │ │", - "│ │ │ simplelog │ 0.12.0 │ │", - "│ │ │ time │ 0.3.12 │ │", - "│ │ ╰───────────────┴───────────────────────────────────────────╯ │", - "│ │ ╭───────────────────┬────────────────────────────────────────╮ │", - "│ dev-dependencies │ │ assert_cmd │ 2.0.2 │ │", - "│ │ │ criterion │ 0.4 │ │", - "│ │ │ hamcrest2 │ 0.3.0 │ │", - "│ │ │ itertools │ 0.10.3 │ │", - "│ │ │ │ ╭─────────┬──────────────────────────╮ │ │", - "│ │ │ nu-test-support │ │ path │ ./crates/nu-test-support │ │ │", - "│ │ │ │ │ version │ 0.74.1 │ │ │", - "│ │ │ │ ╰─────────┴──────────────────────────╯ │ │", - "│ │ │ pretty_assertions │ 1.0.0 │ │", - "│ │ │ │ ╭──────────────────┬────────╮ │ │", - "│ │ │ rstest │ │ default-features │ false │ │ │", - "│ │ │ │ │ version │ 0.15.0 │ │ │", - "│ │ │ │ ╰──────────────────┴────────╯ │ │", - "│ │ │ serial_test │ 0.10.0 │ │", - "│ │ │ tempfile │ 3.2.0 │ │", - "│ │ ╰───────────────────┴────────────────────────────────────────╯ │", - "│ │ ╭─────────────────────┬──────────────────────────────────╮ │", - "│ features │ │ │ ╭───┬───────────────╮ │ │", - "│ │ │ default │ │ 0 │ plugin │ │ │", - "│ │ │ │ │ 1 │ which-support │ │ │", - "│ │ │ │ │ 2 │ trash-support │ │ │", - "│ │ │ │ │ 3 │ sqlite │ │ │", - "│ │ │ │ ╰───┴───────────────╯ │ │", - "│ │ │ │ ╭───┬─────────╮ │ │", - "│ │ │ extra │ │ 0 │ default │ │ │", - "│ │ │ │ ╰───┴─────────╯ │ │", - "│ │ │ │ ╭───┬────────────────────╮ │ │", - "│ │ │ plugin │ │ 0 │ nu-plugin │ │ │", - "│ │ │ │ │ 1 │ nu-cli/plugin │ │ │", - "│ │ │ │ │ 2 │ nu-parser/plugin │ │ │", - "│ │ │ │ │ 3 │ nu-command/plugin │ │ │", - "│ │ │ │ │ 4 │ nu-protocol/plugin │ │ │", - "│ │ │ │ │ 5 │ nu-engine/plugin │ │ │", - "│ │ │ │ ╰───┴────────────────────╯ │ │", - "│ │ │ │ ╭───┬─────────╮ │ │", - "│ │ │ stable │ │ 0 │ default │ │ │", - "│ │ │ │ ╰───┴─────────╯ │ │", - "│ │ │ │ ╭───┬─────────────╮ │ │", - "│ │ │ static-link-openssl │ │ 0 │ dep:openssl │ │ │", - "│ │ │ │ ╰───┴─────────────╯ │ │", - "│ │ │ │ ╭───┬──────────────────────────╮ │ │", - "│ │ │ trash-support │ │ 0 │ nu-command/trash-support │ │ │", - "│ │ │ │ ╰───┴──────────────────────────╯ │ │", - "│ │ │ wasi │ [list 0 items] │ │", - "│ │ │ │ ╭───┬──────────────────────────╮ │ │", - "│ │ │ which-support │ │ 0 │ nu-command/which-support │ │ │", - "│ │ │ │ ╰───┴──────────────────────────╯ │ │", - "│ │ ╰─────────────────────┴──────────────────────────────────╯ │", "│ │ ╭───────────────┬───────────────────────────────────────────────────────────────────────────────╮ │", "│ package │ │ │ ╭───┬────────────────────────────────╮ │ │", "│ │ │ authors │ │ 0 │ The Nushell Project Developers │ │ │", @@ -967,61 +871,23 @@ fn test_expand_big_0() { "│ │ │ │ ╰───┴────────╯ │ │", "│ │ │ homepage │ https://www.nushell.sh │ │", "│ │ │ license │ MIT │ │", - "│ │ │ │ ╭──────────┬────────────────────────────────────────────────────────────────╮ │ │", - "│ │ │ metadata │ │ │ ╭───────────┬────────────────────────────────────────────────╮ │ │ │", - "│ │ │ │ │ binstall │ │ │ ╭────────────────────────┬───────────────────╮ │ │ │ │", - "│ │ │ │ │ │ │ overrides │ │ │ ╭─────────┬─────╮ │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ x86_64-pc-windows-msvc │ │ pkg-fmt │ zip │ │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ │ ╰─────────┴─────╯ │ │ │ │ │", - "│ │ │ │ │ │ │ │ ╰────────────────────────┴───────────────────╯ │ │ │ │", - "│ │ │ │ │ │ │ pkg-fmt │ tgz │ │ │ │", - "│ │ │ │ │ │ │ pkg-url │ { repo }/releases/download/{ version }/{ name │ │ │ │", - "│ │ │ │ │ │ │ │ }-{ version }-{ target }.{ archive-format } │ │ │ │", - "│ │ │ │ │ │ ╰───────────┴────────────────────────────────────────────────╯ │ │ │", - "│ │ │ │ ╰──────────┴────────────────────────────────────────────────────────────────╯ │ │", "│ │ │ name │ nu │ │", "│ │ │ repository │ https://github.com/nushell/nushell │ │", "│ │ │ rust-version │ 1.60 │ │", "│ │ │ version │ 0.74.1 │ │", + "│ │ │ │ ╭──────────┬────────────────────────────────────────────────────────────────╮ │ │", + "│ │ │ metadata │ │ │ ╭───────────┬────────────────────────────────────────────────╮ │ │ │", + "│ │ │ │ │ binstall │ │ pkg-url │ { repo }/releases/download/{ version }/{ name │ │ │ │", + "│ │ │ │ │ │ │ │ }-{ version }-{ target }.{ archive-format } │ │ │ │", + "│ │ │ │ │ │ │ pkg-fmt │ tgz │ │ │ │", + "│ │ │ │ │ │ │ │ ╭────────────────────────┬───────────────────╮ │ │ │ │", + "│ │ │ │ │ │ │ overrides │ │ │ ╭─────────┬─────╮ │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ x86_64-pc-windows-msvc │ │ pkg-fmt │ zip │ │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ │ ╰─────────┴─────╯ │ │ │ │ │", + "│ │ │ │ │ │ │ │ ╰────────────────────────┴───────────────────╯ │ │ │ │", + "│ │ │ │ │ │ ╰───────────┴────────────────────────────────────────────────╯ │ │ │", + "│ │ │ │ ╰──────────┴────────────────────────────────────────────────────────────────╯ │ │", "│ │ ╰───────────────┴───────────────────────────────────────────────────────────────────────────────╯ │", - "│ │ ╭───────────┬───────────────────────────────────────────────────────────────────────────────────╮ │", - "│ patch │ │ │ ╭─────────────────┬─────────────────────────────────────────────────────────────╮ │ │", - "│ │ │ crates-io │ │ │ ╭────────┬─────────────────────────────────────────╮ │ │ │", - "│ │ │ │ │ reedline │ │ branch │ main │ │ │ │", - "│ │ │ │ │ │ │ git │ https://github.com/nushell/reedline.git │ │ │ │", - "│ │ │ │ │ │ ╰────────┴─────────────────────────────────────────╯ │ │ │", - "│ │ │ │ ╰─────────────────┴─────────────────────────────────────────────────────────────╯ │ │", - "│ │ ╰───────────┴───────────────────────────────────────────────────────────────────────────────────╯ │", - "│ │ ╭─────────────────────────────────┬─────────────────────────────────────────────────────────────╮ │", - "│ target │ │ │ ╭──────────────┬──────────────────────────────────────────╮ │ │", - "│ │ │ cfg(not(target_os = \"windows\")) │ │ │ ╭─────────────┬────────────────────────╮ │ │ │", - "│ │ │ │ │ dependencies │ │ │ ╭──────────┬─────────╮ │ │ │ │", - "│ │ │ │ │ │ │ openssl │ │ features │ [list 1 │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ │ item] │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ optional │ true │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ version │ 0.10.38 │ │ │ │ │", - "│ │ │ │ │ │ │ │ ╰──────────┴─────────╯ │ │ │ │", - "│ │ │ │ │ │ │ signal-hook │ {record 2 fields} │ │ │ │", - "│ │ │ │ │ │ ╰─────────────┴────────────────────────╯ │ │ │", - "│ │ │ │ ╰──────────────┴──────────────────────────────────────────╯ │ │", - "│ │ │ │ ╭──────────────┬──────────────────────────────────────────╮ │ │", - "│ │ │ cfg(target_family = \"unix\") │ │ │ ╭──────┬───────────────────────────────╮ │ │ │", - "│ │ │ │ │ dependencies │ │ atty │ 0.2 │ │ │ │", - "│ │ │ │ │ │ │ │ ╭──────────────────┬────────╮ │ │ │ │", - "│ │ │ │ │ │ │ nix │ │ default-features │ false │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ features │ [list │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ │ 4 │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ │ items] │ │ │ │ │", - "│ │ │ │ │ │ │ │ │ version │ 0.25 │ │ │ │ │", - "│ │ │ │ │ │ │ │ ╰──────────────────┴────────╯ │ │ │ │", - "│ │ │ │ │ │ ╰──────┴───────────────────────────────╯ │ │ │", - "│ │ │ │ ╰──────────────┴──────────────────────────────────────────╯ │ │", - "│ │ │ │ ╭────────────────────┬──────────────────╮ │ │", - "│ │ │ cfg(windows) │ │ │ ╭────────┬─────╮ │ │ │", - "│ │ │ │ │ build-dependencies │ │ winres │ 0.1 │ │ │ │", - "│ │ │ │ │ │ ╰────────┴─────╯ │ │ │", - "│ │ │ │ ╰────────────────────┴──────────────────╯ │ │", - "│ │ ╰─────────────────────────────────┴─────────────────────────────────────────────────────────────╯ │", "│ │ ╭─────────┬─────────────────────────────────────────╮ │", "│ workspace │ │ │ ╭────┬────────────────────────────────╮ │ │", "│ │ │ members │ │ 0 │ crates/nu-cli │ │ │", @@ -1039,6 +905,140 @@ fn test_expand_big_0() { "│ │ │ │ │ 12 │ crates/nu-utils │ │ │", "│ │ │ │ ╰────┴────────────────────────────────╯ │ │", "│ │ ╰─────────┴─────────────────────────────────────────╯ │", + "│ │ ╭───────────────┬───────────────────────────────────────────╮ │", + "│ dependencies │ │ │ ╭──────────┬───────────────╮ │ │", + "│ │ │ chrono │ │ version │ 0.4.23 │ │ │", + "│ │ │ │ │ │ ╭───┬───────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ serde │ │ │ │", + "│ │ │ │ │ │ ╰───┴───────╯ │ │ │", + "│ │ │ │ ╰──────────┴───────────────╯ │ │", + "│ │ │ crossterm │ 0.24.0 │ │", + "│ │ │ ctrlc │ 3.2.1 │ │", + "│ │ │ log │ 0.4 │ │", + "│ │ │ │ ╭──────────┬────────────────────────────╮ │ │", + "│ │ │ miette │ │ version │ 5.5.0 │ │ │", + "│ │ │ │ │ │ ╭───┬────────────────────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ fancy-no-backtrace │ │ │ │", + "│ │ │ │ │ │ ╰───┴────────────────────╯ │ │ │", + "│ │ │ │ ╰──────────┴────────────────────────────╯ │ │", + "│ │ │ nu-ansi-term │ 0.46.0 │ │", + "│ │ │ │ ╭─────────┬─────────────────╮ │ │", + "│ │ │ nu-cli │ │ path │ ./crates/nu-cli │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰─────────┴─────────────────╯ │ │", + "│ │ │ │ ╭─────────┬────────────────────╮ │ │", + "│ │ │ nu-engine │ │ path │ ./crates/nu-engine │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰─────────┴────────────────────╯ │ │", + "│ │ │ │ ╭──────────┬──────────────────╮ │ │", + "│ │ │ reedline │ │ version │ 0.14.0 │ │ │", + "│ │ │ │ │ │ ╭───┬──────────╮ │ │ │", + "│ │ │ │ │ features │ │ 0 │ bashisms │ │ │ │", + "│ │ │ │ │ │ │ 1 │ sqlite │ │ │ │", + "│ │ │ │ │ │ ╰───┴──────────╯ │ │ │", + "│ │ │ │ ╰──────────┴──────────────────╯ │ │", + "│ │ │ rayon │ 1.6.1 │ │", + "│ │ │ is_executable │ 1.0.1 │ │", + "│ │ │ simplelog │ 0.12.0 │ │", + "│ │ │ time │ 0.3.12 │ │", + "│ │ ╰───────────────┴───────────────────────────────────────────╯ │", + "│ │ ╭─────────────────────────────────┬─────────────────────────────────────────────────────────────╮ │", + "│ target │ │ │ ╭──────────────┬──────────────────────────────────────────╮ │ │", + "│ │ │ cfg(not(target_os = \"windows\")) │ │ │ ╭─────────────┬────────────────────────╮ │ │ │", + "│ │ │ │ │ dependencies │ │ │ ╭──────────┬─────────╮ │ │ │ │", + "│ │ │ │ │ │ │ openssl │ │ version │ 0.10.38 │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ features │ [list 1 │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ │ item] │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ optional │ true │ │ │ │ │", + "│ │ │ │ │ │ │ │ ╰──────────┴─────────╯ │ │ │ │", + "│ │ │ │ │ │ │ signal-hook │ {record 2 fields} │ │ │ │", + "│ │ │ │ │ │ ╰─────────────┴────────────────────────╯ │ │ │", + "│ │ │ │ ╰──────────────┴──────────────────────────────────────────╯ │ │", + "│ │ │ │ ╭────────────────────┬──────────────────╮ │ │", + "│ │ │ cfg(windows) │ │ │ ╭────────┬─────╮ │ │ │", + "│ │ │ │ │ build-dependencies │ │ winres │ 0.1 │ │ │ │", + "│ │ │ │ │ │ ╰────────┴─────╯ │ │ │", + "│ │ │ │ ╰────────────────────┴──────────────────╯ │ │", + "│ │ │ │ ╭──────────────┬──────────────────────────────────────────╮ │ │", + "│ │ │ cfg(target_family = \"unix\") │ │ │ ╭──────┬───────────────────────────────╮ │ │ │", + "│ │ │ │ │ dependencies │ │ │ ╭──────────────────┬────────╮ │ │ │ │", + "│ │ │ │ │ │ │ nix │ │ version │ 0.25 │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ default-features │ false │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ features │ [list │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ │ 4 │ │ │ │ │", + "│ │ │ │ │ │ │ │ │ │ items] │ │ │ │ │", + "│ │ │ │ │ │ │ │ ╰──────────────────┴────────╯ │ │ │ │", + "│ │ │ │ │ │ │ atty │ 0.2 │ │ │ │", + "│ │ │ │ │ │ ╰──────┴───────────────────────────────╯ │ │ │", + "│ │ │ │ ╰──────────────┴──────────────────────────────────────────╯ │ │", + "│ │ ╰─────────────────────────────────┴─────────────────────────────────────────────────────────────╯ │", + "│ │ ╭───────────────────┬────────────────────────────────────────╮ │", + "│ dev-dependencies │ │ │ ╭─────────┬──────────────────────────╮ │ │", + "│ │ │ nu-test-support │ │ path │ ./crates/nu-test-support │ │ │", + "│ │ │ │ │ version │ 0.74.1 │ │ │", + "│ │ │ │ ╰─────────┴──────────────────────────╯ │ │", + "│ │ │ tempfile │ 3.2.0 │ │", + "│ │ │ assert_cmd │ 2.0.2 │ │", + "│ │ │ criterion │ 0.4 │ │", + "│ │ │ pretty_assertions │ 1.0.0 │ │", + "│ │ │ serial_test │ 0.10.0 │ │", + "│ │ │ hamcrest2 │ 0.3.0 │ │", + "│ │ │ │ ╭──────────────────┬────────╮ │ │", + "│ │ │ rstest │ │ version │ 0.15.0 │ │ │", + "│ │ │ │ │ default-features │ false │ │ │", + "│ │ │ │ ╰──────────────────┴────────╯ │ │", + "│ │ │ itertools │ 0.10.3 │ │", + "│ │ ╰───────────────────┴────────────────────────────────────────╯ │", + "│ │ ╭─────────────────────┬──────────────────────────────────╮ │", + "│ features │ │ │ ╭───┬────────────────────╮ │ │", + "│ │ │ plugin │ │ 0 │ nu-plugin │ │ │", + "│ │ │ │ │ 1 │ nu-cli/plugin │ │ │", + "│ │ │ │ │ 2 │ nu-parser/plugin │ │ │", + "│ │ │ │ │ 3 │ nu-command/plugin │ │ │", + "│ │ │ │ │ 4 │ nu-protocol/plugin │ │ │", + "│ │ │ │ │ 5 │ nu-engine/plugin │ │ │", + "│ │ │ │ ╰───┴────────────────────╯ │ │", + "│ │ │ │ ╭───┬─────────╮ │ │", + "│ │ │ extra │ │ 0 │ default │ │ │", + "│ │ │ │ ╰───┴─────────╯ │ │", + "│ │ │ │ ╭───┬───────────────╮ │ │", + "│ │ │ default │ │ 0 │ plugin │ │ │", + "│ │ │ │ │ 1 │ which-support │ │ │", + "│ │ │ │ │ 2 │ trash-support │ │ │", + "│ │ │ │ │ 3 │ sqlite │ │ │", + "│ │ │ │ ╰───┴───────────────╯ │ │", + "│ │ │ │ ╭───┬─────────╮ │ │", + "│ │ │ stable │ │ 0 │ default │ │ │", + "│ │ │ │ ╰───┴─────────╯ │ │", + "│ │ │ wasi │ [list 0 items] │ │", + "│ │ │ │ ╭───┬─────────────╮ │ │", + "│ │ │ static-link-openssl │ │ 0 │ dep:openssl │ │ │", + "│ │ │ │ ╰───┴─────────────╯ │ │", + "│ │ │ │ ╭───┬──────────────────────────╮ │ │", + "│ │ │ which-support │ │ 0 │ nu-command/which-support │ │ │", + "│ │ │ │ ╰───┴──────────────────────────╯ │ │", + "│ │ │ │ ╭───┬──────────────────────────╮ │ │", + "│ │ │ trash-support │ │ 0 │ nu-command/trash-support │ │ │", + "│ │ │ │ ╰───┴──────────────────────────╯ │ │", + "│ │ ╰─────────────────────┴──────────────────────────────────╯ │", + "│ │ ╭───┬──────┬─────────────╮ │", + "│ bin │ │ # │ name │ path │ │", + "│ │ ├───┼──────┼─────────────┤ │", + "│ │ │ 0 │ nu │ src/main.rs │ │", + "│ │ ╰───┴──────┴─────────────╯ │", + "│ │ ╭───────────┬───────────────────────────────────────────────────────────────────────────────────╮ │", + "│ patch │ │ │ ╭─────────────────┬─────────────────────────────────────────────────────────────╮ │ │", + "│ │ │ crates-io │ │ │ ╭────────┬─────────────────────────────────────────╮ │ │ │", + "│ │ │ │ │ reedline │ │ git │ https://github.com/nushell/reedline.git │ │ │ │", + "│ │ │ │ │ │ │ branch │ main │ │ │ │", + "│ │ │ │ │ │ ╰────────┴─────────────────────────────────────────╯ │ │ │", + "│ │ │ │ ╰─────────────────┴─────────────────────────────────────────────────────────────╯ │ │", + "│ │ ╰───────────┴───────────────────────────────────────────────────────────────────────────────────╯ │", + "│ │ ╭───┬────────────┬─────────╮ │", + "│ bench │ │ # │ name │ harness │ │", + "│ │ ├───┼────────────┼─────────┤ │", + "│ │ │ 0 │ benchmarks │ false │ │", + "│ │ ╰───┴────────────┴─────────╯ │", "╰──────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────╯", ]); @@ -1053,44 +1053,99 @@ fn test_expand_big_0() { let expected = join_lines([ "╭──────────────────┬───────────────────────────────────────╮", - "│ │ ╭───┬─────────┬────────────╮ │", - "│ bench │ │ # │ harness │ name │ │", - "│ │ ├───┼─────────┼────────────┤ │", - "│ │ │ 0 │ false │ benchmarks │ │", - "│ │ ╰───┴─────────┴────────────╯ │", - "│ │ ╭───┬──────┬─────────────╮ │", - "│ bin │ │ # │ name │ path │ │", - "│ │ ├───┼──────┼─────────────┤ │", - "│ │ │ 0 │ nu │ src/main.rs │ │", - "│ │ ╰───┴──────┴─────────────╯ │", + "│ │ ╭───────────────┬───────────────────╮ │", + "│ package │ │ │ ╭───┬───────────╮ │ │", + "│ │ │ authors │ │ 0 │ The │ │ │", + "│ │ │ │ │ │ Nushell │ │ │", + "│ │ │ │ │ │ Project D │ │ │", + "│ │ │ │ │ │ evelopers │ │ │", + "│ │ │ │ ╰───┴───────────╯ │ │", + "│ │ │ default-run │ nu │ │", + "│ │ │ description │ A new type of │ │", + "│ │ │ │ shell │ │", + "│ │ │ documentation │ https://www.nushe │ │", + "│ │ │ │ ll.sh/book/ │ │", + "│ │ │ edition │ 2021 │ │", + "│ │ │ │ ╭───┬────────╮ │ │", + "│ │ │ exclude │ │ 0 │ images │ │ │", + "│ │ │ │ ╰───┴────────╯ │ │", + "│ │ │ homepage │ https://www.nushe │ │", + "│ │ │ │ ll.sh │ │", + "│ │ │ license │ MIT │ │", + "│ │ │ name │ nu │ │", + "│ │ │ repository │ https://github.co │ │", + "│ │ │ │ m/nushell/nushell │ │", + "│ │ │ rust-version │ 1.60 │ │", + "│ │ │ version │ 0.74.1 │ │", + "│ │ │ │ ╭──────────┬────╮ │ │", + "│ │ │ metadata │ │ binstall │ {r │ │ │", + "│ │ │ │ │ │ ec │ │ │", + "│ │ │ │ │ │ or │ │ │", + "│ │ │ │ │ │ d │ │ │", + "│ │ │ │ │ │ 3 │ │ │", + "│ │ │ │ │ │ fi │ │ │", + "│ │ │ │ │ │ el │ │ │", + "│ │ │ │ │ │ ds │ │ │", + "│ │ │ │ │ │ } │ │ │", + "│ │ │ │ ╰──────────┴────╯ │ │", + "│ │ ╰───────────────┴───────────────────╯ │", + "│ │ ╭─────────┬─────────────────────────╮ │", + "│ workspace │ │ │ ╭────┬────────────────╮ │ │", + "│ │ │ members │ │ 0 │ crates/nu-cli │ │ │", + "│ │ │ │ │ 1 │ crates/nu-engi │ │ │", + "│ │ │ │ │ │ ne │ │ │", + "│ │ │ │ │ 2 │ crates/nu-pars │ │ │", + "│ │ │ │ │ │ er │ │ │", + "│ │ │ │ │ 3 │ crates/nu-syst │ │ │", + "│ │ │ │ │ │ em │ │ │", + "│ │ │ │ │ 4 │ crates/nu-comm │ │ │", + "│ │ │ │ │ │ and │ │ │", + "│ │ │ │ │ 5 │ crates/nu-prot │ │ │", + "│ │ │ │ │ │ ocol │ │ │", + "│ │ │ │ │ 6 │ crates/nu-plug │ │ │", + "│ │ │ │ │ │ in │ │ │", + "│ │ │ │ │ 7 │ crates/nu_plug │ │ │", + "│ │ │ │ │ │ in_inc │ │ │", + "│ │ │ │ │ 8 │ crates/nu_plug │ │ │", + "│ │ │ │ │ │ in_gstat │ │ │", + "│ │ │ │ │ 9 │ crates/nu_plug │ │ │", + "│ │ │ │ │ │ in_example │ │ │", + "│ │ │ │ │ 10 │ crates/nu_plug │ │ │", + "│ │ │ │ │ │ in_query │ │ │", + "│ │ │ │ │ 11 │ crates/nu_plug │ │ │", + "│ │ │ │ │ │ in_custom_valu │ │ │", + "│ │ │ │ │ │ es │ │ │", + "│ │ │ │ │ 12 │ crates/nu-util │ │ │", + "│ │ │ │ │ │ s │ │ │", + "│ │ │ │ ╰────┴────────────────╯ │ │", + "│ │ ╰─────────┴─────────────────────────╯ │", "│ │ ╭───────────────┬───────────────────╮ │", "│ dependencies │ │ │ ╭──────────┬────╮ │ │", - "│ │ │ chrono │ │ features │ [l │ │ │", + "│ │ │ chrono │ │ version │ 0. │ │ │", + "│ │ │ │ │ │ 4. │ │ │", + "│ │ │ │ │ │ 23 │ │ │", + "│ │ │ │ │ features │ [l │ │ │", "│ │ │ │ │ │ is │ │ │", "│ │ │ │ │ │ t │ │ │", "│ │ │ │ │ │ 1 │ │ │", "│ │ │ │ │ │ it │ │ │", "│ │ │ │ │ │ em │ │ │", "│ │ │ │ │ │ ] │ │ │", - "│ │ │ │ │ version │ 0. │ │ │", - "│ │ │ │ │ │ 4. │ │ │", - "│ │ │ │ │ │ 23 │ │ │", "│ │ │ │ ╰──────────┴────╯ │ │", "│ │ │ crossterm │ 0.24.0 │ │", "│ │ │ ctrlc │ 3.2.1 │ │", - "│ │ │ is_executable │ 1.0.1 │ │", "│ │ │ log │ 0.4 │ │", "│ │ │ │ ╭──────────┬────╮ │ │", - "│ │ │ miette │ │ features │ [l │ │ │", + "│ │ │ miette │ │ version │ 5. │ │ │", + "│ │ │ │ │ │ 5. │ │ │", + "│ │ │ │ │ │ 0 │ │ │", + "│ │ │ │ │ features │ [l │ │ │", "│ │ │ │ │ │ is │ │ │", "│ │ │ │ │ │ t │ │ │", "│ │ │ │ │ │ 1 │ │ │", "│ │ │ │ │ │ it │ │ │", "│ │ │ │ │ │ em │ │ │", "│ │ │ │ │ │ ] │ │ │", - "│ │ │ │ │ version │ 5. │ │ │", - "│ │ │ │ │ │ 5. │ │ │", - "│ │ │ │ │ │ 0 │ │ │", "│ │ │ │ ╰──────────┴────╯ │ │", "│ │ │ nu-ansi-term │ 0.46.0 │ │", "│ │ │ │ ╭─────────┬─────╮ │ │", @@ -1112,58 +1167,39 @@ fn test_expand_big_0() { "│ │ │ │ │ version │ 0.7 │ │ │", "│ │ │ │ │ │ 4.1 │ │ │", "│ │ │ │ ╰─────────┴─────╯ │ │", - "│ │ │ rayon │ 1.6.1 │ │", "│ │ │ │ ╭──────────┬────╮ │ │", - "│ │ │ reedline │ │ features │ [l │ │ │", + "│ │ │ reedline │ │ version │ 0. │ │ │", + "│ │ │ │ │ │ 14 │ │ │", + "│ │ │ │ │ │ .0 │ │ │", + "│ │ │ │ │ features │ [l │ │ │", "│ │ │ │ │ │ is │ │ │", "│ │ │ │ │ │ t │ │ │", "│ │ │ │ │ │ 2 │ │ │", "│ │ │ │ │ │ it │ │ │", "│ │ │ │ │ │ em │ │ │", "│ │ │ │ │ │ s] │ │ │", - "│ │ │ │ │ version │ 0. │ │ │", - "│ │ │ │ │ │ 14 │ │ │", - "│ │ │ │ │ │ .0 │ │ │", "│ │ │ │ ╰──────────┴────╯ │ │", + "│ │ │ rayon │ 1.6.1 │ │", + "│ │ │ is_executable │ 1.0.1 │ │", "│ │ │ simplelog │ 0.12.0 │ │", "│ │ │ time │ 0.3.12 │ │", "│ │ ╰───────────────┴───────────────────╯ │", + "│ target │ {record 3 fields} │", "│ │ ╭───────────────────┬───────────────╮ │", - "│ dev-dependencies │ │ assert_cmd │ 2.0.2 │ │", - "│ │ │ criterion │ 0.4 │ │", - "│ │ │ hamcrest2 │ 0.3.0 │ │", - "│ │ │ itertools │ 0.10.3 │ │", - "│ │ │ nu-test-support │ {record 2 │ │", + "│ dev-dependencies │ │ nu-test-support │ {record 2 │ │", "│ │ │ │ fields} │ │", + "│ │ │ tempfile │ 3.2.0 │ │", + "│ │ │ assert_cmd │ 2.0.2 │ │", + "│ │ │ criterion │ 0.4 │ │", "│ │ │ pretty_assertions │ 1.0.0 │ │", + "│ │ │ serial_test │ 0.10.0 │ │", + "│ │ │ hamcrest2 │ 0.3.0 │ │", "│ │ │ rstest │ {record 2 │ │", "│ │ │ │ fields} │ │", - "│ │ │ serial_test │ 0.10.0 │ │", - "│ │ │ tempfile │ 3.2.0 │ │", + "│ │ │ itertools │ 0.10.3 │ │", "│ │ ╰───────────────────┴───────────────╯ │", "│ │ ╭─────────────────────┬─────────────╮ │", "│ features │ │ │ ╭───┬─────╮ │ │", - "│ │ │ default │ │ 0 │ plu │ │ │", - "│ │ │ │ │ │ gin │ │ │", - "│ │ │ │ │ 1 │ whi │ │ │", - "│ │ │ │ │ │ ch- │ │ │", - "│ │ │ │ │ │ sup │ │ │", - "│ │ │ │ │ │ por │ │ │", - "│ │ │ │ │ │ t │ │ │", - "│ │ │ │ │ 2 │ tra │ │ │", - "│ │ │ │ │ │ sh- │ │ │", - "│ │ │ │ │ │ sup │ │ │", - "│ │ │ │ │ │ por │ │ │", - "│ │ │ │ │ │ t │ │ │", - "│ │ │ │ │ 3 │ sql │ │ │", - "│ │ │ │ │ │ ite │ │ │", - "│ │ │ │ ╰───┴─────╯ │ │", - "│ │ │ │ ╭───┬─────╮ │ │", - "│ │ │ extra │ │ 0 │ def │ │ │", - "│ │ │ │ │ │ aul │ │ │", - "│ │ │ │ │ │ t │ │ │", - "│ │ │ │ ╰───┴─────╯ │ │", - "│ │ │ │ ╭───┬─────╮ │ │", "│ │ │ plugin │ │ 0 │ nu- │ │ │", "│ │ │ │ │ │ plu │ │ │", "│ │ │ │ │ │ gin │ │ │", @@ -1198,10 +1234,33 @@ fn test_expand_big_0() { "│ │ │ │ │ │ n │ │ │", "│ │ │ │ ╰───┴─────╯ │ │", "│ │ │ │ ╭───┬─────╮ │ │", + "│ │ │ extra │ │ 0 │ def │ │ │", + "│ │ │ │ │ │ aul │ │ │", + "│ │ │ │ │ │ t │ │ │", + "│ │ │ │ ╰───┴─────╯ │ │", + "│ │ │ │ ╭───┬─────╮ │ │", + "│ │ │ default │ │ 0 │ plu │ │ │", + "│ │ │ │ │ │ gin │ │ │", + "│ │ │ │ │ 1 │ whi │ │ │", + "│ │ │ │ │ │ ch- │ │ │", + "│ │ │ │ │ │ sup │ │ │", + "│ │ │ │ │ │ por │ │ │", + "│ │ │ │ │ │ t │ │ │", + "│ │ │ │ │ 2 │ tra │ │ │", + "│ │ │ │ │ │ sh- │ │ │", + "│ │ │ │ │ │ sup │ │ │", + "│ │ │ │ │ │ por │ │ │", + "│ │ │ │ │ │ t │ │ │", + "│ │ │ │ │ 3 │ sql │ │ │", + "│ │ │ │ │ │ ite │ │ │", + "│ │ │ │ ╰───┴─────╯ │ │", + "│ │ │ │ ╭───┬─────╮ │ │", "│ │ │ stable │ │ 0 │ def │ │ │", "│ │ │ │ │ │ aul │ │ │", "│ │ │ │ │ │ t │ │ │", "│ │ │ │ ╰───┴─────╯ │ │", + "│ │ │ wasi │ [list 0 │ │", + "│ │ │ │ items] │ │", "│ │ │ │ ╭───┬─────╮ │ │", "│ │ │ static-link-openssl │ │ 0 │ dep │ │ │", "│ │ │ │ │ │ :op │ │ │", @@ -1209,6 +1268,16 @@ fn test_expand_big_0() { "│ │ │ │ │ │ sl │ │ │", "│ │ │ │ ╰───┴─────╯ │ │", "│ │ │ │ ╭───┬─────╮ │ │", + "│ │ │ which-support │ │ 0 │ nu- │ │ │", + "│ │ │ │ │ │ com │ │ │", + "│ │ │ │ │ │ man │ │ │", + "│ │ │ │ │ │ d/w │ │ │", + "│ │ │ │ │ │ hic │ │ │", + "│ │ │ │ │ │ h-s │ │ │", + "│ │ │ │ │ │ upp │ │ │", + "│ │ │ │ │ │ ort │ │ │", + "│ │ │ │ ╰───┴─────╯ │ │", + "│ │ │ │ ╭───┬─────╮ │ │", "│ │ │ trash-support │ │ 0 │ nu- │ │ │", "│ │ │ │ │ │ com │ │ │", "│ │ │ │ │ │ man │ │ │", @@ -1218,55 +1287,12 @@ fn test_expand_big_0() { "│ │ │ │ │ │ upp │ │ │", "│ │ │ │ │ │ ort │ │ │", "│ │ │ │ ╰───┴─────╯ │ │", - "│ │ │ wasi │ [list 0 │ │", - "│ │ │ │ items] │ │", - "│ │ │ │ ╭───┬─────╮ │ │", - "│ │ │ which-support │ │ 0 │ nu- │ │ │", - "│ │ │ │ │ │ com │ │ │", - "│ │ │ │ │ │ man │ │ │", - "│ │ │ │ │ │ d/w │ │ │", - "│ │ │ │ │ │ hic │ │ │", - "│ │ │ │ │ │ h-s │ │ │", - "│ │ │ │ │ │ upp │ │ │", - "│ │ │ │ │ │ ort │ │ │", - "│ │ │ │ ╰───┴─────╯ │ │", "│ │ ╰─────────────────────┴─────────────╯ │", - "│ │ ╭───────────────┬───────────────────╮ │", - "│ package │ │ │ ╭───┬───────────╮ │ │", - "│ │ │ authors │ │ 0 │ The │ │ │", - "│ │ │ │ │ │ Nushell │ │ │", - "│ │ │ │ │ │ Project D │ │ │", - "│ │ │ │ │ │ evelopers │ │ │", - "│ │ │ │ ╰───┴───────────╯ │ │", - "│ │ │ default-run │ nu │ │", - "│ │ │ description │ A new type of │ │", - "│ │ │ │ shell │ │", - "│ │ │ documentation │ https://www.nushe │ │", - "│ │ │ │ ll.sh/book/ │ │", - "│ │ │ edition │ 2021 │ │", - "│ │ │ │ ╭───┬────────╮ │ │", - "│ │ │ exclude │ │ 0 │ images │ │ │", - "│ │ │ │ ╰───┴────────╯ │ │", - "│ │ │ homepage │ https://www.nushe │ │", - "│ │ │ │ ll.sh │ │", - "│ │ │ license │ MIT │ │", - "│ │ │ │ ╭──────────┬────╮ │ │", - "│ │ │ metadata │ │ binstall │ {r │ │ │", - "│ │ │ │ │ │ ec │ │ │", - "│ │ │ │ │ │ or │ │ │", - "│ │ │ │ │ │ d │ │ │", - "│ │ │ │ │ │ 3 │ │ │", - "│ │ │ │ │ │ fi │ │ │", - "│ │ │ │ │ │ el │ │ │", - "│ │ │ │ │ │ ds │ │ │", - "│ │ │ │ │ │ } │ │ │", - "│ │ │ │ ╰──────────┴────╯ │ │", - "│ │ │ name │ nu │ │", - "│ │ │ repository │ https://github.co │ │", - "│ │ │ │ m/nushell/nushell │ │", - "│ │ │ rust-version │ 1.60 │ │", - "│ │ │ version │ 0.74.1 │ │", - "│ │ ╰───────────────┴───────────────────╯ │", + "│ │ ╭───┬──────┬─────────────╮ │", + "│ bin │ │ # │ name │ path │ │", + "│ │ ├───┼──────┼─────────────┤ │", + "│ │ │ 0 │ nu │ src/main.rs │ │", + "│ │ ╰───┴──────┴─────────────╯ │", "│ │ ╭───────────┬───────────────────────╮ │", "│ patch │ │ │ ╭──────────┬────────╮ │ │", "│ │ │ crates-io │ │ reedline │ {recor │ │ │", @@ -1274,37 +1300,11 @@ fn test_expand_big_0() { "│ │ │ │ │ │ elds} │ │ │", "│ │ │ │ ╰──────────┴────────╯ │ │", "│ │ ╰───────────┴───────────────────────╯ │", - "│ target │ {record 3 fields} │", - "│ │ ╭─────────┬─────────────────────────╮ │", - "│ workspace │ │ │ ╭────┬────────────────╮ │ │", - "│ │ │ members │ │ 0 │ crates/nu-cli │ │ │", - "│ │ │ │ │ 1 │ crates/nu-engi │ │ │", - "│ │ │ │ │ │ ne │ │ │", - "│ │ │ │ │ 2 │ crates/nu-pars │ │ │", - "│ │ │ │ │ │ er │ │ │", - "│ │ │ │ │ 3 │ crates/nu-syst │ │ │", - "│ │ │ │ │ │ em │ │ │", - "│ │ │ │ │ 4 │ crates/nu-comm │ │ │", - "│ │ │ │ │ │ and │ │ │", - "│ │ │ │ │ 5 │ crates/nu-prot │ │ │", - "│ │ │ │ │ │ ocol │ │ │", - "│ │ │ │ │ 6 │ crates/nu-plug │ │ │", - "│ │ │ │ │ │ in │ │ │", - "│ │ │ │ │ 7 │ crates/nu_plug │ │ │", - "│ │ │ │ │ │ in_inc │ │ │", - "│ │ │ │ │ 8 │ crates/nu_plug │ │ │", - "│ │ │ │ │ │ in_gstat │ │ │", - "│ │ │ │ │ 9 │ crates/nu_plug │ │ │", - "│ │ │ │ │ │ in_example │ │ │", - "│ │ │ │ │ 10 │ crates/nu_plug │ │ │", - "│ │ │ │ │ │ in_query │ │ │", - "│ │ │ │ │ 11 │ crates/nu_plug │ │ │", - "│ │ │ │ │ │ in_custom_valu │ │ │", - "│ │ │ │ │ │ es │ │ │", - "│ │ │ │ │ 12 │ crates/nu-util │ │ │", - "│ │ │ │ │ │ s │ │ │", - "│ │ │ │ ╰────┴────────────────╯ │ │", - "│ │ ╰─────────┴─────────────────────────╯ │", + "│ │ ╭───┬────────────┬─────────╮ │", + "│ bench │ │ # │ name │ harness │ │", + "│ │ ├───┼────────────┼─────────┤ │", + "│ │ │ 0 │ benchmarks │ false │ │", + "│ │ ╰───┴────────────┴─────────╯ │", "╰──────────────────┴───────────────────────────────────────╯", ]); @@ -1319,14 +1319,21 @@ fn test_expand_big_0() { let expected = join_lines([ "╭──────────────────┬───────────────────╮", - "│ bench │ [table 1 row] │", - "│ bin │ [table 1 row] │", - "│ dependencies │ {record 13 │", - "│ │ fields} │", - "│ dev-dependencies │ {record 9 fields} │", - "│ features │ {record 8 fields} │", "│ package │ {record 13 │", "│ │ fields} │", + "│ │ ╭─────────┬─────╮ │", + "│ workspace │ │ members │ [li │ │", + "│ │ │ │ st │ │", + "│ │ │ │ 13 │ │", + "│ │ │ │ ite │ │", + "│ │ │ │ ms] │ │", + "│ │ ╰─────────┴─────╯ │", + "│ dependencies │ {record 13 │", + "│ │ fields} │", + "│ target │ {record 3 fields} │", + "│ dev-dependencies │ {record 9 fields} │", + "│ features │ {record 8 fields} │", + "│ bin │ [table 1 row] │", "│ │ ╭───────────┬───╮ │", "│ patch │ │ crates-io │ { │ │", "│ │ │ │ r │ │", @@ -1345,14 +1352,7 @@ fn test_expand_big_0() { "│ │ │ │ d │ │", "│ │ │ │ } │ │", "│ │ ╰───────────┴───╯ │", - "│ target │ {record 3 fields} │", - "│ │ ╭─────────┬─────╮ │", - "│ workspace │ │ members │ [li │ │", - "│ │ │ │ st │ │", - "│ │ │ │ 13 │ │", - "│ │ │ │ ite │ │", - "│ │ │ │ ms] │ │", - "│ │ ╰─────────┴─────╯ │", + "│ bench │ [table 1 row] │", "╰──────────────────┴───────────────────╯", ]); @@ -1952,107 +1952,7 @@ fn test_collapse_big_0() { _print_lines(&actual.out, 80); let expected = join_lines([ - "╭──────────────────┬─────────┬─────────────────────────────────────────────────╮", - "│ bench │ harness │ name │", - "│ ├─────────┼─────────────────────────────────────────────────┤", - "│ │ false │ benchmarks │", - "├──────────────────┼──────┬──┴─────────────────────────────────────────────────┤", - "│ bin │ name │ path │", - "│ ├──────┼────────────────────────────────────────────────────┤", - "│ │ nu │ src/main.rs │", - "├──────────────────┼──────┴────────┬──────────┬────────────────────────────────┤", - "│ dependencies │ chrono │ features │ serde │", - "│ │ ├──────────┼────────────────────────────────┤", - "│ │ │ version │ 0.4.23 │", - "│ ├───────────────┼──────────┴────────────────────────────────┤", - "│ │ crossterm │ 0.24.0 │", - "│ ├───────────────┼───────────────────────────────────────────┤", - "│ │ ctrlc │ 3.2.1 │", - "│ ├───────────────┼───────────────────────────────────────────┤", - "│ │ is_executable │ 1.0.1 │", - "│ ├───────────────┼───────────────────────────────────────────┤", - "│ │ log │ 0.4 │", - "│ ├───────────────┼──────────┬────────────────────────────────┤", - "│ │ miette │ features │ fancy-no-backtrace │", - "│ │ ├──────────┼────────────────────────────────┤", - "│ │ │ version │ 5.5.0 │", - "│ ├───────────────┼──────────┴────────────────────────────────┤", - "│ │ nu-ansi-term │ 0.46.0 │", - "│ ├───────────────┼─────────┬─────────────────────────────────┤", - "│ │ nu-cli │ path │ ./crates/nu-cli │", - "│ │ ├─────────┼─────────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────┼─────────┼─────────────────────────────────┤", - "│ │ nu-engine │ path │ ./crates/nu-engine │", - "│ │ ├─────────┼─────────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────┼─────────┴─────────────────────────────────┤", - "│ │ rayon │ 1.6.1 │", - "│ ├───────────────┼──────────┬────────────────────────────────┤", - "│ │ reedline │ features │ bashisms │", - "│ │ │ ├────────────────────────────────┤", - "│ │ │ │ sqlite │", - "│ │ ├──────────┼────────────────────────────────┤", - "│ │ │ version │ 0.14.0 │", - "│ ├───────────────┼──────────┴────────────────────────────────┤", - "│ │ simplelog │ 0.12.0 │", - "│ ├───────────────┼───────────────────────────────────────────┤", - "│ │ time │ 0.3.12 │", - "├──────────────────┼───────────────┴───┬───────────────────────────────────────┤", - "│ dev-dependencies │ assert_cmd │ 2.0.2 │", - "│ ├───────────────────┼───────────────────────────────────────┤", - "│ │ criterion │ 0.4 │", - "│ ├───────────────────┼───────────────────────────────────────┤", - "│ │ hamcrest2 │ 0.3.0 │", - "│ ├───────────────────┼───────────────────────────────────────┤", - "│ │ itertools │ 0.10.3 │", - "│ ├───────────────────┼─────────┬─────────────────────────────┤", - "│ │ nu-test-support │ path │ ./crates/nu-test-support │", - "│ │ ├─────────┼─────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────────┼─────────┴─────────────────────────────┤", - "│ │ pretty_assertions │ 1.0.0 │", - "│ ├───────────────────┼──────────────────┬────────────────────┤", - "│ │ rstest │ default-features │ false │", - "│ │ ├──────────────────┼────────────────────┤", - "│ │ │ version │ 0.15.0 │", - "│ ├───────────────────┼──────────────────┴────────────────────┤", - "│ │ serial_test │ 0.10.0 │", - "│ ├───────────────────┼───────────────────────────────────────┤", - "│ │ tempfile │ 3.2.0 │", - "├──────────────────┼───────────────────┴─┬─────────────────────────────────────┤", - "│ features │ default │ plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ which-support │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ trash-support │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ sqlite │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ extra │ default │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ plugin │ nu-plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ nu-cli/plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ nu-parser/plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ nu-command/plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ nu-protocol/plugin │", - "│ │ ├─────────────────────────────────────┤", - "│ │ │ nu-engine/plugin │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ stable │ default │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ static-link-openssl │ dep:openssl │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ trash-support │ nu-command/trash-support │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ wasi │ │", - "│ ├─────────────────────┼─────────────────────────────────────┤", - "│ │ which-support │ nu-command/which-support │", - "├──────────────────┼───────────────┬─────┴─────────────────────────────────────┤", + "╭──────────────────┬───────────────┬───────────────────────────────────────────╮", "│ package │ authors │ The Nushell Project Developers │", "│ ├───────────────┼───────────────────────────────────────────┤", "│ │ default-run │ nu │", @@ -2068,19 +1968,7 @@ fn test_collapse_big_0() { "│ │ homepage │ https://www.nushell.sh │", "│ ├───────────────┼───────────────────────────────────────────┤", "│ │ license │ MIT │", - "│ ├───────────────┼──────────┬───────────┬────────────────────┤", - "│ │ metadata │ binstall │ overrides │ ... │", - "│ │ │ ├───────────┼────────────────────┤", - "│ │ │ │ pkg-fmt │ tgz │", - "│ │ │ ├───────────┼────────────────────┤", - "│ │ │ │ pkg-url │ { repo }/releases/ │", - "│ │ │ │ │ download/{ v │", - "│ │ │ │ │ ersion │", - "│ │ │ │ │ }/{ name }-{ vers │", - "│ │ │ │ │ ion }- │", - "│ │ │ │ │ { target }.{ │", - "│ │ │ │ │ archive-format } │", - "│ ├───────────────┼──────────┴───────────┴────────────────────┤", + "│ ├───────────────┼───────────────────────────────────────────┤", "│ │ name │ nu │", "│ ├───────────────┼───────────────────────────────────────────┤", "│ │ repository │ https://github.com/nushell/nushell │", @@ -2088,22 +1976,19 @@ fn test_collapse_big_0() { "│ │ rust-version │ 1.60 │", "│ ├───────────────┼───────────────────────────────────────────┤", "│ │ version │ 0.74.1 │", - "├──────────────────┼───────────┬───┴──────┬────────┬───────────────────────────┤", - "│ patch │ crates-io │ reedline │ branch │ main │", - "│ │ │ ├────────┼───────────────────────────┤", - "│ │ │ │ git │ https://github.com/nushel │", - "│ │ │ │ │ l/reedline.git │", - "├──────────────────┼───────────┴──────────┴────────┴─┬──────────────┬──────────┤", - "│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ ... │", - "│ │ │ ├──────────┤", - "│ │ │ │ ... │", - "│ ├─────────────────────────────────┼──────────────┼──────────┤", - "│ │ cfg(target_family = \"unix\") │ dependencies │ ... │", - "│ │ │ ├──────────┤", - "│ │ │ │ ... │", - "│ ├─────────────────────────────────┼──────────────┴──────────┤", - "│ │ cfg(windows) │ ... │", - "├──────────────────┼─────────┬───────────────────────┴─────────────────────────┤", + "│ ├───────────────┼──────────┬───────────┬────────────────────┤", + "│ │ metadata │ binstall │ pkg-url │ { repo }/releases/ │", + "│ │ │ │ │ download/{ v │", + "│ │ │ │ │ ersion │", + "│ │ │ │ │ }/{ name }-{ vers │", + "│ │ │ │ │ ion }- │", + "│ │ │ │ │ { target }.{ │", + "│ │ │ │ │ archive-format } │", + "│ │ │ ├───────────┼────────────────────┤", + "│ │ │ │ pkg-fmt │ tgz │", + "│ │ │ ├───────────┼────────────────────┤", + "│ │ │ │ overrides │ ... │", + "├──────────────────┼─────────┬─────┴──────────┴───────────┴────────────────────┤", "│ workspace │ members │ crates/nu-cli │", "│ │ ├─────────────────────────────────────────────────┤", "│ │ │ crates/nu-engine │", @@ -2129,7 +2014,122 @@ fn test_collapse_big_0() { "│ │ │ crates/nu_plugin_custom_values │", "│ │ ├─────────────────────────────────────────────────┤", "│ │ │ crates/nu-utils │", - "╰──────────────────┴─────────┴─────────────────────────────────────────────────╯", + "├──────────────────┼─────────┴─────┬──────────┬────────────────────────────────┤", + "│ dependencies │ chrono │ version │ 0.4.23 │", + "│ │ ├──────────┼────────────────────────────────┤", + "│ │ │ features │ serde │", + "│ ├───────────────┼──────────┴────────────────────────────────┤", + "│ │ crossterm │ 0.24.0 │", + "│ ├───────────────┼───────────────────────────────────────────┤", + "│ │ ctrlc │ 3.2.1 │", + "│ ├───────────────┼───────────────────────────────────────────┤", + "│ │ log │ 0.4 │", + "│ ├───────────────┼──────────┬────────────────────────────────┤", + "│ │ miette │ version │ 5.5.0 │", + "│ │ ├──────────┼────────────────────────────────┤", + "│ │ │ features │ fancy-no-backtrace │", + "│ ├───────────────┼──────────┴────────────────────────────────┤", + "│ │ nu-ansi-term │ 0.46.0 │", + "│ ├───────────────┼─────────┬─────────────────────────────────┤", + "│ │ nu-cli │ path │ ./crates/nu-cli │", + "│ │ ├─────────┼─────────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────┼─────────┼─────────────────────────────────┤", + "│ │ nu-engine │ path │ ./crates/nu-engine │", + "│ │ ├─────────┼─────────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────┼─────────┴┬────────────────────────────────┤", + "│ │ reedline │ version │ 0.14.0 │", + "│ │ ├──────────┼────────────────────────────────┤", + "│ │ │ features │ bashisms │", + "│ │ │ ├────────────────────────────────┤", + "│ │ │ │ sqlite │", + "│ ├───────────────┼──────────┴────────────────────────────────┤", + "│ │ rayon │ 1.6.1 │", + "│ ├───────────────┼───────────────────────────────────────────┤", + "│ │ is_executable │ 1.0.1 │", + "│ ├───────────────┼───────────────────────────────────────────┤", + "│ │ simplelog │ 0.12.0 │", + "│ ├───────────────┼───────────────────────────────────────────┤", + "│ │ time │ 0.3.12 │", + "├──────────────────┼───────────────┴─────────────────┬──────────────┬──────────┤", + "│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ ... │", + "│ │ │ ├──────────┤", + "│ │ │ │ ... │", + "│ ├─────────────────────────────────┼──────────────┴──────────┤", + "│ │ cfg(windows) │ ... │", + "│ ├─────────────────────────────────┼──────────────┬──────────┤", + "│ │ cfg(target_family = \"unix\") │ dependencies │ ... │", + "│ │ │ ├──────────┤", + "│ │ │ │ ... │", + "├──────────────────┼───────────────────┬─────────┬───┴──────────────┴──────────┤", + "│ dev-dependencies │ nu-test-support │ path │ ./crates/nu-test-support │", + "│ │ ├─────────┼─────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────────┼─────────┴─────────────────────────────┤", + "│ │ tempfile │ 3.2.0 │", + "│ ├───────────────────┼───────────────────────────────────────┤", + "│ │ assert_cmd │ 2.0.2 │", + "│ ├───────────────────┼───────────────────────────────────────┤", + "│ │ criterion │ 0.4 │", + "│ ├───────────────────┼───────────────────────────────────────┤", + "│ │ pretty_assertions │ 1.0.0 │", + "│ ├───────────────────┼───────────────────────────────────────┤", + "│ │ serial_test │ 0.10.0 │", + "│ ├───────────────────┼───────────────────────────────────────┤", + "│ │ hamcrest2 │ 0.3.0 │", + "│ ├───────────────────┼──────────────────┬────────────────────┤", + "│ │ rstest │ version │ 0.15.0 │", + "│ │ ├──────────────────┼────────────────────┤", + "│ │ │ default-features │ false │", + "│ ├───────────────────┼──────────────────┴────────────────────┤", + "│ │ itertools │ 0.10.3 │", + "├──────────────────┼───────────────────┴─┬─────────────────────────────────────┤", + "│ features │ plugin │ nu-plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ nu-cli/plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ nu-parser/plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ nu-command/plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ nu-protocol/plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ nu-engine/plugin │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ extra │ default │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ default │ plugin │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ which-support │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ trash-support │", + "│ │ ├─────────────────────────────────────┤", + "│ │ │ sqlite │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ stable │ default │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ wasi │ │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ static-link-openssl │ dep:openssl │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ which-support │ nu-command/which-support │", + "│ ├─────────────────────┼─────────────────────────────────────┤", + "│ │ trash-support │ nu-command/trash-support │", + "├──────────────────┼──────┬──────────────┴─────────────────────────────────────┤", + "│ bin │ name │ path │", + "│ ├──────┼────────────────────────────────────────────────────┤", + "│ │ nu │ src/main.rs │", + "├──────────────────┼──────┴────┬──────────┬────────┬───────────────────────────┤", + "│ patch │ crates-io │ reedline │ git │ https://github.com/nushel │", + "│ │ │ │ │ l/reedline.git │", + "│ │ │ ├────────┼───────────────────────────┤", + "│ │ │ │ branch │ main │", + "├──────────────────┼───────────┴┬─────────┴────────┴───────────────────────────┤", + "│ bench │ name │ harness │", + "│ ├────────────┼──────────────────────────────────────────────┤", + "│ │ benchmarks │ false │", + "╰──────────────────┴────────────┴──────────────────────────────────────────────╯", ]); assert_eq!(actual.out, expected); @@ -2142,107 +2142,7 @@ fn test_collapse_big_0() { _print_lines(&actual.out, 111); let expected = join_lines([ - "╭──────────────────┬─────────┬────────────────────────────────────────────────────────────────────────────────╮", - "│ bench │ harness │ name │", - "│ ├─────────┼────────────────────────────────────────────────────────────────────────────────┤", - "│ │ false │ benchmarks │", - "├──────────────────┼──────┬──┴────────────────────────────────────────────────────────────────────────────────┤", - "│ bin │ name │ path │", - "│ ├──────┼───────────────────────────────────────────────────────────────────────────────────┤", - "│ │ nu │ src/main.rs │", - "├──────────────────┼──────┴────────┬──────────┬───────────────────────────────────────────────────────────────┤", - "│ dependencies │ chrono │ features │ serde │", - "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 0.4.23 │", - "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", - "│ │ crossterm │ 0.24.0 │", - "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", - "│ │ ctrlc │ 3.2.1 │", - "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", - "│ │ is_executable │ 1.0.1 │", - "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", - "│ │ log │ 0.4 │", - "│ ├───────────────┼──────────┬───────────────────────────────────────────────────────────────┤", - "│ │ miette │ features │ fancy-no-backtrace │", - "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 5.5.0 │", - "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", - "│ │ nu-ansi-term │ 0.46.0 │", - "│ ├───────────────┼─────────┬────────────────────────────────────────────────────────────────┤", - "│ │ nu-cli │ path │ ./crates/nu-cli │", - "│ │ ├─────────┼────────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────┼─────────┼────────────────────────────────────────────────────────────────┤", - "│ │ nu-engine │ path │ ./crates/nu-engine │", - "│ │ ├─────────┼────────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────┼─────────┴────────────────────────────────────────────────────────────────┤", - "│ │ rayon │ 1.6.1 │", - "│ ├───────────────┼──────────┬───────────────────────────────────────────────────────────────┤", - "│ │ reedline │ features │ bashisms │", - "│ │ │ ├───────────────────────────────────────────────────────────────┤", - "│ │ │ │ sqlite │", - "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 0.14.0 │", - "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", - "│ │ simplelog │ 0.12.0 │", - "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", - "│ │ time │ 0.3.12 │", - "├──────────────────┼───────────────┴───┬──────────────────────────────────────────────────────────────────────┤", - "│ dev-dependencies │ assert_cmd │ 2.0.2 │", - "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", - "│ │ criterion │ 0.4 │", - "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", - "│ │ hamcrest2 │ 0.3.0 │", - "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", - "│ │ itertools │ 0.10.3 │", - "│ ├───────────────────┼─────────┬────────────────────────────────────────────────────────────┤", - "│ │ nu-test-support │ path │ ./crates/nu-test-support │", - "│ │ ├─────────┼────────────────────────────────────────────────────────────┤", - "│ │ │ version │ 0.74.1 │", - "│ ├───────────────────┼─────────┴────────────────────────────────────────────────────────────┤", - "│ │ pretty_assertions │ 1.0.0 │", - "│ ├───────────────────┼──────────────────┬───────────────────────────────────────────────────┤", - "│ │ rstest │ default-features │ false │", - "│ │ ├──────────────────┼───────────────────────────────────────────────────┤", - "│ │ │ version │ 0.15.0 │", - "│ ├───────────────────┼──────────────────┴───────────────────────────────────────────────────┤", - "│ │ serial_test │ 0.10.0 │", - "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", - "│ │ tempfile │ 3.2.0 │", - "├──────────────────┼───────────────────┴─┬────────────────────────────────────────────────────────────────────┤", - "│ features │ default │ plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ which-support │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ trash-support │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ sqlite │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ extra │ default │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ plugin │ nu-plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ nu-cli/plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ nu-parser/plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ nu-command/plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ nu-protocol/plugin │", - "│ │ ├────────────────────────────────────────────────────────────────────┤", - "│ │ │ nu-engine/plugin │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ stable │ default │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ static-link-openssl │ dep:openssl │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ trash-support │ nu-command/trash-support │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ wasi │ │", - "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", - "│ │ which-support │ nu-command/which-support │", - "├──────────────────┼───────────────┬─────┴────────────────────────────────────────────────────────────────────┤", + "╭──────────────────┬───────────────┬──────────────────────────────────────────────────────────────────────────╮", "│ package │ authors │ The Nushell Project Developers │", "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", "│ │ default-run │ nu │", @@ -2258,15 +2158,7 @@ fn test_collapse_big_0() { "│ │ homepage │ https://www.nushell.sh │", "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", "│ │ license │ MIT │", - "│ ├───────────────┼──────────┬───────────┬────────────────────────┬─────────┬────────────────┤", - "│ │ metadata │ binstall │ overrides │ x86_64-pc-windows-msvc │ pkg-fmt │ zip │", - "│ │ │ ├───────────┼────────────────────────┴─────────┴────────────────┤", - "│ │ │ │ pkg-fmt │ tgz │", - "│ │ │ ├───────────┼───────────────────────────────────────────────────┤", - "│ │ │ │ pkg-url │ { repo }/releases/download/{ v │", - "│ │ │ │ │ ersion }/{ name }-{ version }- │", - "│ │ │ │ │ { target }.{ archive-format } │", - "│ ├───────────────┼──────────┴───────────┴───────────────────────────────────────────────────┤", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", "│ │ name │ nu │", "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", "│ │ repository │ https://github.com/nushell/nushell │", @@ -2274,37 +2166,15 @@ fn test_collapse_big_0() { "│ │ rust-version │ 1.60 │", "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", "│ │ version │ 0.74.1 │", - "├──────────────────┼───────────┬───┴──────┬────────┬──────────────────────────────────────────────────────────┤", - "│ patch │ crates-io │ reedline │ branch │ main │", - "│ │ │ ├────────┼──────────────────────────────────────────────────────────┤", - "│ │ │ │ git │ https://github.com/nushell/reedline.git │", - "├──────────────────┼───────────┴──────────┴────────┴─┬──────────────┬─────────────┬──────────┬────────────────┤", - "│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ openssl │ features │ vendored │", - "│ │ │ │ ├──────────┼────────────────┤", - "│ │ │ │ │ optional │ true │", - "│ │ │ │ ├──────────┼────────────────┤", - "│ │ │ │ │ version │ 0.10.38 │", - "│ │ │ ├─────────────┼──────────┴───────┬────────┤", - "│ │ │ │ signal-hook │ default-features │ false │", - "│ │ │ │ ├──────────────────┼────────┤", - "│ │ │ │ │ version │ 0.3.14 │", - "│ ├─────────────────────────────────┼──────────────┼──────┬──────┴──────────────────┴────────┤", - "│ │ cfg(target_family = \"unix\") │ dependencies │ atty │ 0.2 │", - "│ │ │ ├──────┼──────────────────┬───────────────┤", - "│ │ │ │ nix │ default-features │ false │", - "│ │ │ │ ├──────────────────┼───────────────┤", - "│ │ │ │ │ features │ signal │", - "│ │ │ │ │ ├───────────────┤", - "│ │ │ │ │ │ process │", - "│ │ │ │ │ ├───────────────┤", - "│ │ │ │ │ │ fs │", - "│ │ │ │ │ ├───────────────┤", - "│ │ │ │ │ │ term │", - "│ │ │ │ ├──────────────────┼───────────────┤", - "│ │ │ │ │ version │ 0.25 │", - "│ ├─────────────────────────────────┼──────────────┴─────┬┴───────┬──────────┴───────────────┤", - "│ │ cfg(windows) │ build-dependencies │ winres │ 0.1 │", - "├──────────────────┼─────────┬───────────────────────┴────────────────────┴────────┴──────────────────────────┤", + "│ ├───────────────┼──────────┬───────────┬───────────────────────────────────────────────────┤", + "│ │ metadata │ binstall │ pkg-url │ { repo }/releases/download/{ v │", + "│ │ │ │ │ ersion }/{ name }-{ version }- │", + "│ │ │ │ │ { target }.{ archive-format } │", + "│ │ │ ├───────────┼───────────────────────────────────────────────────┤", + "│ │ │ │ pkg-fmt │ tgz │", + "│ │ │ ├───────────┼────────────────────────┬─────────┬────────────────┤", + "│ │ │ │ overrides │ x86_64-pc-windows-msvc │ pkg-fmt │ zip │", + "├──────────────────┼─────────┬─────┴──────────┴───────────┴────────────────────────┴─────────┴────────────────┤", "│ workspace │ members │ crates/nu-cli │", "│ │ ├────────────────────────────────────────────────────────────────────────────────┤", "│ │ │ crates/nu-engine │", @@ -2330,7 +2200,137 @@ fn test_collapse_big_0() { "│ │ │ crates/nu_plugin_custom_values │", "│ │ ├────────────────────────────────────────────────────────────────────────────────┤", "│ │ │ crates/nu-utils │", - "╰──────────────────┴─────────┴────────────────────────────────────────────────────────────────────────────────╯", + "├──────────────────┼─────────┴─────┬──────────┬───────────────────────────────────────────────────────────────┤", + "│ dependencies │ chrono │ version │ 0.4.23 │", + "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", + "│ │ │ features │ serde │", + "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", + "│ │ crossterm │ 0.24.0 │", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", + "│ │ ctrlc │ 3.2.1 │", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", + "│ │ log │ 0.4 │", + "│ ├───────────────┼──────────┬───────────────────────────────────────────────────────────────┤", + "│ │ miette │ version │ 5.5.0 │", + "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", + "│ │ │ features │ fancy-no-backtrace │", + "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", + "│ │ nu-ansi-term │ 0.46.0 │", + "│ ├───────────────┼─────────┬────────────────────────────────────────────────────────────────┤", + "│ │ nu-cli │ path │ ./crates/nu-cli │", + "│ │ ├─────────┼────────────────────────────────────────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────┼─────────┼────────────────────────────────────────────────────────────────┤", + "│ │ nu-engine │ path │ ./crates/nu-engine │", + "│ │ ├─────────┼────────────────────────────────────────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────┼─────────┴┬───────────────────────────────────────────────────────────────┤", + "│ │ reedline │ version │ 0.14.0 │", + "│ │ ├──────────┼───────────────────────────────────────────────────────────────┤", + "│ │ │ features │ bashisms │", + "│ │ │ ├───────────────────────────────────────────────────────────────┤", + "│ │ │ │ sqlite │", + "│ ├───────────────┼──────────┴───────────────────────────────────────────────────────────────┤", + "│ │ rayon │ 1.6.1 │", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", + "│ │ is_executable │ 1.0.1 │", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", + "│ │ simplelog │ 0.12.0 │", + "│ ├───────────────┼──────────────────────────────────────────────────────────────────────────┤", + "│ │ time │ 0.3.12 │", + "├──────────────────┼───────────────┴─────────────────┬──────────────┬─────────────┬──────────┬────────────────┤", + "│ target │ cfg(not(target_os = \"windows\")) │ dependencies │ openssl │ version │ 0.10.38 │", + "│ │ │ │ ├──────────┼────────────────┤", + "│ │ │ │ │ features │ vendored │", + "│ │ │ │ ├──────────┼────────────────┤", + "│ │ │ │ │ optional │ true │", + "│ │ │ ├─────────────┼──────────┴───────┬────────┤", + "│ │ │ │ signal-hook │ version │ 0.3.14 │", + "│ │ │ │ ├──────────────────┼────────┤", + "│ │ │ │ │ default-features │ false │", + "│ ├─────────────────────────────────┼──────────────┴─────┬───────┴┬─────────────────┴────────┤", + "│ │ cfg(windows) │ build-dependencies │ winres │ 0.1 │", + "│ ├─────────────────────────────────┼──────────────┬─────┴┬───────┴──────────┬───────────────┤", + "│ │ cfg(target_family = \"unix\") │ dependencies │ nix │ version │ 0.25 │", + "│ │ │ │ ├──────────────────┼───────────────┤", + "│ │ │ │ │ default-features │ false │", + "│ │ │ │ ├──────────────────┼───────────────┤", + "│ │ │ │ │ features │ signal │", + "│ │ │ │ │ ├───────────────┤", + "│ │ │ │ │ │ process │", + "│ │ │ │ │ ├───────────────┤", + "│ │ │ │ │ │ fs │", + "│ │ │ │ │ ├───────────────┤", + "│ │ │ │ │ │ term │", + "│ │ │ ├──────┼──────────────────┴───────────────┤", + "│ │ │ │ atty │ 0.2 │", + "├──────────────────┼───────────────────┬─────────┬───┴──────────────┴──────┴──────────────────────────────────┤", + "│ dev-dependencies │ nu-test-support │ path │ ./crates/nu-test-support │", + "│ │ ├─────────┼────────────────────────────────────────────────────────────┤", + "│ │ │ version │ 0.74.1 │", + "│ ├───────────────────┼─────────┴────────────────────────────────────────────────────────────┤", + "│ │ tempfile │ 3.2.0 │", + "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", + "│ │ assert_cmd │ 2.0.2 │", + "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", + "│ │ criterion │ 0.4 │", + "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", + "│ │ pretty_assertions │ 1.0.0 │", + "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", + "│ │ serial_test │ 0.10.0 │", + "│ ├───────────────────┼──────────────────────────────────────────────────────────────────────┤", + "│ │ hamcrest2 │ 0.3.0 │", + "│ ├───────────────────┼──────────────────┬───────────────────────────────────────────────────┤", + "│ │ rstest │ version │ 0.15.0 │", + "│ │ ├──────────────────┼───────────────────────────────────────────────────┤", + "│ │ │ default-features │ false │", + "│ ├───────────────────┼──────────────────┴───────────────────────────────────────────────────┤", + "│ │ itertools │ 0.10.3 │", + "├──────────────────┼───────────────────┴─┬────────────────────────────────────────────────────────────────────┤", + "│ features │ plugin │ nu-plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ nu-cli/plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ nu-parser/plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ nu-command/plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ nu-protocol/plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ nu-engine/plugin │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ extra │ default │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ default │ plugin │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ which-support │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ trash-support │", + "│ │ ├────────────────────────────────────────────────────────────────────┤", + "│ │ │ sqlite │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ stable │ default │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ wasi │ │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ static-link-openssl │ dep:openssl │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ which-support │ nu-command/which-support │", + "│ ├─────────────────────┼────────────────────────────────────────────────────────────────────┤", + "│ │ trash-support │ nu-command/trash-support │", + "├──────────────────┼──────┬──────────────┴────────────────────────────────────────────────────────────────────┤", + "│ bin │ name │ path │", + "│ ├──────┼───────────────────────────────────────────────────────────────────────────────────┤", + "│ │ nu │ src/main.rs │", + "├──────────────────┼──────┴────┬──────────┬────────┬──────────────────────────────────────────────────────────┤", + "│ patch │ crates-io │ reedline │ git │ https://github.com/nushell/reedline.git │", + "│ │ │ ├────────┼──────────────────────────────────────────────────────────┤", + "│ │ │ │ branch │ main │", + "├──────────────────┼───────────┴┬─────────┴────────┴──────────────────────────────────────────────────────────┤", + "│ bench │ name │ harness │", + "│ ├────────────┼─────────────────────────────────────────────────────────────────────────────┤", + "│ │ benchmarks │ false │", + "╰──────────────────┴────────────┴─────────────────────────────────────────────────────────────────────────────╯", ]); assert_eq!(actual.out, expected); diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index 453786ad4e..728cf1dcb2 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -906,7 +906,7 @@ fn test_cp_debug_default() { #[cfg(any(target_os = "linux", target_os = "freebsd"))] if !actual .out - .contains("copy offload: unknown, reflink: unsupported, sparse detection: no") + .contains("copy offload: yes, reflink: unsupported, sparse detection: no") { panic!("{}", format!("Failure: stdout was \n{}", actual.out)); } diff --git a/crates/nu-command/tests/format_conversions/csv.rs b/crates/nu-command/tests/format_conversions/csv.rs index a9be76d5c3..f10a84b672 100644 --- a/crates/nu-command/tests/format_conversions/csv.rs +++ b/crates/nu-command/tests/format_conversions/csv.rs @@ -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 "# )); diff --git a/crates/nu-command/tests/format_conversions/ssv.rs b/crates/nu-command/tests/format_conversions/ssv.rs index a673b122fb..5a62f796f7 100644 --- a/crates/nu-command/tests/format_conversions/ssv.rs +++ b/crates/nu-command/tests/format_conversions/ssv.rs @@ -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 "# )); diff --git a/crates/nu-command/tests/format_conversions/tsv.rs b/crates/nu-command/tests/format_conversions/tsv.rs index be57c60242..31740ef8d9 100644 --- a/crates/nu-command/tests/format_conversions/tsv.rs +++ b/crates/nu-command/tests/format_conversions/tsv.rs @@ -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 "# )); diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml new file mode 100644 index 0000000000..50ca5d2407 --- /dev/null +++ b/crates/nu-derive-value/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ["The Nushell Project Developers"] +description = "Macros implementation of #[derive(FromValue, IntoValue)]" +edition = "2021" +license = "MIT" +name = "nu-derive-value" +repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" +version = "0.95.1" + +[lib] +proc-macro = true +# we can only use exposed macros in doctests really, +# so we cannot test anything useful in a doctest +doctest = false + +[dependencies] +proc-macro2 = { workspace = true } +syn = { workspace = true } +quote = { workspace = true } +proc-macro-error = { workspace = true } +convert_case = { workspace = true } \ No newline at end of file diff --git a/crates/nu-derive-value/LICENSE b/crates/nu-derive-value/LICENSE new file mode 100644 index 0000000000..ae174e8595 --- /dev/null +++ b/crates/nu-derive-value/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2023 The Nushell Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/nu-derive-value/src/attributes.rs b/crates/nu-derive-value/src/attributes.rs new file mode 100644 index 0000000000..6969b64287 --- /dev/null +++ b/crates/nu-derive-value/src/attributes.rs @@ -0,0 +1,116 @@ +use convert_case::Case; +use syn::{spanned::Spanned, Attribute, Fields, LitStr}; + +use crate::{error::DeriveError, HELPER_ATTRIBUTE}; + +#[derive(Debug)] +pub struct ContainerAttributes { + pub rename_all: Case, +} + +impl Default for ContainerAttributes { + fn default() -> Self { + Self { + rename_all: Case::Snake, + } + } +} + +impl ContainerAttributes { + pub fn parse_attrs<'a, M>( + iter: impl Iterator, + ) -> Result> { + let mut container_attrs = ContainerAttributes::default(); + for attr in filter(iter) { + // This is a container to allow returning derive errors inside the parse_nested_meta fn. + let mut err = Ok(()); + + attr.parse_nested_meta(|meta| { + let ident = meta.path.require_ident()?; + match ident.to_string().as_str() { + "rename_all" => { + // The matched case are all useful variants from `convert_case` with aliases + // that `serde` uses. + let case: LitStr = meta.value()?.parse()?; + let case = match case.value().as_str() { + "UPPER CASE" | "UPPER WITH SPACES CASE" => Case::Upper, + "lower case" | "lower with spaces case" => Case::Lower, + "Title Case" => Case::Title, + "camelCase" => Case::Camel, + "PascalCase" | "UpperCamelCase" => Case::Pascal, + "snake_case" => Case::Snake, + "UPPER_SNAKE_CASE" | "SCREAMING_SNAKE_CASE" => Case::UpperSnake, + "kebab-case" => Case::Kebab, + "COBOL-CASE" | "UPPER-KEBAB-CASE" | "SCREAMING-KEBAB-CASE" => { + Case::Cobol + } + "Train-Case" => Case::Train, + "flatcase" | "lowercase" => Case::Flat, + "UPPERFLATCASE" | "UPPERCASE" => Case::UpperFlat, + // Although very funny, we don't support `Case::{Toggle, Alternating}`, + // as we see no real benefit. + c => { + err = Err(DeriveError::InvalidAttributeValue { + value_span: case.span(), + value: Box::new(c.to_string()), + }); + return Ok(()); // We stored the err in `err`. + } + }; + container_attrs.rename_all = case; + } + ident => { + err = Err(DeriveError::UnexpectedAttribute { + meta_span: ident.span(), + }); + } + } + + Ok(()) + }) + .map_err(DeriveError::Syn)?; + + err?; // Shortcircuit here if `err` is holding some error. + } + + Ok(container_attrs) + } +} + +pub fn filter<'a>( + iter: impl Iterator, +) -> impl Iterator { + iter.filter(|attr| attr.path().is_ident(HELPER_ATTRIBUTE)) +} + +// The deny functions are built to easily deny the use of the helper attribute if used incorrectly. +// As the usage of it gets more complex, these functions might be discarded or replaced. + +/// Deny any attribute that uses the helper attribute. +pub fn deny(attrs: &[Attribute]) -> Result<(), DeriveError> { + match filter(attrs.iter()).next() { + Some(attr) => Err(DeriveError::InvalidAttributePosition { + attribute_span: attr.span(), + }), + None => Ok(()), + } +} + +/// Deny any attributes that uses the helper attribute on any field. +pub fn deny_fields(fields: &Fields) -> Result<(), DeriveError> { + match fields { + Fields::Named(fields) => { + for field in fields.named.iter() { + deny(&field.attrs)?; + } + } + Fields::Unnamed(fields) => { + for field in fields.unnamed.iter() { + deny(&field.attrs)?; + } + } + Fields::Unit => (), + } + + Ok(()) +} diff --git a/crates/nu-derive-value/src/error.rs b/crates/nu-derive-value/src/error.rs new file mode 100644 index 0000000000..b7fd3e2f91 --- /dev/null +++ b/crates/nu-derive-value/src/error.rs @@ -0,0 +1,82 @@ +use std::{any, fmt::Debug, marker::PhantomData}; + +use proc_macro2::Span; +use proc_macro_error::{Diagnostic, Level}; + +#[derive(Debug)] +pub enum DeriveError { + /// Marker variant, makes the `M` generic parameter valid. + _Marker(PhantomData), + + /// Parsing errors thrown by `syn`. + Syn(syn::parse::Error), + + /// `syn::DeriveInput` was a union, currently not supported + UnsupportedUnions, + + /// Only plain enums are supported right now. + UnsupportedEnums { fields_span: Span }, + + /// Found a `#[nu_value(x)]` attribute where `x` is unexpected. + UnexpectedAttribute { meta_span: Span }, + + /// Found a `#[nu_value(x)]` attribute at a invalid position. + InvalidAttributePosition { attribute_span: Span }, + + /// Found a valid `#[nu_value(x)]` attribute but the passed values is invalid. + InvalidAttributeValue { + value_span: Span, + value: Box, + }, +} + +impl From> for Diagnostic { + fn from(value: DeriveError) -> Self { + let derive_name = any::type_name::().split("::").last().expect("not empty"); + match value { + DeriveError::_Marker(_) => panic!("used marker variant"), + + DeriveError::Syn(e) => Diagnostic::spanned(e.span(), Level::Error, e.to_string()), + + DeriveError::UnsupportedUnions => Diagnostic::new( + Level::Error, + format!("`{derive_name}` cannot be derived from unions"), + ) + .help("consider refactoring to a struct".to_string()) + .note("if you really need a union, consider opening an issue on Github".to_string()), + + DeriveError::UnsupportedEnums { fields_span } => Diagnostic::spanned( + fields_span, + Level::Error, + format!("`{derive_name}` can only be derived from plain enums"), + ) + .help( + "consider refactoring your data type to a struct with a plain enum as a field" + .to_string(), + ) + .note("more complex enums could be implemented in the future".to_string()), + + DeriveError::InvalidAttributePosition { attribute_span } => Diagnostic::spanned( + attribute_span, + Level::Error, + "invalid attribute position".to_string(), + ) + .help(format!( + "check documentation for `{derive_name}` for valid placements" + )), + + DeriveError::UnexpectedAttribute { meta_span } => { + Diagnostic::spanned(meta_span, Level::Error, "unknown attribute".to_string()).help( + format!("check documentation for `{derive_name}` for valid attributes"), + ) + } + + DeriveError::InvalidAttributeValue { value_span, value } => { + Diagnostic::spanned(value_span, Level::Error, format!("invalid value {value:?}")) + .help(format!( + "check documentation for `{derive_name}` for valid attribute values" + )) + } + } + } +} diff --git a/crates/nu-derive-value/src/from.rs b/crates/nu-derive-value/src/from.rs new file mode 100644 index 0000000000..783a22920e --- /dev/null +++ b/crates/nu-derive-value/src/from.rs @@ -0,0 +1,567 @@ +use convert_case::Casing; +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}; + +#[derive(Debug)] +pub struct FromValue; +type DeriveError = super::error::DeriveError; + +/// Inner implementation of the `#[derive(FromValue)]` macro for structs and enums. +/// +/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`. +/// +/// This function directs the `FromValue` trait derivation to the correct implementation based on +/// the input type: +/// - For structs: [`derive_struct_from_value`] +/// - For enums: [`derive_enum_from_value`] +/// - Unions are not supported and will return an error. +pub fn derive_from_value(input: TokenStream2) -> Result { + let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?; + match input.data { + Data::Struct(data_struct) => Ok(derive_struct_from_value( + input.ident, + data_struct, + input.generics, + input.attrs, + )?), + Data::Enum(data_enum) => Ok(derive_enum_from_value( + input.ident, + data_enum, + input.generics, + input.attrs, + )?), + Data::Union(_) => Err(DeriveError::UnsupportedUnions), + } +} + +/// Implements the `#[derive(FromValue)]` macro for structs. +/// +/// This function ensures that the helper attribute is not used anywhere, as it is not supported for +/// structs. +/// Other than this, this function provides the impl signature for `FromValue`. +/// The implementation for `FromValue::from_value` is handled by [`struct_from_value`] and the +/// `FromValue::expected_type` is handled by [`struct_expected_type`]. +fn derive_struct_from_value( + ident: Ident, + data: DataStruct, + generics: Generics, + attrs: Vec, +) -> Result { + attributes::deny(&attrs)?; + attributes::deny_fields(&data.fields)?; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let from_value_impl = struct_from_value(&data); + let expected_type_impl = struct_expected_type(&data.fields); + Ok(quote! { + #[automatically_derived] + impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause { + #from_value_impl + #expected_type_impl + } + }) +} + +/// Implements `FromValue::from_value` for structs. +/// +/// This function constructs the `from_value` function for structs. +/// The implementation is straightforward as most of the heavy lifting is handled by +/// `parse_value_via_fields`, and this function only needs to construct the signature around it. +/// +/// For structs with named fields, this constructs a large return type where each field +/// contains the implementation for that specific field. +/// In structs with unnamed fields, a [`VecDeque`](std::collections::VecDeque) is used to load each +/// field one after another, and the result is used to construct the tuple. +/// For unit structs, this only checks if the input value is `Value::Nothing`. +/// +/// # Examples +/// +/// These examples show what the macro would generate. +/// +/// Struct with named fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Pet { +/// name: String, +/// age: u8, +/// favorite_toy: Option, +/// } +/// +/// impl nu_protocol::FromValue for Pet { +/// fn from_value( +/// v: nu_protocol::Value +/// ) -> std::result::Result { +/// let span = v.span(); +/// let mut record = v.into_record()?; +/// std::result::Result::Ok(Pet { +/// name: ::from_value( +/// record +/// .remove("name") +/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { +/// col_name: std::string::ToString::to_string("name"), +/// span: std::option::Option::None, +/// src_span: span +/// })?, +/// )?, +/// age: ::from_value( +/// record +/// .remove("age") +/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { +/// col_name: std::string::ToString::to_string("age"), +/// 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(), +/// }) +/// } +/// } +/// ``` +/// +/// Struct with unnamed fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Color(u8, u8, u8); +/// +/// impl nu_protocol::FromValue for Color { +/// fn from_value( +/// v: nu_protocol::Value +/// ) -> std::result::Result { +/// let span = v.span(); +/// let list = v.into_list()?; +/// let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list); +/// std::result::Result::Ok(Self( +/// { +/// ::from_value( +/// deque +/// .pop_front() +/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { +/// col_name: std::string::ToString::to_string(&0), +/// span: std::option::Option::None, +/// src_span: span +/// })?, +/// )? +/// }, +/// { +/// ::from_value( +/// deque +/// .pop_front() +/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { +/// col_name: std::string::ToString::to_string(&1), +/// span: std::option::Option::None, +/// src_span: span +/// })?, +/// )? +/// }, +/// { +/// ::from_value( +/// deque +/// .pop_front() +/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { +/// col_name: std::string::ToString::to_string(&2), +/// span: std::option::Option::None, +/// src_span: span +/// })?, +/// )? +/// } +/// )) +/// } +/// } +/// ``` +/// +/// Unit struct: +/// ```rust +/// #[derive(IntoValue)] +/// struct Unicorn; +/// +/// impl nu_protocol::FromValue for Unicorn { +/// fn from_value( +/// v: nu_protocol::Value +/// ) -> std::result::Result { +/// match v { +/// nu_protocol::Value::Nothing {..} => Ok(Self), +/// v => std::result::Result::Err(nu_protocol::ShellError::CantConvert { +/// to_type: std::string::ToString::to_string(&::expected_type()), +/// from_type: std::string::ToString::to_string(&v.get_type()), +/// span: v.span(), +/// help: std::option::Option::None +/// }) +/// } +/// } +/// } +/// ``` +fn struct_from_value(data: &DataStruct) -> TokenStream2 { + let body = parse_value_via_fields(&data.fields, quote!(Self)); + quote! { + fn from_value( + v: nu_protocol::Value + ) -> std::result::Result { + #body + } + } +} + +/// Implements `FromValue::expected_type` for structs. +/// +/// This function constructs the `expected_type` function for structs. +/// The type depends on the `fields`: named fields construct a record type with every key and type +/// laid out. +/// Unnamed fields construct a custom type with the name in the format like +/// `list[type0, type1, type2]`. +/// No fields expect the `Type::Nothing`. +/// +/// # Examples +/// +/// These examples show what the macro would generate. +/// +/// Struct with named fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Pet { +/// name: String, +/// age: u8, +/// favorite_toy: Option, +/// } +/// +/// impl nu_protocol::FromValue for Pet { +/// fn expected_type() -> nu_protocol::Type { +/// nu_protocol::Type::Record( +/// std::vec![ +/// ( +/// std::string::ToString::to_string("name"), +/// ::expected_type(), +/// ), +/// ( +/// std::string::ToString::to_string("age"), +/// ::expected_type(), +/// ), +/// ( +/// std::string::ToString::to_string("favorite_toy"), +/// as nu_protocol::FromValue>::expected_type(), +/// ) +/// ].into_boxed_slice() +/// ) +/// } +/// } +/// ``` +/// +/// Struct with unnamed fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Color(u8, u8, u8); +/// +/// impl nu_protocol::FromValue for Color { +/// fn expected_type() -> nu_protocol::Type { +/// nu_protocol::Type::Custom( +/// std::format!( +/// "[{}, {}, {}]", +/// ::expected_type(), +/// ::expected_type(), +/// ::expected_type() +/// ) +/// .into_boxed_str() +/// ) +/// } +/// } +/// ``` +/// +/// Unit struct: +/// ```rust +/// #[derive(IntoValue)] +/// struct Unicorn; +/// +/// impl nu_protocol::FromValue for Color { +/// fn expected_type() -> nu_protocol::Type { +/// nu_protocol::Type::Nothing +/// } +/// } +/// ``` +fn struct_expected_type(fields: &Fields) -> TokenStream2 { + let ty = match fields { + Fields::Named(fields) => { + let fields = fields.named.iter().map(|field| { + let ident = field.ident.as_ref().expect("named has idents"); + let ident_s = ident.to_string(); + let ty = &field.ty; + quote! {( + std::string::ToString::to_string(#ident_s), + <#ty as nu_protocol::FromValue>::expected_type(), + )} + }); + quote!(nu_protocol::Type::Record( + std::vec![#(#fields),*].into_boxed_slice() + )) + } + Fields::Unnamed(fields) => { + let mut iter = fields.unnamed.iter(); + let fields = fields.unnamed.iter().map(|field| { + let ty = &field.ty; + quote!(<#ty as nu_protocol::FromValue>::expected_type()) + }); + let mut template = String::new(); + template.push('['); + if iter.next().is_some() { + template.push_str("{}") + } + iter.for_each(|_| template.push_str(", {}")); + template.push(']'); + quote! { + nu_protocol::Type::Custom( + std::format!( + #template, + #(#fields),* + ) + .into_boxed_str() + ) + } + } + Fields::Unit => quote!(nu_protocol::Type::Nothing), + }; + + quote! { + fn expected_type() -> nu_protocol::Type { + #ty + } + } +} + +/// Implements the `#[derive(FromValue)]` macro for enums. +/// +/// This function constructs the implementation of the `FromValue` trait for enums. +/// It is designed to be on the same level as [`derive_struct_from_value`], even though this +/// function only provides the impl signature for `FromValue`. +/// The actual implementation for `FromValue::from_value` is handled by [`enum_from_value`]. +/// +/// Since variants are difficult to type with the current type system, this function uses the +/// default implementation for `expected_type`. +fn derive_enum_from_value( + ident: Ident, + data: DataEnum, + generics: Generics, + attrs: Vec, +) -> Result { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let from_value_impl = enum_from_value(&data, &attrs)?; + Ok(quote! { + #[automatically_derived] + impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause { + #from_value_impl + } + }) +} + +/// Implements `FromValue::from_value` for enums. +/// +/// This function constructs the `from_value` implementation for enums. +/// It only accepts enums with unit variants, as it is currently unclear how other types of enums +/// should be represented via a `Value`. +/// This function checks that every field is a unit variant and constructs a match statement over +/// all possible variants. +/// The input value is expected to be a `Value::String` containing the name of the variant formatted +/// as defined by the `#[nu_value(rename_all = "...")]` attribute. +/// If no attribute is given, [`convert_case::Case::Snake`] is expected. +/// +/// If no matching variant is found, `ShellError::CantConvert` is returned. +/// +/// This is how such a derived implementation looks: +/// ```rust +/// #[derive(IntoValue)] +/// enum Weather { +/// Sunny, +/// Cloudy, +/// Raining +/// } +/// +/// impl nu_protocol::IntoValue for Weather { +/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { +/// let span = v.span(); +/// let ty = v.get_type(); +/// +/// let s = v.into_string()?; +/// match s.as_str() { +/// "sunny" => std::result::Ok(Self::Sunny), +/// "cloudy" => std::result::Ok(Self::Cloudy), +/// "raining" => std::result::Ok(Self::Raining), +/// _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert { +/// to_type: std::string::ToString::to_string( +/// &::expected_type() +/// ), +/// from_type: std::string::ToString::to_string(&ty), +/// span: span,help: std::option::Option::None, +/// }), +/// } +/// } +/// } +/// ``` +fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result { + let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?; + let arms: Vec = data + .variants + .iter() + .map(|variant| { + attributes::deny(&variant.attrs)?; + let ident = &variant.ident; + let ident_s = format!("{ident}") + .as_str() + .to_case(container_attrs.rename_all); + match &variant.fields { + Fields::Named(fields) => Err(DeriveError::UnsupportedEnums { + fields_span: fields.span(), + }), + Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums { + fields_span: fields.span(), + }), + Fields::Unit => Ok(quote!(#ident_s => std::result::Result::Ok(Self::#ident))), + } + }) + .collect::>()?; + + Ok(quote! { + fn from_value( + v: nu_protocol::Value + ) -> std::result::Result { + let span = v.span(); + let ty = v.get_type(); + + let s = v.into_string()?; + match s.as_str() { + #(#arms,)* + _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert { + to_type: std::string::ToString::to_string( + &::expected_type() + ), + from_type: std::string::ToString::to_string(&ty), + span: span, + help: std::option::Option::None, + }), + } + } + }) +} + +/// Parses a `Value` into self. +/// +/// This function handles the actual parsing of a `Value` into self. +/// It takes two parameters: `fields` and `self_ident`. +/// The `fields` parameter determines the expected type of `Value`: named fields expect a +/// `Value::Record`, unnamed fields expect a `Value::List`, and a unit expects `Value::Nothing`. +/// +/// For named fields, the `fields` parameter indicates which field in the record corresponds to +/// which struct field. +/// For both named and unnamed fields, it also helps cast the type into a `FromValue` type. +/// This approach maintains +/// [hygiene](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene). +/// +/// The `self_ident` parameter is used to describe the identifier of the returned value. +/// For structs, `Self` is usually sufficient, but for enums, `Self::Variant` may be needed in the +/// future. +/// +/// This function is more complex than the equivalent for `IntoValue` due to error handling +/// requirements. +/// For missing fields, `ShellError::CantFindColumn` is used, and for unit structs, +/// `ShellError::CantConvert` is used. +/// The implementation avoids local variables for fields to prevent accidental shadowing, ensuring +/// that poorly named fields don't cause issues. +/// While this style is not typically recommended in handwritten Rust, it is acceptable for code +/// generation. +fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenStream2 { + match fields { + Fields::Named(fields) => { + let fields = fields.named.iter().map(|field| { + let ident = field.ident.as_ref().expect("named has idents"); + let ident_s = ident.to_string(); + let ty = &field.ty; + match type_is_option(ty) { + true => quote! { + #ident: record + .remove(#ident_s) + .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! { + let span = v.span(); + let mut record = v.into_record()?; + std::result::Result::Ok(#self_ident {#(#fields),*}) + } + } + Fields::Unnamed(fields) => { + let fields = fields.unnamed.iter().enumerate().map(|(i, field)| { + let ty = &field.ty; + quote! {{ + <#ty as nu_protocol::FromValue>::from_value( + deque + .pop_front() + .ok_or_else(|| nu_protocol::ShellError::CantFindColumn { + col_name: std::string::ToString::to_string(&#i), + span: std::option::Option::None, + src_span: span + })?, + )? + }} + }); + quote! { + let span = v.span(); + let list = v.into_list()?; + let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list); + std::result::Result::Ok(#self_ident(#(#fields),*)) + } + } + Fields::Unit => quote! { + match v { + nu_protocol::Value::Nothing {..} => Ok(#self_ident), + v => std::result::Result::Err(nu_protocol::ShellError::CantConvert { + to_type: std::string::ToString::to_string(&::expected_type()), + from_type: std::string::ToString::to_string(&v.get_type()), + span: v.span(), + help: std::option::Option::None + }) + } + }, + } +} + +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) +} diff --git a/crates/nu-derive-value/src/into.rs b/crates/nu-derive-value/src/into.rs new file mode 100644 index 0000000000..a7cec06378 --- /dev/null +++ b/crates/nu-derive-value/src/into.rs @@ -0,0 +1,266 @@ +use convert_case::Casing; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; +use syn::{ + spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, + Index, +}; + +use crate::attributes::{self, ContainerAttributes}; + +#[derive(Debug)] +pub struct IntoValue; +type DeriveError = super::error::DeriveError; + +/// Inner implementation of the `#[derive(IntoValue)]` macro for structs and enums. +/// +/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`. +/// +/// This function directs the `IntoValue` trait derivation to the correct implementation based on +/// the input type: +/// - For structs: [`struct_into_value`] +/// - For enums: [`enum_into_value`] +/// - Unions are not supported and will return an error. +pub fn derive_into_value(input: TokenStream2) -> Result { + let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?; + match input.data { + Data::Struct(data_struct) => Ok(struct_into_value( + input.ident, + data_struct, + input.generics, + input.attrs, + )?), + Data::Enum(data_enum) => Ok(enum_into_value( + input.ident, + data_enum, + input.generics, + input.attrs, + )?), + Data::Union(_) => Err(DeriveError::UnsupportedUnions), + } +} + +/// Implements the `#[derive(IntoValue)]` macro for structs. +/// +/// Automatically derives the `IntoValue` trait for any struct where each field implements +/// `IntoValue`. +/// For structs with named fields, the derived implementation creates a `Value::Record` using the +/// struct fields as keys. +/// Each field value is converted using the `IntoValue::into_value` method. +/// For structs with unnamed fields, this generates a `Value::List` with each field in the list. +/// For unit structs, this generates `Value::Nothing`, because there is no data. +/// +/// Note: The helper attribute `#[nu_value(...)]` is currently not allowed on structs. +/// +/// # Examples +/// +/// These examples show what the macro would generate. +/// +/// Struct with named fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Pet { +/// name: String, +/// age: u8, +/// favorite_toy: Option, +/// } +/// +/// impl nu_protocol::IntoValue for Pet { +/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { +/// nu_protocol::Value::record(nu_protocol::record! { +/// "name" => nu_protocol::IntoValue::into_value(self.name, span), +/// "age" => nu_protocol::IntoValue::into_value(self.age, span), +/// "favorite_toy" => nu_protocol::IntoValue::into_value(self.favorite_toy, span), +/// }, span) +/// } +/// } +/// ``` +/// +/// Struct with unnamed fields: +/// ```rust +/// #[derive(IntoValue)] +/// struct Color(u8, u8, u8); +/// +/// impl nu_protocol::IntoValue for Color { +/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { +/// nu_protocol::Value::list(vec![ +/// nu_protocol::IntoValue::into_value(self.0, span), +/// nu_protocol::IntoValue::into_value(self.1, span), +/// nu_protocol::IntoValue::into_value(self.2, span), +/// ], span) +/// } +/// } +/// ``` +/// +/// Unit struct: +/// ```rust +/// #[derive(IntoValue)] +/// struct Unicorn; +/// +/// impl nu_protocol::IntoValue for Unicorn { +/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { +/// nu_protocol::Value::nothing(span) +/// } +/// } +/// ``` +fn struct_into_value( + ident: Ident, + data: DataStruct, + generics: Generics, + attrs: Vec, +) -> Result { + attributes::deny(&attrs)?; + attributes::deny_fields(&data.fields)?; + let record = match &data.fields { + Fields::Named(fields) => { + let accessor = fields + .named + .iter() + .map(|field| field.ident.as_ref().expect("named has idents")) + .map(|ident| quote!(self.#ident)); + fields_return_value(&data.fields, accessor) + } + Fields::Unnamed(fields) => { + let accessor = fields + .unnamed + .iter() + .enumerate() + .map(|(n, _)| Index::from(n)) + .map(|index| quote!(self.#index)); + fields_return_value(&data.fields, accessor) + } + Fields::Unit => quote!(nu_protocol::Value::nothing(span)), + }; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + Ok(quote! { + #[automatically_derived] + impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause { + fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { + #record + } + } + }) +} + +/// Implements the `#[derive(IntoValue)]` macro for enums. +/// +/// This function implements the derive macro `IntoValue` for enums. +/// Currently, only unit enum variants are supported as it is not clear how other types of enums +/// should be represented in a `Value`. +/// For simple enums, we represent the enum as a `Value::String`. For other types of variants, we return an error. +/// The variant name will be case-converted as described by the `#[nu_value(rename_all = "...")]` helper attribute. +/// If no attribute is used, the default is `case_convert::Case::Snake`. +/// The implementation matches over all variants, uses the appropriate variant name, and constructs a `Value::String`. +/// +/// This is how such a derived implementation looks: +/// ```rust +/// #[derive(IntoValue)] +/// enum Weather { +/// Sunny, +/// Cloudy, +/// Raining +/// } +/// +/// impl nu_protocol::IntoValue for Weather { +/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { +/// match self { +/// Self::Sunny => nu_protocol::Value::string("sunny", span), +/// Self::Cloudy => nu_protocol::Value::string("cloudy", span), +/// Self::Raining => nu_protocol::Value::string("raining", span), +/// } +/// } +/// } +/// ``` +fn enum_into_value( + ident: Ident, + data: DataEnum, + generics: Generics, + attrs: Vec, +) -> Result { + let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?; + let arms: Vec = data + .variants + .into_iter() + .map(|variant| { + attributes::deny(&variant.attrs)?; + let ident = variant.ident; + let ident_s = format!("{ident}") + .as_str() + .to_case(container_attrs.rename_all); + match &variant.fields { + // In the future we can implement more complexe enums here. + Fields::Named(fields) => Err(DeriveError::UnsupportedEnums { + fields_span: fields.span(), + }), + Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums { + fields_span: fields.span(), + }), + Fields::Unit => { + Ok(quote!(Self::#ident => nu_protocol::Value::string(#ident_s, span))) + } + } + }) + .collect::>()?; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + Ok(quote! { + impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause { + fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value { + match self { + #(#arms,)* + } + } + } + }) +} + +/// Constructs the final `Value` that the macro generates. +/// +/// This function handles the construction of the final `Value` that the macro generates. +/// It is currently only used for structs but may be used for enums in the future. +/// The function takes two parameters: the `fields`, which allow iterating over each field of a data +/// type, and the `accessor`. +/// The fields determine whether we need to generate a `Value::Record`, `Value::List`, or +/// `Value::Nothing`. +/// For named fields, they are also directly used to generate the record key. +/// +/// The `accessor` parameter generalizes how the data is accessed. +/// For named fields, this is usually the name of the fields preceded by `self` in a struct, and +/// maybe something else for enums. +/// For unnamed fields, this should be an iterator similar to the one with named fields, but +/// accessing tuple fields, so we get `self.n`. +/// For unit structs, this parameter is ignored. +/// By using the accessor like this, we can have the same code for structs and enums with data +/// variants in the future. +fn fields_return_value( + fields: &Fields, + accessor: impl Iterator, +) -> TokenStream2 { + match fields { + Fields::Named(fields) => { + let items: Vec = fields + .named + .iter() + .zip(accessor) + .map(|(field, accessor)| { + let ident = field.ident.as_ref().expect("named has idents"); + let field = ident.to_string(); + quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span)) + }) + .collect(); + quote! { + nu_protocol::Value::record(nu_protocol::record! { + #(#items),* + }, span) + } + } + Fields::Unnamed(fields) => { + let items = + fields.unnamed.iter().zip(accessor).map( + |(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)), + ); + quote!(nu_protocol::Value::list(std::vec![#(#items),*], span)) + } + Fields::Unit => quote!(nu_protocol::Value::nothing(span)), + } +} diff --git a/crates/nu-derive-value/src/lib.rs b/crates/nu-derive-value/src/lib.rs new file mode 100644 index 0000000000..269566fc88 --- /dev/null +++ b/crates/nu-derive-value/src/lib.rs @@ -0,0 +1,69 @@ +//! Macro implementations of `#[derive(FromValue, IntoValue)]`. +//! +//! As this crate is a [`proc_macro`] crate, it is only allowed to export +//! [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html). +//! Therefore, it only exports [`IntoValue`] and [`FromValue`]. +//! +//! To get documentation for other functions and types used in this crate, run +//! `cargo doc -p nu-derive-value --document-private-items`. +//! +//! This crate uses a lot of +//! [`proc_macro2::TokenStream`](https://docs.rs/proc-macro2/1.0.24/proc_macro2/struct.TokenStream.html) +//! as `TokenStream2` to allow testing the behavior of the macros directly, including the output +//! token stream or if the macro errors as expected. +//! The tests for functionality can be found in `nu_protocol::value::test_derive`. +//! +//! This documentation is often less reference-heavy than typical Rust documentation. +//! This is because this crate is a dependency for `nu_protocol`, and linking to it would create a +//! cyclic dependency. +//! Also all examples in the documentation aren't tested as this crate cannot be compiled as a +//! normal library very easily. +//! This might change in the future if cargo allows building a proc-macro crate differently for +//! `cfg(doctest)` as they are already doing for `cfg(test)`. +//! +//! The generated code from the derive macros tries to be as +//! [hygienic](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene) as possible. +//! This ensures that the macro can be called anywhere without requiring specific imports. +//! This results in obtuse code, which isn't recommended for manual, handwritten Rust +//! but ensures that no other code may influence this generated code or vice versa. + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::{proc_macro_error, Diagnostic}; + +mod attributes; +mod error; +mod from; +mod into; +#[cfg(test)] +mod tests; + +const HELPER_ATTRIBUTE: &str = "nu_value"; + +/// Derive macro generating an impl of the trait `IntoValue`. +/// +/// For further information, see the docs on the trait itself. +#[proc_macro_derive(IntoValue, attributes(nu_value))] +#[proc_macro_error] +pub fn derive_into_value(input: TokenStream) -> TokenStream { + let input = TokenStream2::from(input); + let output = match into::derive_into_value(input) { + Ok(output) => output, + Err(e) => Diagnostic::from(e).abort(), + }; + TokenStream::from(output) +} + +/// Derive macro generating an impl of the trait `FromValue`. +/// +/// For further information, see the docs on the trait itself. +#[proc_macro_derive(FromValue, attributes(nu_value))] +#[proc_macro_error] +pub fn derive_from_value(input: TokenStream) -> TokenStream { + let input = TokenStream2::from(input); + let output = match from::derive_from_value(input) { + Ok(output) => output, + Err(e) => Diagnostic::from(e).abort(), + }; + TokenStream::from(output) +} diff --git a/crates/nu-derive-value/src/tests.rs b/crates/nu-derive-value/src/tests.rs new file mode 100644 index 0000000000..b94d12a732 --- /dev/null +++ b/crates/nu-derive-value/src/tests.rs @@ -0,0 +1,157 @@ +// These tests only check that the derive macros throw the relevant errors. +// Functionality of the derived types is tested in nu_protocol::value::test_derive. + +use crate::error::DeriveError; +use crate::from::derive_from_value; +use crate::into::derive_into_value; +use quote::quote; + +#[test] +fn unsupported_unions() { + let input = quote! { + #[nu_value] + union SomeUnion { + f1: u32, + f2: f32, + } + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::UnsupportedUnions)), + "expected `DeriveError::UnsupportedUnions`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::UnsupportedUnions)), + "expected `DeriveError::UnsupportedUnions`, got {:?}", + into_res + ); +} + +#[test] +fn unsupported_enums() { + let input = quote! { + #[nu_value(rename_all = "SCREAMING_SNAKE_CASE")] + enum ComplexEnum { + Unit, + Unnamed(u32, f32), + Named { + u: u32, + f: f32, + } + } + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::UnsupportedEnums { .. })), + "expected `DeriveError::UnsupportedEnums`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::UnsupportedEnums { .. })), + "expected `DeriveError::UnsupportedEnums`, got {:?}", + into_res + ); +} + +#[test] +fn unexpected_attribute() { + let input = quote! { + #[nu_value(what)] + enum SimpleEnum { + A, + B, + } + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::UnexpectedAttribute { .. })), + "expected `DeriveError::UnexpectedAttribute`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::UnexpectedAttribute { .. })), + "expected `DeriveError::UnexpectedAttribute`, got {:?}", + into_res + ); +} + +#[test] +fn deny_attribute_on_structs() { + let input = quote! { + #[nu_value] + struct SomeStruct; + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })), + "expected `DeriveError::InvalidAttributePosition`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })), + "expected `DeriveError::InvalidAttributePosition`, got {:?}", + into_res + ); +} + +#[test] +fn deny_attribute_on_fields() { + let input = quote! { + struct SomeStruct { + #[nu_value] + field: () + } + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })), + "expected `DeriveError::InvalidAttributePosition`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })), + "expected `DeriveError::InvalidAttributePosition`, got {:?}", + into_res + ); +} + +#[test] +fn invalid_attribute_value() { + let input = quote! { + #[nu_value(rename_all = "CrazY-CasE")] + enum SimpleEnum { + A, + B + } + }; + + let from_res = derive_from_value(input.clone()); + assert!( + matches!(from_res, Err(DeriveError::InvalidAttributeValue { .. })), + "expected `DeriveError::InvalidAttributeValue`, got {:?}", + from_res + ); + + let into_res = derive_into_value(input); + assert!( + matches!(into_res, Err(DeriveError::InvalidAttributeValue { .. })), + "expected `DeriveError::InvalidAttributeValue`, got {:?}", + into_res + ); +} diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 7bf5ffe9d5..3e6c3f787b 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -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.1" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.94.1" } -nu-path = { path = "../nu-path", version = "0.94.1" } -nu-glob = { path = "../nu-glob", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } +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 = [] \ No newline at end of file diff --git a/crates/nu-engine/src/command_prelude.rs b/crates/nu-engine/src/command_prelude.rs index 112f280db5..5c21af27e0 100644 --- a/crates/nu-engine/src/command_prelude.rs +++ b/crates/nu-engine/src/command_prelude.rs @@ -1,7 +1,7 @@ pub use crate::CallExt; pub use nu_protocol::{ ast::{Call, CellPath}, - engine::{Command, EngineState, Stack}, + engine::{Command, EngineState, Stack, StateWorkingSet}, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 3cd1130060..a7d4950036 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -2,9 +2,9 @@ use crate::eval_call; use nu_protocol::{ ast::{Argument, Call, Expr, Expression, RecordItem}, debugger::WithoutDebug, - engine::{Command, EngineState, Stack}, - record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Type, - Value, + engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID}, + record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned, + SyntaxShape, Type, Value, }; use std::{collections::HashMap, fmt::Write}; @@ -296,6 +296,28 @@ fn get_documentation( } if let Some(result) = &example.result { + let mut table_call = Call::new(Span::unknown()); + if example.example.ends_with("--collapse") { + // collapse the result + table_call.add_named(( + Spanned { + item: "collapse".to_string(), + span: Span::unknown(), + }, + None, + None, + )) + } else { + // expand the result + table_call.add_named(( + Spanned { + item: "expand".to_string(), + span: Span::unknown(), + }, + None, + None, + )) + } let table = engine_state .find_decl("table".as_bytes(), &[]) .and_then(|decl_id| { @@ -304,7 +326,7 @@ fn get_documentation( .run( engine_state, stack, - &Call::new(Span::new(0, 0)), + &table_call, PipelineData::Value(result.clone(), None), ) .ok() @@ -339,8 +361,9 @@ fn get_ansi_color_for_component_or_default( if let Some(color) = &engine_state.get_config().color_config.get(theme_component) { let caller_stack = &mut Stack::new().capture(); let span = Span::unknown(); + let span_id = UNKNOWN_SPAN_ID; - let argument_opt = get_argument_for_color_value(engine_state, color, span); + let argument_opt = get_argument_for_color_value(engine_state, color, span, span_id); // Call ansi command using argument if let Some(argument) = argument_opt { @@ -371,6 +394,7 @@ fn get_argument_for_color_value( engine_state: &EngineState, color: &&Value, span: Span, + span_id: SpanId, ) -> Option { match color { Value::Record { val, .. } => { @@ -378,43 +402,43 @@ fn get_argument_for_color_value( .iter() .map(|(k, v)| { RecordItem::Pair( - Expression { - expr: Expr::String(k.clone()), + Expression::new_existing( + Expr::String(k.clone()), span, - ty: Type::String, - custom_completion: None, - }, - Expression { - expr: Expr::String( + span_id, + Type::String, + ), + Expression::new_existing( + Expr::String( v.clone().to_expanded_string("", engine_state.get_config()), ), span, - ty: Type::String, - custom_completion: None, - }, + span_id, + Type::String, + ), ) }) .collect(); - Some(Argument::Positional(Expression { - span: Span::unknown(), - ty: Type::Record( + Some(Argument::Positional(Expression::new_existing( + Expr::Record(record_exp), + Span::unknown(), + UNKNOWN_SPAN_ID, + Type::Record( [ ("fg".to_string(), Type::String), ("attr".to_string(), Type::String), ] .into(), ), - expr: Expr::Record(record_exp), - custom_completion: None, - })) + ))) } - Value::String { val, .. } => Some(Argument::Positional(Expression { - span: Span::unknown(), - ty: Type::String, - expr: Expr::String(val.clone()), - custom_completion: None, - })), + Value::String { val, .. } => Some(Argument::Positional(Expression::new_existing( + Expr::String(val.clone()), + Span::unknown(), + UNKNOWN_SPAN_ID, + Type::String, + ))), _ => None, } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index af051a1dc7..044bf86980 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -208,11 +208,13 @@ fn eval_external( ) -> Result { let decl_id = engine_state .find_decl("run-external".as_bytes(), &[]) - .ok_or(ShellError::ExternalNotSupported { span: head.span })?; + .ok_or(ShellError::ExternalNotSupported { + span: head.span(&engine_state), + })?; let command = engine_state.get_decl(decl_id); - let mut call = Call::new(head.span); + let mut call = Call::new(head.span(&engine_state)); call.add_positional(head.clone()); @@ -712,7 +714,7 @@ impl Eval for EvalRuntime { args: &[ExternalArgument], _: Span, ) -> Result { - let span = head.span; + let span = head.span(&engine_state); // FIXME: protect this collect with ctrl-c eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span) } @@ -779,9 +781,11 @@ impl Eval for EvalRuntime { let var_info = engine_state.get_var(*var_id); if var_info.mutable { stack.add_var(*var_id, rhs); - Ok(Value::nothing(lhs.span)) + Ok(Value::nothing(lhs.span(&engine_state))) } else { - Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span }) + Err(ShellError::AssignmentRequiresMutableVar { + lhs_span: lhs.span(&engine_state), + }) } } Expr::FullCellPath(cell_path) => { @@ -797,7 +801,7 @@ impl Eval for EvalRuntime { // Reject attempts to assign to the entire $env if cell_path.tail.is_empty() { return Err(ShellError::CannotReplaceEnv { - span: cell_path.head.span, + span: cell_path.head.span(&engine_state), }); } @@ -837,15 +841,21 @@ impl Eval for EvalRuntime { lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?; stack.add_var(*var_id, lhs); } - Ok(Value::nothing(cell_path.head.span)) + Ok(Value::nothing(cell_path.head.span(&engine_state))) } else { - Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span }) + Err(ShellError::AssignmentRequiresMutableVar { + lhs_span: lhs.span(&engine_state), + }) } } - _ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }), + _ => Err(ShellError::AssignmentRequiresVar { + lhs_span: lhs.span(&engine_state), + }), } } - _ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }), + _ => Err(ShellError::AssignmentRequiresVar { + lhs_span: lhs.span(&engine_state), + }), } } @@ -882,8 +892,8 @@ impl Eval for EvalRuntime { Ok(Value::string(name, span)) } - fn unreachable(expr: &Expression) -> Result { - Ok(Value::nothing(expr.span)) + fn unreachable(engine_state: &EngineState, expr: &Expression) -> Result { + Ok(Value::nothing(expr.span(&engine_state))) } } diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 006e301859..6d0816ee3e 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -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.1" +version = "0.95.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.94.1" } -nu-parser = { path = "../nu-parser", version = "0.94.1" } -nu-color-config = { path = "../nu-color-config", version = "0.94.1" } -nu-engine = { path = "../nu-engine", version = "0.94.1" } -nu-table = { path = "../nu-table", version = "0.94.1" } -nu-json = { path = "../nu-json", version = "0.94.1" } -nu-utils = { path = "../nu-utils", version = "0.94.1" } +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.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } anyhow = { workspace = true } log = { workspace = true } diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index d7d75af6c5..cbb86336af 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -1,7 +1,7 @@ use super::ViewCommand; use crate::{ nu_common::{self, collect_input}, - views::Preview, + views::{Preview, ViewConfig}, }; use anyhow::Result; use nu_color_config::StyleComputer; @@ -43,6 +43,7 @@ impl ViewCommand for ExpandCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + _: &ViewConfig, ) -> Result { if let Some(value) = value { let value_as_string = convert_value_to_string(value, engine_state, stack)?; diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 8be468383e..684e713939 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -1,5 +1,5 @@ use super::ViewCommand; -use crate::views::Preview; +use crate::views::{Preview, ViewConfig}; use anyhow::Result; use nu_ansi_term::Color; use nu_protocol::{ @@ -99,7 +99,13 @@ impl ViewCommand for HelpCmd { Ok(()) } - fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option) -> Result { + fn spawn( + &mut self, + _: &EngineState, + _: &mut Stack, + _: Option, + _: &ViewConfig, + ) -> Result { Ok(HelpCmd::view()) } } diff --git a/crates/nu-explore/src/commands/mod.rs b/crates/nu-explore/src/commands/mod.rs index 141dc25f56..a748ddaaa3 100644 --- a/crates/nu-explore/src/commands/mod.rs +++ b/crates/nu-explore/src/commands/mod.rs @@ -1,3 +1,5 @@ +use crate::views::ViewConfig; + use super::pager::{Pager, Transition}; use anyhow::Result; use nu_protocol::{ @@ -49,5 +51,6 @@ pub trait ViewCommand { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result; } diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index 1310dfbd1f..84e1fb9706 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -27,7 +27,7 @@ impl NuCmd { } impl ViewCommand for NuCmd { - type View = NuView<'static>; + type View = NuView; fn name(&self) -> &'static str { Self::NAME @@ -48,6 +48,7 @@ impl ViewCommand for NuCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); @@ -62,22 +63,22 @@ impl ViewCommand for NuCmd { return Ok(NuView::Preview(Preview::new(&text))); } - let mut view = RecordView::new(columns, values); + let mut view = RecordView::new(columns, values, config.explore_config.clone()); if is_record { - view.set_orientation_current(Orientation::Left); + view.set_top_layer_orientation(Orientation::Left); } Ok(NuView::Records(view)) } } -pub enum NuView<'a> { - Records(RecordView<'a>), +pub enum NuView { + Records(RecordView), Preview(Preview), } -impl View for NuView<'_> { +impl View for NuView { fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) { match self { NuView::Records(v) => v.draw(f, area, cfg, layout), @@ -119,11 +120,4 @@ impl View for NuView<'_> { NuView::Preview(v) => v.exit(), } } - - fn setup(&mut self, config: ViewConfig<'_>) { - match self { - NuView::Records(v) => v.setup(config), - NuView::Preview(v) => v.setup(config), - } - } } diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index 39ef8f0f99..9ab1e39b98 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -1,10 +1,9 @@ use super::ViewCommand; use crate::{ nu_common::collect_input, - views::{Orientation, RecordView}, + views::{Orientation, RecordView, ViewConfig}, }; use anyhow::Result; -use nu_ansi_term::Style; use nu_protocol::{ engine::{EngineState, Stack}, Value, @@ -19,12 +18,6 @@ pub struct TableCmd { #[derive(Debug, Default, Clone)] struct TableSettings { orientation: Option, - split_line_s: Option