Compare commits

..

2 Commits

Author SHA1 Message Date
Stefan Holderbach
0120e4040d Bump to 0.77.1 development version (#8453)
# Description

Either to be used in an emergency point release or to indicate
development builds in the `version` command
2023-03-17 18:10:56 +13:00
WindSoilder
716c144e24 Revert "Throw out error if external command in subexpression is failed to run (#8204)" (#8475)
This reverts commit dec0a2517f.

It breaks programs like `fzf`

# Description

Fixes: #8472 
Fixes:  #8313
Reopen: #7690 

# User-Facing Changes

_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-17 17:42:26 +13:00
304 changed files with 3850 additions and 9435 deletions

View File

@@ -18,7 +18,6 @@ Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows

View File

@@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: cargo fmt
run: cargo fmt --all -- --check
@@ -77,14 +77,14 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
std-lib-and-python-virtualenv:
python-virtualenv:
env:
NU_LOG_LEVEL: DEBUG
NUSHELL_CARGO_TARGET: ci
strategy:
fail-fast: true
@@ -101,22 +101,10 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: Install Nushell
# prior to [*standard library: bring the tests into the main CI*](#8525)
# there was a `--profile ci` here in the `cargo install`, as well as
# `NUSHELL_CARGO_TARGET: ci` in the prelude above.
#
# this caused a "stackoverflow" error in the CI on windows,
# see [this failing job](https://github.com/nushell/nushell/actions/runs/4512034615/jobs/7944945590)
#
# the CI profile has been removed in 00b820de9021227d1910a9ea388297ee7aee308e
# as part of #8525.
run: cargo install --path . --locked --no-default-features
- name: Standard library tests
run: nu crates/nu-utils/standard_library/tests.nu
run: cargo install --locked --path=. --profile ci --no-default-features
- name: Setup Python
uses: actions/setup-python@v4
@@ -160,7 +148,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: Clippy
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
@@ -169,40 +157,40 @@ jobs:
run: cargo test --profile ci --package nu_plugin_*
# nu-coverage:
# needs: nu-tests
# env:
# NUSHELL_CARGO_TARGET: ci
nu-coverage:
needs: nu-tests
env:
NUSHELL_CARGO_TARGET: ci
# strategy:
# fail-fast: true
# matrix:
# # disabled mac due to problems with merging coverage and similarity to linux
# # disabled windows due to running out of disk space when having too many crates or tests
# platform: [ubuntu-20.04] # windows-latest
# rust:
# - stable
strategy:
fail-fast: true
matrix:
# disabled mac due to problems with merging coverage and similarity to linux
# disabled windows due to running out of disk space when having too many crates or tests
platform: [ubuntu-20.04] # windows-latest
rust:
- stable
# runs-on: ${{ matrix.platform }}
runs-on: ${{ matrix.platform }}
# steps:
# - uses: actions/checkout@v3
steps:
- uses: actions/checkout@v3
# - name: Setup Rust toolchain and cache
# uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
# - name: Install cargo-llvm-cov
# uses: taiki-e/install-action@cargo-llvm-cov
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
# - name: Tests
# shell: bash
# run: |
# source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
# cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
# cargo build --workspace --profile ci
# cargo test --workspace --profile ci
# cargo llvm-cov report --profile ci --lcov --output-path lcov.info
- name: Tests
shell: bash
run: |
source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
cargo build --workspace --profile ci
cargo test --workspace --profile ci
cargo llvm-cov report --profile ci --lcov --output-path lcov.info
# - name: Upload coverage reports to Codecov with GitHub Action
# uses: codecov/codecov-action@v3
# with:
# files: lcov.info
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v3
with:
files: lcov.info

View File

@@ -70,7 +70,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.4.4
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
- name: Setup Nushell
uses: hustcer/setup-nu@v3

3
.gitignore vendored
View File

@@ -22,9 +22,6 @@ debian/nu/
# VSCode's IDE items
.vscode/*
# JetBrains' Fleet IDE
.fleet/*
# Visual Studio Extension SourceGear Rust items
VSWorkspaceSettings.json
unstable_cargo_features.txt

View File

@@ -99,90 +99,3 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
cargo run --release -- --log-level trace --log-target file
open $"($nu.temp-path)/nu-($nu.pid).log"
```
## Git etiquette
As nushell thrives on its broad base of volunteer contributors and maintainers with different backgrounds we have a few guidelines for how we best utilize git and GitHub for our contributions. We strive to balance three goals with those recommendations:
1. The **volunteer maintainers and contributors** can easily follow the changes you propose, gauge the impact, and come to help you or make a decision.
2. **You as a contributor** can focus most of your time on improving the quality of the nushell project and contributing your expertise to the code or documentation.
3. Making sure we can trace back *why* decisions were made in the past.
This includes discarded approaches. Also we want to quickly identify regressions and fix when something broke.
### How we merge PRs
In general the maintainers **squash** all changes of your PR into a single commit when merging.
This keeps a clean enough linear history, while not forcing you to conform to a too strict style while iterating in your PR or fixing small problems. As an added benefit the commits on the `main` branch are tied to the discussion that happened in the PR through their `#1234` issue number.
> **Note**
> **Pro advice:** In some circumstances, we can agree on rebase-merging a particularly large but connected PR as a series of atomic commits onto the `main` branch to ensure we can more easily revert or bisect particular aspects.
### A good PR makes a change!
As a result of this PR-centric strategy and the general goal that the reviewers should easily understand your change, the **PR title and description matters** a great deal!
Make sure your description is **concise** but contains all relevant information and context.
This means demonstrating what changes, ideally through nushell code or output **examples**.
Furthermore links to technical documentation or instructions for folks that want to play with your change make the review process much easier.
> **Note**
> Try to follow the suggestions in our PR message template to make sure we can quickly focus on the technical merits and impact on the users.
#### A PR should limit itself to a single functional change or related set of same changes.
Mixing different changes in the same PR will make the review process much harder. A PR might get stuck on one aspect while we would actually like to land another change. Furthermore, if we are forced to revert a change, mixing and matching different aspects makes fixing bugs or regressions much harder.
Thus, please try to **separate out unrelated changes**!
**Don't** mix unrelated refactors with a potentially contested change.
Stylistic fixes and housekeeping can be bundled up into singular PRs.
#### Guidelines for the PR title
The PR title should be concise but contain everything for a contributor to know if they should help out in the review of this particular change.
**DON'T**
- `Update file/in/some/deeply/nested/path.rs`
- Why are you making this change?
- `Fix 2134`
- What has to be fixed?
- Hard to follow when not online on GitHub.
- ``Ignore `~` expansion``
- In what context should this change take effect?
- `[feature] refactor the whole parser and also make nushell indentation-sensitive, upgrade to using Cpython. Let me know what you think!`
- Be concise
- Maybe break up into smaller commits or PRs if the title already appears too long?
**DO**
- Mention the nushell feature or command that is affected.
- ``Fix URL parsing in `http get` (issue #1234)``
- You can mention the issue number if other context is there.
- In general, mention all related issues in the description to crosslink (e.g. `Fixes #1234`, `Closes #6789`)
- For internal changes mention the area or symbols affected if it helps to clarify
- ``Factor out `quote_string()` from parser to reuse in `explore` ``
### Review process / Merge conflicts
> **Note**
> Keep in mind that the maintainers are volunteers that need to allocate their attention to several different areas and active PRs. We will try to get back to you as soon as possible.
You can help us to make the review process a smooth experience:
- Testing:
- We generally review in detail after all the tests pass. Let us know if there is a problem you want to discuss to fix a test failure or forces us to accept a breaking change.
- If you fix a bug, it is highly recommended that you add a test that reproduces the original issue/panic in a minimal form.
- In general, added tests help us to understand which assumptions go into a particular addition/change.
- Try to also test corner cases where those assumptions might break. This can be more valuable than simply adding many similar tests.
- Commit history inside a PR during code review:
- Good **atomic commits** can help follow larger changes, but we are not pedantic.
- We don't shame fixup commits while you try to figure out a problem. They can help others see what you tried and what didn't work. (see our [squash policy](#how-we-merge-prs))
- During active review constant **force pushing** just to amend changes can be confusing!
- GitHub's UI presents reviewers with less options to compare diffs
- fetched branches for experimentation become invalid!
- the notification a maintainer receives has a low signal-to-noise ratio
- Git pros *can* use their judgement to rebase/squash to clean up the history *if it aids the understanding* of a larger change during review
- Merge conflicts:
- In general you should take care of resolving merge conflicts.
- Use your judgement whether to `git merge main` or to `git rebase main`
- Choose what simplifies having confidence in the conflict resolution and the review. **Merge commits in your branch are OK** in the squash model.
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)

246
Cargo.lock generated
View File

@@ -68,9 +68,9 @@ dependencies = [
[[package]]
name = "alphanumeric-sort"
version = "1.5.0"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e972aa01d34ded9a7fd0d838a1b6f73e70daf78ff77221c82bd411afa1f3fa9"
checksum = "77e9c9abb82613923ec78d7a461595d52491ba7240f3c64c0bbe0e6d98e0fce0"
[[package]]
name = "android_system_properties"
@@ -1739,12 +1739,6 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hex"
version = "0.4.3"
@@ -1969,18 +1963,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "is-terminal"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys 0.45.0",
]
[[package]]
name = "is_ci"
version = "1.1.1"
@@ -2284,9 +2266,9 @@ dependencies = [
[[package]]
name = "lru"
version = "0.10.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e"
checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17"
dependencies = [
"hashbrown 0.13.2",
]
@@ -2399,11 +2381,11 @@ dependencies = [
[[package]]
name = "miette"
version = "5.6.0"
version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07749fb52853e739208049fb513287c6f448de9103dfa78b05ae01f2fc5809bb"
checksum = "4afd9b301defa984bbdbe112b4763e093ed191750a0d914a78c1106b2d0fe703"
dependencies = [
"is-terminal",
"atty",
"miette-derive",
"once_cell",
"owo-colors",
@@ -2418,9 +2400,9 @@ dependencies = [
[[package]]
name = "miette-derive"
version = "5.6.0"
version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a07ad93a80d1b92bb44cb42d7c49b49c9aab1778befefad49cceb5e4c5bf460"
checksum = "97c2401ab7ac5282ca5c8b518a87635b1a93762b0b90b9990c509888eeccba29"
dependencies = [
"proc-macro2",
"quote",
@@ -2515,9 +2497,9 @@ dependencies = [
[[package]]
name = "mockito"
version = "1.0.0"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1eecc3baf782e3c8d6803cc8780268da1f32df6eb88c016c1d80b0df7944cf"
checksum = "406f43768da5a859ce19bb0978fd8dc2167a7d9a52f3935c6a187242e1a4ff9f"
dependencies = [
"assert-json-diff",
"colored",
@@ -2667,7 +2649,7 @@ dependencies = [
[[package]]
name = "nu"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"assert_cmd",
"atty",
@@ -2722,7 +2704,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"atty",
"chrono",
@@ -2751,7 +2733,7 @@ dependencies = [
[[package]]
name = "nu-cmd-lang"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"fancy-regex",
"itertools",
@@ -2764,12 +2746,11 @@ dependencies = [
"nu-test-support",
"nu-utils",
"shadow-rs",
"unicode-segmentation",
]
[[package]]
name = "nu-color-config"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"nu-ansi-term",
"nu-engine",
@@ -2783,7 +2764,7 @@ dependencies = [
[[package]]
name = "nu-command"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"Inflector",
"alphanumeric-sort",
@@ -2816,7 +2797,6 @@ dependencies = [
"log",
"lscolors",
"md-5",
"miette",
"mime",
"mime_guess",
"mockito",
@@ -2848,7 +2828,8 @@ dependencies = [
"polars",
"powierza-coefficient",
"print-positions",
"quick-xml 0.28.1",
"proptest",
"quick-xml 0.27.1",
"quickcheck",
"quickcheck_macros",
"rand 0.8.5",
@@ -2881,13 +2862,13 @@ dependencies = [
"uuid",
"wax",
"which",
"windows 0.46.0",
"windows",
"winreg",
]
[[package]]
name = "nu-engine"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"chrono",
"nu-glob",
@@ -2900,7 +2881,7 @@ dependencies = [
[[package]]
name = "nu-explore"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"ansi-str 0.7.2",
"crossterm 0.24.0",
@@ -2920,14 +2901,14 @@ dependencies = [
[[package]]
name = "nu-glob"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"doc-comment",
]
[[package]]
name = "nu-json"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"linked-hash-map",
"num-traits",
@@ -2936,7 +2917,7 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"bytesize",
"chrono",
@@ -2953,7 +2934,7 @@ dependencies = [
[[package]]
name = "nu-path"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"dirs-next",
"omnipath",
@@ -2962,7 +2943,7 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"bincode",
"nu-engine",
@@ -2975,7 +2956,7 @@ dependencies = [
[[package]]
name = "nu-pretty-hex"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"heapless",
"nu-ansi-term",
@@ -2984,7 +2965,7 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"byte-unit",
"chrono",
@@ -3007,7 +2988,7 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"atty",
"chrono",
@@ -3025,8 +3006,9 @@ dependencies = [
[[package]]
name = "nu-table"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"atty",
"json_to_table",
"nu-ansi-term",
"nu-color-config",
@@ -3039,7 +3021,7 @@ dependencies = [
[[package]]
name = "nu-term-grid"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"nu-utils",
"unicode-width",
@@ -3047,7 +3029,7 @@ dependencies = [
[[package]]
name = "nu-test-support"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"getset",
"hamcrest2",
@@ -3055,13 +3037,14 @@ dependencies = [
"nu-path",
"nu-utils",
"num-format",
"once_cell",
"tempfile",
"which",
]
[[package]]
name = "nu-utils"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"crossterm_winapi",
"log",
@@ -3083,7 +3066,7 @@ dependencies = [
[[package]]
name = "nu_plugin_example"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"nu-plugin",
"nu-protocol",
@@ -3091,7 +3074,7 @@ dependencies = [
[[package]]
name = "nu_plugin_formats"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"eml-parser",
"ical",
@@ -3104,7 +3087,7 @@ dependencies = [
[[package]]
name = "nu_plugin_gstat"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"git2",
"nu-engine",
@@ -3114,7 +3097,7 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"nu-plugin",
"nu-protocol",
@@ -3123,7 +3106,7 @@ dependencies = [
[[package]]
name = "nu_plugin_query"
version = "0.78.0"
version = "0.77.1"
dependencies = [
"gjson",
"nu-engine",
@@ -3322,18 +3305,18 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "open"
version = "4.0.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd61e3bf9d78956c72ee864bba52431f7f43994b21a17e9e72596a81bd61075b"
checksum = "21ecf2487e799604735754d2c5896106785987b441b5aee58f242e4d4963179a"
dependencies = [
"pathdiff",
]
[[package]]
name = "openssl"
version = "0.10.48"
version = "0.10.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@@ -3372,9 +3355,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.83"
version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg",
"cc",
@@ -4051,6 +4034,27 @@ dependencies = [
"rustix",
]
[[package]]
name = "proptest"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70"
dependencies = [
"bit-set",
"bitflags",
"byteorder",
"lazy_static",
"num-traits",
"quick-error 2.0.1",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "pure-rust-locales"
version = "0.5.6"
@@ -4073,6 +4077,12 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.25.0"
@@ -4085,9 +4095,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.28.1"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2"
checksum = "ffc053f057dd768a56f62cd7e434c42c831d296968997e9ac1f76ea7c2d14c41"
dependencies = [
"memchr",
]
@@ -4214,6 +4224,15 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rayon"
version = "1.7.0"
@@ -4258,9 +4277,9 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.18.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9be1b4ddfacccf35ea8b4a8d3f7ec9e494ea22969c4156d4b2a393c1b9cef"
checksum = "e3afcd7fc553d129e9a03d17f15c4b4748baa589570f01a372caae2de81989b8"
dependencies = [
"chrono",
"crossterm 0.24.0",
@@ -4334,9 +4353,9 @@ dependencies = [
[[package]]
name = "rstest"
version = "0.17.0"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
dependencies = [
"rstest_macros",
"rustc_version",
@@ -4344,9 +4363,9 @@ dependencies = [
[[package]]
name = "rstest_macros"
version = "0.17.0"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
dependencies = [
"cfg-if 1.0.0",
"proc-macro2",
@@ -4459,6 +4478,18 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "rusty-fork"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
dependencies = [
"fnv",
"quick-error 1.2.3",
"tempfile",
"wait-timeout",
]
[[package]]
name = "ryu"
version = "1.0.13"
@@ -4829,9 +4860,9 @@ dependencies = [
[[package]]
name = "sqlparser"
version = "0.32.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0366f270dbabb5cc2e4c88427dc4c08bba144f81e32fbd459a013f26a4d16aa0"
checksum = "db67dc6ef36edb658196c3fef0464a80b53dbbc194a904e81f9bd4190f9ecc5b"
dependencies = [
"log",
"serde",
@@ -4926,30 +4957,30 @@ dependencies = [
[[package]]
name = "supports-color"
version = "2.0.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f"
dependencies = [
"is-terminal",
"atty",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "2.0.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b4806e0b03b9906e76b018a5d821ebf198c8e9dc0829ed3328eeeb5094aed60"
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
dependencies = [
"is-terminal",
"atty",
]
[[package]]
name = "supports-unicode"
version = "2.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2"
dependencies = [
"is-terminal",
"atty",
]
[[package]]
@@ -4969,7 +5000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea"
dependencies = [
"peresil",
"quick-error",
"quick-error 1.2.3",
"sxd-document",
]
@@ -5369,7 +5400,7 @@ dependencies = [
"once_cell",
"scopeguard",
"url",
"windows 0.44.0",
"windows",
]
[[package]]
@@ -5433,6 +5464,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b0c16eadfb312c7acd6970fc97d1f3152eb536714a2ff72ca09a92cae6fa67"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicase"
version = "2.6.0"
@@ -5837,15 +5874,6 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
@@ -5872,9 +5900,9 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@@ -5887,45 +5915,45 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"

View File

@@ -10,7 +10,7 @@ license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.78.0"
version = "0.77.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -45,25 +45,25 @@ chrono = { version = "0.4.23", features = ["serde"] }
crossterm = "0.24.0"
ctrlc = "3.2.1"
log = "0.4"
miette = { version = "5.6.0", features = ["fancy-no-backtrace"] }
nu-cli = { path = "./crates/nu-cli", version = "0.78.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.78.0" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.78.0" }
nu-command = { path = "./crates/nu-command", version = "0.78.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.78.0" }
nu-json = { path = "./crates/nu-json", version = "0.78.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.78.0" }
nu-path = { path = "./crates/nu-path", version = "0.78.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.78.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.78.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.78.0" }
nu-system = { path = "./crates/nu-system", version = "0.78.0" }
nu-table = { path = "./crates/nu-table", version = "0.78.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.78.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.78.0" }
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
nu-cli = { path = "./crates/nu-cli", version = "0.77.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.77.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.77.1" }
nu-command = { path = "./crates/nu-command", version = "0.77.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.77.1" }
nu-json = { path = "./crates/nu-json", version = "0.77.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.77.1" }
nu-path = { path = "./crates/nu-path", version = "0.77.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.77.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.77.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.77.1" }
nu-system = { path = "./crates/nu-system", version = "0.77.1" }
nu-table = { path = "./crates/nu-table", version = "0.77.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.77.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.77.1" }
nu-ansi-term = "0.47.0"
reedline = { version = "0.18.0", features = ["bashisms", "sqlite"] }
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
rayon = "1.7.0"
is_executable = "1.0.1"
@@ -72,7 +72,7 @@ time = "0.3.12"
[target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows
openssl = { version = "0.10.48", features = ["vendored"], optional = true }
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false }
@@ -89,14 +89,14 @@ nix = { version = "0.26", default-features = false, features = [
atty = "0.2"
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.78.0" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.77.1" }
tempfile = "3.4.0"
assert_cmd = "2.0.2"
criterion = "0.4"
pretty_assertions = "1.0.0"
serial_test = "1.0.0"
hamcrest2 = "0.3.0"
rstest = { version = "0.17.0", default-features = false }
rstest = { version = "0.16.0", default-features = false }
itertools = "0.10.3"
[features]

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Copyright (c) 2019 - 2022 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

View File

@@ -6,7 +6,7 @@
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)](https://github.com/nushell/nushell/graphs/commit-activity)
[![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)](https://github.com/nushell/nushell/graphs/contributors)
<!-- [![codecov](https://codecov.io/gh/nushell/nushell/branch/main/graph/badge.svg?token=JheS8qu2II)](https://codecov.io/gh/nushell/nushell) -->
[![codecov](https://codecov.io/gh/nushell/nushell/branch/main/graph/badge.svg?token=JheS8qu2II)](https://codecov.io/gh/nushell/nushell)
A new type of shell.
@@ -175,8 +175,7 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins while the [showcase repo](https://github.com/nushell/showcase) *shows* off informative blog posts that have been written about Nushell along with videos that highlight technical
topics that have been presented.
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins.
## Goals

View File

@@ -20,6 +20,5 @@ for plugin in $plugins {
'----------------------------'
cd $'crates/($plugin)'
cargo build
cd ../../
ignore
}

View File

@@ -5,26 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.78.0"
version = "0.77.1"
[lib]
bench = false
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.78.0" }
rstest = { version = "0.17.0", default-features = false }
nu-test-support = { path = "../nu-test-support", version = "0.77.1" }
nu-command = { path = "../nu-command", version = "0.77.1" }
rstest = { version = "0.16.0", default-features = false }
[dependencies]
nu-command = { path = "../nu-command", version = "0.78.0" }
nu-engine = { path = "../nu-engine", version = "0.78.0" }
nu-path = { path = "../nu-path", version = "0.78.0" }
nu-parser = { path = "../nu-parser", version = "0.78.0" }
nu-protocol = { path = "../nu-protocol", version = "0.78.0" }
nu-utils = { path = "../nu-utils", version = "0.78.0" }
nu-color-config = { path = "../nu-color-config", version = "0.78.0" }
nu-engine = { path = "../nu-engine", version = "0.77.1" }
nu-path = { path = "../nu-path", version = "0.77.1" }
nu-parser = { path = "../nu-parser", version = "0.77.1" }
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
nu-utils = { path = "../nu-utils", version = "0.77.1" }
nu-color-config = { path = "../nu-color-config", version = "0.77.1" }
nu-ansi-term = "0.47.0"
reedline = { version = "0.18.0", features = ["bashisms", "sqlite"] }
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
atty = "0.2.14"
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
@@ -34,7 +34,7 @@ fuzzy-matcher = "0.3.7"
is_executable = "1.0.1"
once_cell = "1.17.0"
log = "0.4"
miette = { version = "5.6.0", features = ["fancy-no-backtrace"] }
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
percent-encoding = "2"
sysinfo = "0.28.2"
thiserror = "1.0.31"

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Copyright (c) 2019 - 2022 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

View File

@@ -1,6 +1,6 @@
use crate::util::report_error;
use log::info;
use miette::Result;
use nu_command::util::report_error;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_protocol::engine::Stack;

View File

@@ -1,5 +1,4 @@
use crate::util::eval_source;
use nu_command::util::report_error;
use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")]
use nu_parser::ParseError;
#[cfg(feature = "plugin")]

View File

@@ -1,8 +1,7 @@
use crate::util::eval_source;
use crate::util::{eval_source, report_error};
use log::info;
use log::trace;
use miette::{IntoDiagnostic, Result};
use nu_command::util::report_error;
use nu_engine::{convert_env_values, current_dir};
use nu_parser::parse;
use nu_path::canonicalize_with;

View File

@@ -18,13 +18,13 @@ pub use completions::{FileCompletion, NuCompleter};
pub use config_files::eval_config_contents;
pub use eval_file::evaluate_file;
pub use menus::{DescriptionMenu, NuHelpCompleter};
pub use nu_command::util::{get_init_cwd, report_error, report_error_new};
pub use nu_highlight::NuHighlight;
pub use print::Print;
pub use prompt::NushellPrompt;
pub use repl::evaluate_repl;
pub use repl::{eval_env_change_hook, eval_hook};
pub use syntax_highlight::NuHighlighter;
pub use util::{eval_source, gather_parent_env_vars};
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
pub use validation::NuValidator;
#[cfg(feature = "plugin")]

View File

@@ -1,6 +1,6 @@
use crate::util::report_error;
use crate::NushellPrompt;
use log::trace;
use nu_command::util::report_error;
use nu_engine::eval_subexpression;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},

View File

@@ -626,12 +626,9 @@ fn add_parsed_keybinding(
"shift" => KeyModifiers::SHIFT,
"alt" => KeyModifiers::ALT,
"none" => KeyModifiers::NONE,
"shift_alt" | "alt_shift" => KeyModifiers::SHIFT | KeyModifiers::ALT,
"control_shift" | "shift_control" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
"control_alt" | "alt_control" => KeyModifiers::CONTROL | KeyModifiers::ALT,
"control_alt_shift" | "control_shift_alt" => {
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
}
"control | shift" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
"control | alt" => KeyModifiers::CONTROL | KeyModifiers::ALT,
"control | alt | shift" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT,
_ => {
return Err(ShellError::UnsupportedConfigValue(
"CONTROL, SHIFT, ALT or NONE".to_string(),

View File

@@ -2,21 +2,21 @@ use crate::{
completions::NuCompleter,
prompt_update,
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
util::eval_source,
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
NuHighlighter, NuValidator, NushellPrompt,
};
use crossterm::cursor::CursorShape;
use log::{trace, warn};
use miette::{IntoDiagnostic, Result};
use nu_color_config::StyleComputer;
use nu_command::hook::eval_hook;
use nu_command::util::{get_guaranteed_cwd, report_error, report_error_new};
use nu_engine::{convert_env_values, eval_block};
use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
ast::PathMember,
config::NuCursorShape,
engine::{EngineState, Stack, StateWorkingSet},
format_duration, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
Spanned, Type, Value, VarId,
};
use nu_utils::utils::perf;
use reedline::{CursorConfig, DefaultHinter, EditCommand, Emacs, SqliteBackedHistory, Vi};
@@ -44,7 +44,6 @@ pub fn evaluate_repl(
prerun_command: Option<Spanned<String>>,
entire_start_time: Instant,
) -> Result<()> {
use nu_command::hook;
use reedline::{FileBackedHistory, Reedline, Signal};
let use_color = engine_state.get_config().use_ansi_coloring;
@@ -401,7 +400,7 @@ pub fn evaluate_repl(
// fire the "env_change" hook
let config = engine_state.get_config();
if let Err(error) =
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
{
report_error_new(engine_state, &error)
}
@@ -460,29 +459,20 @@ pub fn evaluate_repl(
.into_diagnostic()?; // todo: don't stop repl if error here?
}
// Right before we start running the code the user gave us, fire the `pre_execution`
// hook
if let Some(hook) = config.hooks.pre_execution.clone() {
// Set the REPL buffer to the current command for the "pre_execution" hook
let mut repl_buffer = engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex");
*repl_buffer = s.to_string();
drop(repl_buffer);
engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex")
.replace(line_editor.current_buffer_contents().to_string());
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
if let Some(hook) = config.hooks.pre_execution.clone() {
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
let mut repl_buffer = engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex");
*repl_buffer = line_editor.current_buffer_contents().to_string();
drop(repl_buffer);
if shell_integration {
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
}
@@ -638,23 +628,23 @@ pub fn evaluate_repl(
run_ansi_sequence(RESET_APPLICATION_MODE)?;
}
let mut repl_buffer = engine_state
.repl_buffer_state
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl buffer state mutex");
let mut repl_cursor_pos = engine_state
.repl_cursor_pos
.lock()
.expect("repl cursor pos mutex");
line_editor.run_edit_commands(&[
EditCommand::Clear,
EditCommand::InsertString(repl_buffer.to_string()),
EditCommand::MoveToPosition(*repl_cursor_pos),
]);
*repl_buffer = "".to_string();
drop(repl_buffer);
*repl_cursor_pos = 0;
drop(repl_cursor_pos);
.expect("repl op queue mutex");
while let Some(op) = ops.pop_front() {
match op {
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
EditCommand::MoveToEnd,
EditCommand::InsertString(s),
]),
ReplOperation::Insert(s) => {
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
}
ReplOperation::Replace(s) => line_editor
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
}
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
@@ -820,6 +810,341 @@ pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) ->
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
}
pub fn eval_env_change_hook(
env_change_hook: Option<Value>,
engine_state: &mut EngineState,
stack: &mut Stack,
) -> Result<(), ShellError> {
if let Some(hook) = env_change_hook {
match hook {
Value::Record {
cols: env_names,
vals: hook_values,
..
} => {
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
let before = engine_state
.previous_env_vars
.get(env_name)
.cloned()
.unwrap_or_default();
let after = stack
.get_env_var(engine_state, env_name)
.unwrap_or_default();
if before != after {
eval_hook(
engine_state,
stack,
None,
vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value,
)?;
engine_state
.previous_env_vars
.insert(env_name.to_string(), after);
}
}
}
x => {
return Err(ShellError::TypeMismatch {
err_message: "record for the 'env_change' hook".to_string(),
span: x.span()?,
});
}
}
}
Ok(())
}
pub fn eval_hook(
engine_state: &mut EngineState,
stack: &mut Stack,
input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
value: &Value,
) -> Result<PipelineData, ShellError> {
let value_span = value.span()?;
// Hooks can optionally be a record in this form:
// {
// condition: {|before, after| ... } # block that evaluates to true/false
// code: # block or a string
// }
// The condition block will be run to check whether the main hook (in `code`) should be run.
// If it returns true (the default if a condition block is not specified), the hook should be run.
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
};
let mut output = PipelineData::empty();
let code_path = PathMember::String {
val: "code".to_string(),
span: value_span,
};
match value {
Value::List { vals, .. } => {
for val in vals {
eval_hook(engine_state, stack, None, arguments.clone(), val)?;
}
}
Value::Record { .. } => {
let do_run_hook = if let Ok(condition) =
value
.clone()
.follow_cell_path(&[condition_path], false, false)
{
match condition {
Value::Block {
val: block_id,
span: block_span,
..
}
| Value::Closure {
val: block_id,
span: block_span,
..
} => {
match run_hook_block(
engine_state,
stack,
block_id,
None,
arguments.clone(),
block_span,
) {
Ok(pipeline_data) => {
if let PipelineData::Value(Value::Bool { val, .. }, ..) =
pipeline_data
{
val
} else {
return Err(ShellError::UnsupportedConfigValue(
"boolean output".to_string(),
"other PipelineData variant".to_string(),
block_span,
));
}
}
Err(err) => {
return Err(err);
}
}
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
} else {
// always run the hook
true
};
if do_run_hook {
match value.clone().follow_cell_path(&[code_path], false, false)? {
Value::String {
val,
span: source_span,
} => {
let (block, delta, vars) = {
let mut working_set = StateWorkingSet::new(engine_state);
let mut vars: Vec<(VarId, Value)> = vec![];
for (name, val) in arguments {
let var_id = working_set.add_variable(
name.as_bytes().to_vec(),
val.span()?,
Type::Any,
false,
);
vars.push((var_id, val));
}
let (output, err) =
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
if let Some(err) = err {
report_error(&working_set, &err);
return Err(ShellError::UnsupportedConfigValue(
"valid source code".into(),
"source code with syntax errors".into(),
source_span,
));
}
(output, working_set.render(), vars)
};
engine_state.merge_delta(delta)?;
let input = PipelineData::empty();
let var_ids: Vec<VarId> = vars
.into_iter()
.map(|(var_id, val)| {
stack.add_var(var_id, val);
var_id
})
.collect();
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
output = pipeline_data;
}
Err(err) => {
report_error_new(engine_state, &err);
}
}
for var_id in var_ids.iter() {
stack.vars.remove(var_id);
}
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
run_hook_block(
engine_state,
stack,
block_id,
input,
arguments,
block_span,
)?;
}
Value::Closure {
val: block_id,
span: block_span,
..
} => {
run_hook_block(
engine_state,
stack,
block_id,
input,
arguments,
block_span,
)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block or string".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
}
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
output = run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
*block_span,
)?;
}
Value::Closure {
val: block_id,
span: block_span,
..
} => {
output = run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
*block_span,
)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block, record, or list of records".into(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
Ok(output)
}
fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<PipelineData, ShellError> {
let block = engine_state.get_block(block_id);
let input = optional_input.unwrap_or_else(PipelineData::empty);
let mut callee_stack = stack.gather_captures(&block.captures);
for (idx, PositionalArg { var_id, .. }) in
block.signature.required_positional.iter().enumerate()
{
if let Some(var_id) = var_id {
if let Some(arg) = arguments.get(idx) {
callee_stack.add_var(*var_id, arg.1.clone())
} else {
return Err(ShellError::IncompatibleParametersSingle {
msg: "This hook block has too many parameters".into(),
span,
});
}
}
}
let pipeline_data =
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
if let PipelineData::Value(Value::Error { error }, _) = pipeline_data {
return Err(*error);
}
// If all went fine, preserve the environment of the called block
let caller_env_vars = stack.get_env_var_names(engine_state);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
stack.add_env_var(var, value);
}
Ok(pipeline_data)
}
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
io::stdout().write_all(seq.as_bytes()).map_err(|e| {
ShellError::GenericError(

View File

@@ -79,27 +79,29 @@ impl Highlighter for NuHighlighter {
}};
}
let mut add_colored_token = |shape: &FlatShape, text: String| {
output.push((get_shape_color(shape.to_string(), &self.config), text));
};
macro_rules! add_colored_token {
($shape:expr, $text:expr) => {
output.push((get_shape_color($shape.to_string(), &self.config), $text))
};
}
match shape.1 {
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
FlatShape::Binary => add_colored_token(&shape.1, next_token),
FlatShape::Bool => add_colored_token(&shape.1, next_token),
FlatShape::Int => add_colored_token(&shape.1, next_token),
FlatShape::Float => add_colored_token(&shape.1, next_token),
FlatShape::Range => add_colored_token(&shape.1, next_token),
FlatShape::InternalCall => add_colored_token(&shape.1, next_token),
FlatShape::External => add_colored_token(&shape.1, next_token),
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
FlatShape::Literal => add_colored_token(&shape.1, next_token),
FlatShape::Operator => add_colored_token(&shape.1, next_token),
FlatShape::Signature => add_colored_token(&shape.1, next_token),
FlatShape::String => add_colored_token(&shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
FlatShape::Binary => add_colored_token!(shape.1, next_token),
FlatShape::Bool => add_colored_token!(shape.1, next_token),
FlatShape::Int => add_colored_token!(shape.1, next_token),
FlatShape::Float => add_colored_token!(shape.1, next_token),
FlatShape::Range => add_colored_token!(shape.1, next_token),
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
FlatShape::External => add_colored_token!(shape.1, next_token),
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
FlatShape::Literal => add_colored_token!(shape.1, next_token),
FlatShape::Operator => add_colored_token!(shape.1, next_token),
FlatShape::Signature => add_colored_token!(shape.1, next_token),
FlatShape::String => add_colored_token!(shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
FlatShape::List => {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
@@ -114,17 +116,16 @@ impl Highlighter for NuHighlighter {
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
}
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
FlatShape::Directory => add_colored_token(&shape.1, next_token),
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
FlatShape::Variable => add_colored_token(&shape.1, next_token),
FlatShape::Flag => add_colored_token(&shape.1, next_token),
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
FlatShape::And => add_colored_token(&shape.1, next_token),
FlatShape::Or => add_colored_token(&shape.1, next_token),
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
FlatShape::Directory => add_colored_token!(shape.1, next_token),
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
FlatShape::Variable => add_colored_token!(shape.1, next_token),
FlatShape::Flag => add_colored_token!(shape.1, next_token),
FlatShape::Pipe => add_colored_token!(shape.1, next_token),
FlatShape::And => add_colored_token!(shape.1, next_token),
FlatShape::Or => add_colored_token!(shape.1, next_token),
FlatShape::Redirection => add_colored_token!(shape.1, next_token),
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
}
last_seen_span = shape.0.end;
}
@@ -307,8 +308,6 @@ fn find_matching_block_end_in_expr(
Expr::ImportPattern(_) => None,
Expr::Overlay(_) => None,
Expr::Signature(_) => None,
Expr::MatchPattern(_) => None,
Expr::MatchBlock(_) => None,
Expr::Nothing => None,
Expr::Garbage => None,

View File

@@ -1,8 +1,8 @@
use nu_command::hook::eval_hook;
use nu_command::util::{report_error, report_error_new};
use crate::repl::eval_hook;
use nu_engine::{eval_block, eval_block_with_early_return};
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
use nu_protocol::engine::StateWorkingSet;
use nu_protocol::CliError;
use nu_protocol::{
engine::{EngineState, Stack},
print_if_stream, PipelineData, ShellError, Span, Value,
@@ -10,7 +10,7 @@ use nu_protocol::{
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use nu_utils::utils::perf;
use std::path::Path;
use std::path::{Path, PathBuf};
// This will collect environment variables from std::env and adds them to a stack.
//
@@ -310,6 +310,43 @@ fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
);
}
pub fn report_error(
working_set: &StateWorkingSet,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
eprintln!("Error: {:?}", CliError(error, working_set));
// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
{
let _ = nu_utils::enable_vt_processing();
}
}
pub fn report_error_new(
engine_state: &EngineState,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, error);
}
pub fn get_init_cwd() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| {
std::env::var("PWD")
.map(Into::into)
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
})
}
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
})
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -6,17 +6,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021"
license = "MIT"
name = "nu-cmd-lang"
version = "0.78.0"
version = "0.77.1"
[lib]
bench = false
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.78.0" }
nu-engine = { path = "../nu-engine", version = "0.78.0" }
nu-parser = { path = "../nu-parser", version = "0.78.0" }
nu-protocol = { path = "../nu-protocol", version = "0.78.0" }
nu-utils = { path = "../nu-utils", version = "0.78.0" }
nu-color-config = { path = "../nu-color-config", version = "0.77.1" }
nu-engine = { path = "../nu-engine", version = "0.77.1" }
nu-parser = { path = "../nu-parser", version = "0.77.1" }
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
nu-utils = { path = "../nu-utils", version = "0.77.1" }
nu-ansi-term = "0.47.0"
@@ -24,10 +24,9 @@ fancy-regex = "0.11.0"
itertools = "0.10.0"
log = "0.4.14"
shadow-rs = { version = "0.21.0", default-features = false }
unicode-segmentation = "1.10.0"
[build-dependencies]
shadow-rs = { version = "0.21.0", default-features = false }
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.78.0" }
nu-test-support = { path="../nu-test-support", version = "0.77.1" }

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Copyright (c) 2019 - 2022 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

View File

@@ -1,10 +1,10 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::ReplOperation;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::IntoPipelineData;
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)]
pub struct Commandline;
@@ -17,11 +17,6 @@ impl Command for Commandline {
fn signature(&self) -> Signature {
Signature::build("commandline")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.switch(
"cursor",
"Set or get the current cursor position",
Some('c'),
)
.switch(
"append",
"appends the string to the end of the buffer",
@@ -61,77 +56,30 @@ impl Command for Commandline {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let mut buffer = engine_state
.repl_buffer_state
let mut ops = engine_state
.repl_operation_queue
.lock()
.expect("repl buffer state mutex");
let mut cursor_pos = engine_state
.repl_cursor_pos
.lock()
.expect("repl cursor pos mutex");
if call.has_flag("cursor") {
let cmd_str = cmd.as_string()?;
match cmd_str.parse::<i64>() {
Ok(n) => {
*cursor_pos = if n <= 0 {
0usize
} else {
buffer
.grapheme_indices(true)
.map(|(i, _c)| i)
.nth(n as usize)
.unwrap_or(buffer.len())
}
}
Err(_) => {
return Err(ShellError::CantConvert {
to_type: "int".to_string(),
from_type: "string".to_string(),
span: cmd.span()?,
help: Some(format!(
r#"string "{cmd_str}" does not represent a valid integer"#
)),
})
}
}
} else if call.has_flag("append") {
buffer.push_str(&cmd.as_string()?);
.expect("repl op queue mutex");
ops.push_back(if call.has_flag("append") {
ReplOperation::Append(cmd.as_string()?)
} else if call.has_flag("insert") {
let cmd_str = cmd.as_string()?;
buffer.insert_str(*cursor_pos, &cmd_str);
*cursor_pos += cmd_str.len();
ReplOperation::Insert(cmd.as_string()?)
} else {
*buffer = cmd.as_string()?;
*cursor_pos = buffer.len();
}
ReplOperation::Replace(cmd.as_string()?)
});
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
} else {
let buffer = engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex");
if call.has_flag("cursor") {
let cursor_pos = engine_state
.repl_cursor_pos
.lock()
.expect("repl cursor pos mutex");
let char_pos = buffer
.grapheme_indices(true)
.position(|(i, _c)| i == *cursor_pos)
.unwrap_or(buffer.len());
Ok(Value::String {
val: char_pos.to_string(),
span: call.head,
}
.into_pipeline_data())
} else {
Ok(Value::String {
val: buffer.to_string(),
span: call.head,
}
.into_pipeline_data())
} else if let Some(ref cmd) = *engine_state
.repl_buffer_state
.lock()
.expect("repl buffer state mutex")
{
Ok(Value::String {
val: cmd.clone(),
span: call.head,
}
.into_pipeline_data())
} else {
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
}
}
}

View File

@@ -21,7 +21,7 @@ impl Command for Const {
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by constant value",
)
.category(Category::Core)

View File

@@ -22,11 +22,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::Any, "the closure to run")
.input_output_types(vec![(Type::Any, Type::Any)])
.switch(
"ignore-errors",

View File

@@ -44,66 +44,43 @@ impl Command for ErrorMake {
let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned");
let throw_error = if unspanned { None } else { Some(span) };
Err(make_error(&arg, throw_error).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
if unspanned {
Err(make_error(&arg, None).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
} else {
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a simple custom error",
example: r#"error make {msg: "my custom error message"}"#,
result: Some(Value::Error {
error: Box::new(ShellError::GenericError(
"my custom error message".to_string(),
"".to_string(),
None,
None,
Vec::new(),
)),
}),
},
Example {
description: "Create a more complex custom error",
example: r#"error make {
msg: "my custom error message"
label: {
text: "my custom label text" # not mandatory unless $.label exists
start: 123 # not mandatory unless $.label.end is set
end: 456 # not mandatory unless $.label.start is set
}
}"#,
result: Some(Value::Error {
error: Box::new(ShellError::GenericError(
"my custom error message".to_string(),
"my custom label text".to_string(),
Some(Span::new(123, 456)),
None,
Vec::new(),
)),
}),
},
Example {
description:
"Create a custom error for a custom command that shows the span of the argument",
description: "Create a custom error for a custom command",
example: r#"def foo [x] {
let span = (metadata $x).span;
error make {
msg: "this is fishy"
label: {
text: "fish right here"
start: $span.start
end: $span.end
}
}
error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } }
}"#,
result: None,
},
Example {
description: "Create a simple custom error for a custom command",
example: r#"def foo [x] {
error make {msg: "this is fishy"}
}"#,
result: None,
},
@@ -112,7 +89,7 @@ impl Command for ErrorMake {
}
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
if let Value::Record { span, .. } = &value {
if let Value::Record { .. } = &value {
let msg = value.get_data_by_key("msg");
let label = value.get_data_by_key("label");
@@ -122,11 +99,6 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
let label_end = label.get_data_by_key("end");
let label_text = label.get_data_by_key("text");
let label_span = match label.span() {
Ok(lspan) => Some(lspan),
Err(_) => None,
};
match (label_start, label_end, label_text) {
(
Some(Value::Int { val: start, .. }),
@@ -134,25 +106,13 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
Some(Value::String {
val: label_text, ..
}),
) => {
if start > end {
Some(ShellError::GenericError(
"invalid error format.".into(),
"`$.label.start` should be smaller than `$.label.end`".into(),
label_span,
Some(format!("{} > {}", start, end)),
Vec::new(),
))
} else {
Some(ShellError::GenericError(
message,
label_text,
Some(Span::new(start as usize, end as usize)),
None,
Vec::new(),
))
}
}
) => Some(ShellError::GenericError(
message,
label_text,
Some(Span::new(start as usize, end as usize)),
None,
Vec::new(),
)),
(
None,
None,
@@ -166,27 +126,6 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
None,
Vec::new(),
)),
(_, _, None) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.text`".into(),
label_span,
None,
Vec::new(),
)),
(Some(Value::Int { .. }), None, _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.end`".into(),
label_span,
Some("required because `$.label.start` is set".to_string()),
Vec::new(),
)),
(None, Some(Value::Int { .. }), _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.start`".into(),
label_span,
Some("required because `$.label.end` is set".to_string()),
Vec::new(),
)),
_ => None,
}
}
@@ -197,13 +136,6 @@ fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
None,
Vec::new(),
)),
(None, _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.msg`".into(),
Some(*span),
None,
Vec::new(),
)),
_ => None,
}
} else {

View File

@@ -162,7 +162,7 @@ impl Command for For {
return Err(err);
}
Ok(pipeline) => {
let exit_code = pipeline.drain_with_exit_code()?;
let exit_code = pipeline.print(&engine_state, stack, false, false)?;
if exit_code != 0 {
break;
}

View File

@@ -1,152 +0,0 @@
use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use nu_engine::{get_full_help, scope::ScopeData, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct HelpExterns;
impl Command for HelpExterns {
fn name(&self) -> &str {
"help externs"
}
fn usage(&self) -> &str {
"Show help on nushell externs."
}
fn signature(&self) -> Signature {
Signature::build("help externs")
.category(Category::Core)
.rest(
"rest",
SyntaxShape::String,
"the name of extern to get help on",
)
.named(
"find",
SyntaxShape::String,
"string to find in extern names and usage",
Some('f'),
)
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.allow_variants_without_examples(true)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "show all externs",
example: "help externs",
result: None,
},
Example {
description: "show help for single extern",
example: "help externs smth",
result: None,
},
Example {
description: "search for string in extern names and usages",
example: "help externs --find smth",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
help_externs(engine_state, stack, call)
}
}
pub fn help_externs(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// 🚩The following two-lines are copied from filters/find.rs:
let style_computer = StyleComputer::from_config(engine_state, stack);
// Currently, search results all use the same style.
// Also note that this sample string is passed into user-written code (the closure that may or may not be
// defined for "string").
let string_style = style_computer.compute("string", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_externs(engine_state, stack, head);
let found_cmds_vec =
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
return Ok(found_cmds_vec
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone()));
}
if rest.is_empty() {
let found_cmds_vec = build_help_externs(engine_state, stack, head);
Ok(found_cmds_vec
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone()))
} else {
let mut name = String::new();
for r in &rest {
if !name.is_empty() {
name.push(' ');
}
name.push_str(&r.item);
}
let output = engine_state
.get_signatures_with_examples(false)
.iter()
.filter(|(signature, _, _, _, _)| signature.name == name)
.map(|(signature, examples, _, _, is_parser_keyword)| {
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
})
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(Value::String {
val: output.join("======================\n\n"),
span: call.head,
}
.into_pipeline_data())
} else {
Err(ShellError::CommandNotFound(span(&[
rest[0].span,
rest[rest.len() - 1].span,
])))
}
}
}
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
let mut scope = ScopeData::new(engine_state, stack);
scope.populate_all();
scope.collect_externs(span)
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::HelpExterns;
use crate::test_examples;
test_examples(HelpExterns {})
}
}

View File

@@ -20,7 +20,7 @@ impl Command for If {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("if")
.input_output_types(vec![(Type::Any, Type::Any)])
.required("cond", SyntaxShape::MathExpression, "condition to check")
.required("cond", SyntaxShape::Expression, "condition to check")
.required(
"then_block",
SyntaxShape::Block,

View File

@@ -22,7 +22,7 @@ impl Command for Let {
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)

View File

@@ -67,7 +67,7 @@ impl Command for Loop {
return Err(err);
}
Ok(pipeline) => {
let exit_code = pipeline.drain_with_exit_code()?;
let exit_code = pipeline.print(engine_state, stack, false, false)?;
if exit_code != 0 {
break;
}

View File

@@ -1,133 +0,0 @@
use nu_engine::{eval_block, eval_expression_with_input, CallExt};
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Matcher, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct Match;
impl Command for Match {
fn name(&self) -> &str {
"match"
}
fn usage(&self) -> &str {
"Conditionally run a block on a matched value."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("match")
.input_output_types(vec![(Type::Any, Type::Any)])
.required("value", SyntaxShape::Any, "value to check")
.required(
"match_block",
SyntaxShape::MatchBlock,
"block to run if check succeeds",
)
.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 is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value: Value = call.req(engine_state, stack, 0)?;
let block = call.positional_nth(1);
if let Some(Expression {
expr: Expr::MatchBlock(matches),
..
}) = block
{
for match_ in matches {
let mut match_variables = vec![];
if match_.0.match_value(&value, &mut match_variables) {
// This case does match, go ahead and return the evaluated expression
for match_variable in match_variables {
stack.add_var(match_variable.0, match_variable.1);
}
if let Some(block_id) = match_.1.as_block() {
let block = engine_state.get_block(block_id);
return eval_block(
engine_state,
stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
} else {
return eval_expression_with_input(
engine_state,
stack,
&match_.1,
input,
call.redirect_stdout,
call.redirect_stderr,
)
.map(|x| x.0);
}
}
}
}
Ok(PipelineData::Empty)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Match on a value in range",
example: "match 3 { 1..10 => 'yes!' }",
result: Some(Value::test_string("yes!")),
},
Example {
description: "Match on a field in a record",
example: "match {a: 100} { {a: $my_value} => { $my_value } }",
result: Some(Value::test_int(100)),
},
Example {
description: "Match with a catch-all",
example: "match 3 { 1 => { 'yes!' }, _ => { 'no!' } }",
result: Some(Value::test_string("no!")),
},
Example {
description: "Match against a list",
example: "match [1, 2, 3] { [$a, $b, $c] => { $a + $b + $c }, _ => 0 }",
result: Some(Value::test_int(6)),
},
Example {
description: "Match against pipeline input",
example: "{a: {b: 3}} | match $in {{a: { $b }} => ($b + 10) }",
result: Some(Value::test_int(13)),
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Match {})
}
}

View File

@@ -1,6 +1,5 @@
mod alias;
mod break_;
mod collect;
mod commandline;
mod const_;
mod continue_;
@@ -21,7 +20,6 @@ mod for_;
pub mod help;
pub mod help_aliases;
pub mod help_commands;
pub mod help_externs;
pub mod help_modules;
mod help_operators;
mod hide;
@@ -30,7 +28,6 @@ mod if_;
mod ignore;
mod let_;
mod loop_;
mod match_;
mod module;
mod mut_;
pub(crate) mod overlay;
@@ -42,7 +39,6 @@ mod while_;
pub use alias::Alias;
pub use break_::Break;
pub use collect::Collect;
pub use commandline::Commandline;
pub use const_::Const;
pub use continue_::Continue;
@@ -63,7 +59,6 @@ pub use for_::For;
pub use help::Help;
pub use help_aliases::HelpAliases;
pub use help_commands::HelpCommands;
pub use help_externs::HelpExterns;
pub use help_modules::HelpModules;
pub use help_operators::HelpOperators;
pub use hide::Hide;
@@ -72,7 +67,6 @@ pub use if_::If;
pub use ignore::Ignore;
pub use let_::Let;
pub use loop_::Loop;
pub use match_::Match;
pub use module::Module;
pub use mut_::Mut;
pub use overlay::*;

View File

@@ -22,7 +22,7 @@ impl Command for Mut {
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)

View File

@@ -63,7 +63,7 @@ impl Command for Register {
result: None,
},
Example {
description: "Register `nu_plugin_query` plugin from `nu -c` (writes/updates $nu.plugin-path)",
description: "Register `nu_plugin_query` plugin from `nu -c`(plugin will be available in that nu session only)",
example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#,
result: None,
},

View File

@@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
#[derive(Clone)]
@@ -26,10 +26,7 @@ impl Command for Try {
"catch_block",
SyntaxShape::Keyword(
b"catch".to_vec(),
Box::new(SyntaxShape::OneOf(vec![
SyntaxShape::Closure(None),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
])),
Box::new(SyntaxShape::Closure(Some(vec![SyntaxShape::Any]))),
),
"block to run if try block fails",
)
@@ -62,13 +59,17 @@ impl Command for Try {
match result {
Err(error) => {
let error = intercept_block_control(error)?;
let err_record = err_to_record(error, call.head);
handle_catch(err_record, catch_block, engine_state, stack)
let err_value = Value::Error {
error: Box::new(error),
};
handle_catch(err_value, catch_block, engine_state, stack)
}
Ok(PipelineData::Value(Value::Error { error }, ..)) => {
let error = intercept_block_control(*error)?;
let err_record = err_to_record(error, call.head);
handle_catch(err_record, catch_block, engine_state, stack)
let err_value = Value::Error {
error: Box::new(error),
};
handle_catch(err_value, catch_block, engine_state, stack)
}
// external command may fail to run
Ok(pipeline) => {
@@ -144,19 +145,6 @@ fn intercept_block_control(error: ShellError) -> Result<ShellError, ShellError>
}
}
/// Convert from `error` to [`Value::Record`] so the error information can be easily accessed in catch.
fn err_to_record(error: ShellError, head: Span) -> Value {
let cols = vec!["msg".to_string(), "debug".to_string(), "raw".to_string()];
let vals = vec![
Value::string(error.to_string(), head),
Value::string(format!("{error:?}"), head),
Value::Error {
error: Box::new(error),
},
];
Value::record(cols, vals, head)
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -21,7 +21,7 @@ impl Command for While {
Signature::build("while")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("cond", SyntaxShape::MathExpression, "condition to check")
.required("cond", SyntaxShape::Expression, "condition to check")
.required(
"block",
SyntaxShape::Block,
@@ -77,7 +77,8 @@ impl Command for While {
return Err(err);
}
Ok(pipeline) => {
let exit_code = pipeline.drain_with_exit_code()?;
let exit_code =
pipeline.print(engine_state, stack, false, false)?;
if exit_code != 0 {
break;
}

View File

@@ -18,7 +18,6 @@ pub fn create_default_context() -> EngineState {
bind_command! {
Alias,
Break,
Collect,
Commandline,
Const,
Continue,
@@ -40,7 +39,6 @@ pub fn create_default_context() -> EngineState {
HelpAliases,
HelpCommands,
HelpModules,
HelpExterns,
HelpOperators,
Hide,
HideEnv,
@@ -53,7 +51,6 @@ pub fn create_default_context() -> EngineState {
OverlayHide,
Let,
Loop,
Match,
Module,
Mut,
Return,

View File

@@ -13,7 +13,7 @@ mod test_examples {
check_example_evaluates_to_expected_output,
check_example_input_and_output_types_match_command_signature,
};
use crate::{Break, Collect, Describe, Mut};
use crate::{Break, Describe, Mut};
use crate::{Echo, If, Let};
use nu_protocol::{
engine::{Command, EngineState, StateWorkingSet},
@@ -66,7 +66,6 @@ mod test_examples {
working_set.add_decl(Box::new(If));
working_set.add_decl(Box::new(Let));
working_set.add_decl(Box::new(Mut));
working_set.add_decl(Box::new(Collect));
// Adding the command that is being tested to the working set
working_set.add_decl(cmd);

View File

@@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.78.0"
version = "0.77.1"
[lib]
bench = false
@@ -15,11 +15,11 @@ serde = { version="1.0.123", features=["derive"] }
# used only for text_style Alignments
tabled = { version = "0.10.0", features = ["color"], default-features = false }
nu-protocol = { path = "../nu-protocol", version = "0.78.0" }
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
nu-ansi-term = "0.47.0"
nu-utils = { path = "../nu-utils", version = "0.78.0" }
nu-engine = { path = "../nu-engine", version = "0.78.0" }
nu-json = { path="../nu-json", version = "0.78.0" }
nu-utils = { path = "../nu-utils", version = "0.77.1" }
nu-engine = { path = "../nu-engine", version = "0.77.1" }
nu-json = { path="../nu-json", version = "0.77.1" }
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.78.0" }
nu-test-support = { path="../nu-test-support", version = "0.77.1" }

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Copyright (c) 2019 - 2022 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

View File

@@ -20,26 +20,22 @@ impl From<Style> for NuStyle {
}
fn style_get_attr(s: Style) -> Option<String> {
macro_rules! check {
($attrs:expr, $b:expr, $c:expr) => {
if $b {
$attrs.push($c);
}
};
}
let mut attrs = String::new();
if s.is_blink {
attrs.push('l');
};
if s.is_bold {
attrs.push('b');
};
if s.is_dimmed {
attrs.push('d');
};
if s.is_reverse {
attrs.push('r');
};
if s.is_strikethrough {
attrs.push('s');
};
if s.is_underline {
attrs.push('u');
};
check!(attrs, s.is_blink, 'l');
check!(attrs, s.is_bold, 'b');
check!(attrs, s.is_dimmed, 'd');
check!(attrs, s.is_reverse, 'r');
check!(attrs, s.is_strikethrough, 's');
check!(attrs, s.is_underline, 'u');
if attrs.is_empty() {
None

View File

@@ -23,7 +23,6 @@ pub fn default_shape_color(shape: String) -> Style {
"shape_internalcall" => Style::new().fg(Color::Cyan).bold(),
"shape_list" => Style::new().fg(Color::Cyan).bold(),
"shape_literal" => Style::new().fg(Color::Blue),
"shape_match_pattern" => Style::new().fg(Color::Green),
"shape_nothing" => Style::new().fg(Color::LightCyan),
"shape_operator" => Style::new().fg(Color::Yellow),
"shape_or" => Style::new().fg(Color::Purple).bold(),

View File

@@ -21,6 +21,13 @@ pub enum ComputableStyle {
Closure(Value),
}
// macro used for adding initial values to the style hashmap
macro_rules! initial {
($a:expr, $b:expr) => {
($a.to_string(), ComputableStyle::Static($b))
};
}
// An alias for the mapping used internally by StyleComputer.
pub type StyleMapping = HashMap<String, ComputableStyle>;
//
@@ -146,12 +153,6 @@ impl<'a> StyleComputer<'a> {
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
let config = engine_state.get_config();
macro_rules! initial {
($a:expr, $b:expr) => {
($a.to_string(), ComputableStyle::Static($b))
};
}
// Create the hashmap
let mut map: StyleMapping = HashMap::from([
initial!("separator", Color::White.normal()),

View File

@@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.78.0"
version = "0.77.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,27 +13,27 @@ version = "0.78.0"
bench = false
[dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.78.0" }
nu-color-config = { path = "../nu-color-config", version = "0.78.0" }
nu-engine = { path = "../nu-engine", version = "0.78.0" }
nu-explore = { path = "../nu-explore", version = "0.78.0" }
nu-glob = { path = "../nu-glob", version = "0.78.0" }
nu-json = { path = "../nu-json", version = "0.78.0" }
nu-parser = { path = "../nu-parser", version = "0.78.0" }
nu-path = { path = "../nu-path", version = "0.78.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.78.0" }
nu-protocol = { path = "../nu-protocol", version = "0.78.0" }
nu-system = { path = "../nu-system", version = "0.78.0" }
nu-table = { path = "../nu-table", version = "0.78.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.78.0" }
nu-utils = { path = "../nu-utils", version = "0.78.0" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.77.1" }
nu-color-config = { path = "../nu-color-config", version = "0.77.1" }
nu-engine = { path = "../nu-engine", version = "0.77.1" }
nu-explore = { path = "../nu-explore", version = "0.77.1" }
nu-glob = { path = "../nu-glob", version = "0.77.1" }
nu-json = { path = "../nu-json", version = "0.77.1" }
nu-parser = { path = "../nu-parser", version = "0.77.1" }
nu-path = { path = "../nu-path", version = "0.77.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.77.1" }
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
nu-system = { path = "../nu-system", version = "0.77.1" }
nu-table = { path = "../nu-table", version = "0.77.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.77.1" }
nu-utils = { path = "../nu-utils", version = "0.77.1" }
num-format = { version = "0.4.3" }
nu-ansi-term = "0.47.0"
# Potential dependencies for extras
Inflector = "0.11"
alphanumeric-sort = "1.5.0"
alphanumeric-sort = "1.4.4"
atty = "0.2.14"
base64 = "0.21.0"
byteorder = "1.4.3"
@@ -60,17 +60,16 @@ itertools = "0.10.0"
log = "0.4.14"
lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false }
md5 = { package = "md-5", version = "0.10.0" }
miette = { version = "5.6.0", features = ["fancy-no-backtrace"] }
mime = "0.3.16"
mime_guess = "2.0.4"
notify = "4.0.17"
num = { version = "0.4.0", optional = true }
num-traits = "0.2.14"
once_cell = "1.17"
open = "4.0.0"
open = "3.2.0"
pathdiff = "0.2.1"
powierza-coefficient = "1.0.2"
quick-xml = "0.28"
quick-xml = "0.27"
rand = "0.8"
rayon = "1.7.0"
regex = "1.7.1"
@@ -85,9 +84,9 @@ serde_yaml = "0.9.4"
sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile)
percent-encoding = "2.2.0"
reedline = { version = "0.18.0", features = ["bashisms", "sqlite"] }
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.32.0", features = ["serde"], optional = true }
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
sysinfo = "0.28.2"
tabled = "0.10.0"
terminal_size = "0.2.1"
@@ -147,7 +146,7 @@ version = "0.27.2"
[target.'cfg(windows)'.dependencies.windows]
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"]
version = "0.46.0"
version = "0.44.0"
[features]
dataframe = ["num", "polars", "sqlparser"]
@@ -157,11 +156,12 @@ trash-support = ["trash"]
which-support = ["which"]
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.78.0" }
mockito = "1.0.0"
nu-test-support = { path = "../nu-test-support", version = "0.77.1" }
mockito = "0.32.3"
dirs-next = "2.0.0"
hamcrest2 = "0.3.0"
proptest = "1.1.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
rstest = { version = "0.17.0", default-features = false }
rstest = { version = "0.16.0", default-features = false }

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Copyright (c) 2019 - 2022 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

View File

@@ -0,0 +1,23 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 96a80ecd19729fb43a7b7bb2766b37d6083ba73b16abb97075875e3cfcdc763f # shrinks to c = '"'
cc 4146602559ea717a02bcef3c6d73cdf613c30d0c3f92c48e26c79b9a1544e027 # shrinks to c = '\\'
cc 80532a0ee73df456a719b9e3cce1ae5f3d26009dde819cbaf16f8e0cb6709705 # shrinks to c = ':'
cc cdb88505686eea3c74c36f282fd29b2b68bc118ded4ebfc36f9838d174bd7653 # shrinks to c = '`'
cc 0f534d55f9771e8810b9c4252a4168abfaec1a35e1b0cac12dbaf726d295a08c # shrinks to c = '\0'
cc 5d31bcbab722acd1f4e23ca3a4f95ff309a636b45a73ca8ae9f820d93ff57acc # shrinks to c = '{'
cc 5afec063bc96160d681d77f90041b67ef5cfdea4dcbd12d984fd828fbeb4b421 # shrinks to c = '#'
cc f919beb3ee5c70e756a15635d65ded7d44f3ae58b5e86b6c09e814d5d8cdd506 # shrinks to c = ';'
cc ec00f39b8d45dfd8808947a56af5e50ba5a0ef7c951723b45377815a02e515b1 # shrinks to c = '('
cc 25b773cdf4c24179151fa86244c7de4136e05df9e94e6ee77a336ebfd8764444 # shrinks to c = '|'
cc 94dc0d54b97d59e1c0f4cb11bdccb3823a1bb908cbc3fd643ee8f067169fad72 # shrinks to c = '0'
cc c9d0051fb1e5a8bdc1d4f5a3dceac1b4b465827d1dff4fc3a3755baae6a7bb48 # shrinks to c = '$'
cc 14ec40d2eb5bd2663e9b11bb49fb2120852f9ea71678c69d732161412b55a3ec # shrinks to s = ""
cc d4afccc51ed9d421bdb7e1723e273dfb6e77c3a449489671a496db234e87c5ed # shrinks to c = '\r'
cc 515a56d73eb1b69290ef4c714b629421989879aebd57991bd2c2bf11294353b1 # shrinks to s = "\\\\𐊀{"
cc 111566990fffa432acd2dbc845141b0e7870f97125c7621e3ddf142204568246 # shrinks to s = "'\":`"
cc 0424c33000d9188be96b3049046eb052770b2158bf5ebb0c98656d7145e8aca9 # shrinks to s = "0"

View File

@@ -90,7 +90,7 @@ impl Command for SubCommand {
Example {
description:
"Apply logical negation to a list of numbers, treat input as 2 bytes number",
example: "[4 3 2] | bits not -n '2'",
example: "[4 3 2] | bits not -n 2",
result: Some(Value::List {
vals: vec![
Value::test_int(65531),

View File

@@ -85,7 +85,7 @@ impl Command for SubCommand {
},
Example {
description: "Rotate right a list of numbers of one byte",
example: "[15 33 92] | bits ror 2 -n '1'",
example: "[15 33 92] | bits ror 2 -n 1",
result: Some(Value::List {
vals: vec![
Value::test_int(195),

View File

@@ -85,7 +85,7 @@ impl Command for SubCommand {
},
Example {
description: "Shift left a number with 1 byte by 7 bits",
example: "2 | bits shl 7 -n '1'",
example: "2 | bits shl 7 -n 1",
result: Some(Value::test_int(0)),
},
Example {

View File

@@ -1,19 +1,20 @@
use crate::{
input_handler::{operate, CmdArgument},
util,
};
use crate::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, Range, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use std::cmp::Ordering;
#[derive(Clone)]
pub struct BytesAt;
struct Arguments {
indexes: Subbytes,
start: isize,
end: isize,
arg_span: Span,
cell_paths: Option<Vec<CellPath>>,
}
@@ -23,14 +24,103 @@ impl CmdArgument for Arguments {
}
}
impl From<(isize, isize)> for Subbytes {
fn from(input: (isize, isize)) -> Self {
Self(input.0, input.1)
}
}
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
let (start, end, span) = match range {
Value::List { mut vals, span } => {
if vals.len() != 2 {
return Err(ShellError::UnsupportedInput(
"More than two indices in range".to_string(),
"value originates from here".to_string(),
head,
span,
));
} else {
let end = vals.pop().expect("Already check has size 2");
let end = match end {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(*error),
other => {
return Err(ShellError::UnsupportedInput(
"Only string or list<int> ranges are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
other.expect_span(),
))
}
};
let start = vals.pop().expect("Already check has size 1");
let start = match start {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(*error),
other => {
return Err(ShellError::UnsupportedInput(
"Only string or list<int> ranges are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
other.expect_span(),
))
}
};
(start, end, span)
}
}
Value::String { val, span } => {
let split_result = val.split_once(',');
match split_result {
Some((start, end)) => (start.to_string(), end.to_string(), span),
None => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span,
))
}
}
}
// Explicitly propagate errors instead of dropping them.
Value::Error { error } => return Err(*error),
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
"with this range".to_string(),
head,
other.expect_span(),
))
}
};
#[derive(Clone, Copy)]
struct Subbytes(isize, isize);
let start: isize = if start.is_empty() || start == "_" {
0
} else {
start.trim().parse().map_err(|_| {
ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span,
)
})?
};
let end: isize = if end.is_empty() || end == "_" {
isize::max_value()
} else {
end.trim().parse().map_err(|_| {
ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span,
)
})?
};
Ok((start, end, span))
}
impl Command for BytesAt {
fn name(&self) -> &str {
@@ -41,7 +131,7 @@ impl Command for BytesAt {
Signature::build("bytes at")
.input_output_types(vec![(Type::Binary, Type::Binary)])
.vectorizes_over_list(true)
.required("range", SyntaxShape::Range, "the range to get bytes")
.required("range", SyntaxShape::Any, "the indexes to get bytes")
.rest(
"rest",
SyntaxShape::CellPath,
@@ -51,7 +141,7 @@ impl Command for BytesAt {
}
fn usage(&self) -> &str {
"Get bytes defined by a range"
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
}
fn search_terms(&self) -> Vec<&str> {
@@ -65,45 +155,48 @@ impl Command for BytesAt {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let range: Range = call.req(engine_state, stack, 0)?;
let indexes = match util::process_range(&range) {
Ok(idxs) => idxs.into(),
Err(processing_error) => {
return Err(processing_error("could not perform subbytes", call.head));
}
};
let range: Value = call.req(engine_state, stack, 0)?;
let (start, end, arg_span) = parse_range(range, call.head)?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
indexes,
let arg = Arguments {
start,
end,
arg_span,
cell_paths,
};
operate(action, args, input, call.head, engine_state.ctrlc.clone())
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at 3..<4",
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Get a subbytes `0x[10 01 13]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at 3..6",
description: "Alternatively, you can use the form",
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Drop the last `n` characters from the string",
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55],
span: Span::test_data(),
}),
},
Example {
description: "Get the remaining characters from a starting index",
example: " 0x[33 44 55 10 01 13] | bytes at 3..",
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
span: Span::test_data(),
@@ -111,7 +204,7 @@ impl Command for BytesAt {
},
Example {
description: "Get the characters from the beginning until ending index",
example: " 0x[33 44 55 10 01 13] | bytes at ..<4",
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55, 0x10],
span: Span::test_data(),
@@ -120,7 +213,7 @@ impl Command for BytesAt {
Example {
description:
"Or the characters from the beginning until ending index inside a table",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB ColC"#,
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
@@ -147,66 +240,73 @@ impl Command for BytesAt {
}
}
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 {
cmp::max(range.1 + len, 0)
} else {
range.1
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::Binary {
val: vec![],
span: head,
},
Ordering::Greater => Value::Error {
error: Box::new(ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: head,
}),
},
Ordering::Less => Value::Binary {
val: {
if end == isize::max_value() {
val.iter().skip(start as usize).copied().collect()
} else {
val.iter()
.skip(start as usize)
.take((end - start) as usize)
.copied()
.collect()
}
},
span: head,
},
}
} else {
Value::Binary {
val: vec![],
span: head,
}
}
}
Value::Error { .. } => input.clone(),
fn at(val: &Value, args: &Arguments, span: Span) -> Value {
match val {
Value::Binary {
val,
span: val_span,
} => at_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error {
error: Box::new(ShellError::UnsupportedInput(
"Only binary values are supported".into(),
format!("input type: {:?}", other.get_type()),
head,
// This line requires the Value::Error match above.
other.expect_span(),
)),
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
}),
},
}
}
fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
let len: isize = input.len() as isize;
let start: isize = if arg.start < 0 {
arg.start + len
} else {
arg.start
};
let end: isize = if arg.end < 0 {
std::cmp::max(len + arg.end, 0)
} else {
arg.end
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::Binary { val: vec![], span },
Ordering::Greater => Value::Error {
error: Box::new(ShellError::TypeMismatch {
err_message: "End must be greater than or equal to Start".to_string(),
span: arg.arg_span,
}),
},
Ordering::Less => Value::Binary {
val: {
let input_iter = input.iter().copied().skip(start as usize);
if end == isize::max_value() {
input_iter.collect()
} else {
input_iter.take((end - start) as usize).collect()
}
},
span,
},
}
} else {
Value::Binary { val: vec![], span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesAt {})
}
}

View File

@@ -49,12 +49,12 @@ impl Command for BytesLen {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the length of a binary",
description: "Return the lengths of multiple strings",
example: "0x[1F FF AA AB] | bytes length",
result: Some(Value::test_int(4)),
},
Example {
description: "Return the lengths of multiple binaries",
description: "Return the lengths of multiple strings",
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
result: Some(Value::List {
vals: vec![Value::test_int(4), Value::test_int(1)],

View File

@@ -240,11 +240,7 @@ mod test {
},
Value::CellPath {
val: CellPath {
members: vec![PathMember::Int {
val: 0,
span,
optional: false,
}],
members: vec![PathMember::Int { val: 0, span }],
},
span,
},

View File

@@ -98,17 +98,11 @@ impl Command for Histogram {
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
let frequency_column_name = match frequency_name_arg {
Some(inner) => {
let forbidden_column_names = ["value", "count", "quantile", "percentage"];
if forbidden_column_names.contains(&inner.item.as_str()) {
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
return Err(ShellError::TypeMismatch {
err_message: format!(
"frequency-column-name can't be {}",
forbidden_column_names
.iter()
.map(|val| format!("'{}'", val))
.collect::<Vec<_>>()
.join(", ")
),
err_message:
"frequency-column-name can't be 'value', 'count' or 'percentage'"
.to_string(),
span: inner.span,
});
}

View File

@@ -105,7 +105,7 @@ impl Command for Fill {
Example {
description:
"Fill a number on the left side to a width of 5 with the character '0'",
example: "1 | fill --alignment right --character '0' --width 5",
example: "1 | fill --alignment right --character 0 --width 5",
result: Some(Value::String {
val: "00001".into(),
span: Span::test_data(),
@@ -113,7 +113,7 @@ impl Command for Fill {
},
Example {
description: "Fill a number on both sides to a width of 5 with the character '0'",
example: "1.1 | fill --alignment center --character '0' --width 5",
example: "1.1 | fill --alignment center --character 0 --width 5",
result: Some(Value::String {
val: "01.10".into(),
span: Span::test_data(),
@@ -122,7 +122,7 @@ impl Command for Fill {
Example {
description:
"Fill a filesize on the left side to a width of 5 with the character '0'",
example: "1kib | fill --alignment middle --character '0' --width 10",
example: "1kib | fill --alignment middle --character 0 --width 10",
result: Some(Value::String {
val: "0001024000".into(),
span: Span::test_data(),

View File

@@ -145,30 +145,6 @@ impl Command for SubCommand {
span,
}),
},
Example {
description: "Convert µs duration to the requested duration as a string",
example: "1000000µs | into duration --convert sec",
result: Some(Value::String {
val: "1 sec".to_string(),
span,
}),
},
Example {
description: "Convert duration to the µs duration as a string",
example: "1sec | into duration --convert µs",
result: Some(Value::String {
val: "1000000 µs".to_string(),
span,
}),
},
Example {
description: "Convert duration to µs as a string if unit asked for was us",
example: "1sec | into duration --convert us",
result: Some(Value::String {
val: "1000000 µs".to_string(),
span,
}),
},
]
}
}
@@ -221,8 +197,6 @@ fn convert_str_from_unit_to_unit(
match (from_unit, to_unit) {
("ns", "ns") => Ok(val as f64),
("ns", "us") => Ok(val as f64 / 1000.0),
("ns", "µs") => Ok(val as f64 / 1000.0), // Micro sign
("ns", "μs") => Ok(val as f64 / 1000.0), // Greek small letter
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
@@ -237,8 +211,6 @@ fn convert_str_from_unit_to_unit(
("us", "ns") => Ok(val as f64 * 1000.0),
("us", "us") => Ok(val as f64),
("us", "µs") => Ok(val as f64), // Micro sign
("us", "μs") => Ok(val as f64), // Greek small letter
("us", "ms") => Ok(val as f64 / 1000.0),
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
@@ -249,40 +221,8 @@ fn convert_str_from_unit_to_unit(
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
// Micro sign
("µs", "ns") => Ok(val as f64 * 1000.0),
("µs", "us") => Ok(val as f64),
("µs", "µs") => Ok(val as f64), // Micro sign
("µs", "μs") => Ok(val as f64), // Greek small letter
("µs", "ms") => Ok(val as f64 / 1000.0),
("µs", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("µs", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
("µs", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
("µs", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("µs", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("µs", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("µs", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("µs", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
// Greek small letter
("μs", "ns") => Ok(val as f64 * 1000.0),
("μs", "us") => Ok(val as f64),
("μs", "µs") => Ok(val as f64), // Micro sign
("μs", "μs") => Ok(val as f64), // Greek small letter
("μs", "ms") => Ok(val as f64 / 1000.0),
("μs", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
("μs", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
("μs", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
("μs", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
("μs", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
("μs", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
("μs", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("μs", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
("ms", "us") => Ok(val as f64 * 1000.0),
("ms", "µs") => Ok(val as f64 * 1000.0), // Micro sign
("ms", "μs") => Ok(val as f64 * 1000.0), // Greek small letter
("ms", "ms") => Ok(val as f64),
("ms", "sec") => Ok(val as f64 / 1000.0),
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
@@ -295,8 +235,6 @@ fn convert_str_from_unit_to_unit(
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
("sec", "µs") => Ok(val as f64 * 1000.0 * 1000.0), // Micro sign
("sec", "μs") => Ok(val as f64 * 1000.0 * 1000.0), // Greek small letter
("sec", "ms") => Ok(val as f64 * 1000.0),
("sec", "sec") => Ok(val as f64),
("sec", "min") => Ok(val as f64 / 60.0),
@@ -309,8 +247,6 @@ fn convert_str_from_unit_to_unit(
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
("min", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0), // Micro sign
("min", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0), // Greek small letter
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
("min", "sec") => Ok(val as f64 * 60.0),
("min", "min") => Ok(val as f64),
@@ -323,8 +259,6 @@ fn convert_str_from_unit_to_unit(
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
("hr", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0), // Micro sign
("hr", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0), // Greek small letter
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
("hr", "min") => Ok(val as f64 * 60.0),
@@ -337,8 +271,6 @@ fn convert_str_from_unit_to_unit(
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0), // Micro sign
("day", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0), // Greek small letter
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
@@ -351,8 +283,6 @@ fn convert_str_from_unit_to_unit(
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0), // Micro sign
("wk", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0), // Greek small letter
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
@@ -365,8 +295,6 @@ fn convert_str_from_unit_to_unit(
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0), // Micro sign
("month", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0), // Greek small letter
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
@@ -379,8 +307,6 @@ fn convert_str_from_unit_to_unit(
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0), // Micro sign
("yr", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0), // Greek small letter
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
@@ -398,7 +324,7 @@ fn convert_str_from_unit_to_unit(
dst_span: span,
src_span: value_span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec"
.to_string(),
),
}),
@@ -431,8 +357,7 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
dst_span: span,
src_span: value_span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
.to_string(),
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
),
})
}
@@ -447,7 +372,7 @@ fn string_to_unit_duration(
if let Expr::Int(x) = value.expr {
match unit.item {
Unit::Nanosecond => return Ok(("ns", x)),
Unit::Microsecond => return Ok(("µs", x)),
Unit::Microsecond => return Ok(("us", x)),
Unit::Millisecond => return Ok(("ms", x)),
Unit::Second => return Ok(("sec", x)),
Unit::Minute => return Ok(("min", x)),
@@ -468,8 +393,7 @@ fn string_to_unit_duration(
dst_span: span,
src_span: value_span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
.to_string(),
"supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(),
),
})
}
@@ -496,19 +420,14 @@ fn action(
*value_span,
) {
Ok(d) => {
let unit = if &to_unit.item == "us" {
"µs"
} else {
&to_unit.item
};
if d.fract() == 0.0 {
Value::String {
val: format!("{} {}", d, unit),
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, unit),
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
@@ -535,19 +454,14 @@ fn action(
*value_span,
) {
Ok(d) => {
let unit = if &to_unit.item == "us" {
"µs"
} else {
&to_unit.item
};
if d.fract() == 0.0 {
Value::String {
val: format!("{} {}", d, unit),
val: format!("{} {}", d, &to_unit.item),
span: *value_span,
}
} else {
Value::String {
val: format!("{:.float_precision$} {}", d, unit),
val: format!("{:.float_precision$} {}", d, &to_unit.item),
span: *value_span,
}
}
@@ -622,34 +536,6 @@ mod test {
assert_eq!(actual, expected);
}
#[test]
fn turns_micro_sign_s_to_duration() {
let span = Span::new(0, 2);
let word = Value::test_string("4\u{00B5}s");
let expected = Value::Duration {
val: 4 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
#[test]
fn turns_mu_s_to_duration() {
let span = Span::new(0, 2);
let word = Value::test_string("4\u{03BC}s");
let expected = Value::Duration {
val: 4 * 1000,
span,
};
let convert_duration = None;
let actual = action(&word, &convert_duration, 2, span);
assert_eq!(actual, expected);
}
#[test]
fn turns_ms_to_duration() {
let span = Span::new(0, 2);

View File

@@ -270,7 +270,6 @@ fn nu_value_to_string(value: Value, separator: &str) -> String {
Value::Binary { val, .. } => format!("{val:?}"),
Value::CellPath { val, .. } => val.into_string(),
Value::CustomValue { val, .. } => val.value_string(),
Value::MatchPattern { val, .. } => format!("{:?}", val),
}
}

View File

@@ -22,7 +22,7 @@ impl Command for OpenDataFrame {
}
fn usage(&self) -> &str {
"Opens CSV, JSON, arrow, or parquet file to create dataframe."
"Opens csv, json, arrow, or parquet file to create dataframe."
}
fn signature(&self) -> Signature {

View File

@@ -19,7 +19,7 @@ impl Command for ToCSV {
}
fn usage(&self) -> &str {
"Saves dataframe to CSV file."
"Saves dataframe to csv file."
}
fn signature(&self) -> Signature {
@@ -40,12 +40,12 @@ impl Command for ToCSV {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Saves dataframe to CSV file",
description: "Saves dataframe to csv file",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv",
result: None,
},
Example {
description: "Saves dataframe to CSV file using other delimiter",
description: "Saves dataframe to csv file using other delimiter",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv -d '|'",
result: None,
},

View File

@@ -76,8 +76,7 @@ impl Command for Debug {
},
Example {
description: "Debug print a table",
example:
"[[version patch]; ['0.1.0' false] ['0.1.1' true] ['0.2.0' false]] | debug",
example: "[[version patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | debug",
result: Some(Value::List {
vals: vec![
Value::test_string("{version: 0.1.0, patch: false}"),

View File

@@ -328,6 +328,5 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
Value::Binary { val, .. } => format!("{val:?}"),
Value::CellPath { val, .. } => val.into_string(),
Value::CustomValue { val, .. } => val.value_string(),
Value::MatchPattern { val, .. } => format!("{:?}", val),
}
}

View File

@@ -7,9 +7,8 @@ use tabled::{
};
use self::{
global_horizontal_char::SetHorizontalCharOnFirstRow, peak2::Peak2,
table_column_width::get_first_cell_width, truncate_table::TruncateTable,
width_increase::IncWidth,
global_horizontal_char::SetHorizontalChar, peak2::Peak2, table_column_width::GetColumnWidths,
truncate_table::TruncateTable, width_increase::IncWidth,
};
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
@@ -24,8 +23,7 @@ pub fn build_table(value: Value, description: String, termsize: usize) -> String
let mut desc_table = Builder::from(desc).build();
let desc_table_width = desc_table.total_width();
#[allow(clippy::manual_clamp)]
let width = val_table_width.max(desc_table_width).min(termsize);
let width = val_table_width.clamp(desc_table_width, termsize);
desc_table
.with(Style::rounded().off_bottom())
@@ -38,11 +36,10 @@ pub fn build_table(value: Value, description: String, termsize: usize) -> String
.with(Wrap::new(width).priority::<PriorityMax>())
.with(IncWidth(width));
// we use only 1, cause left border considered 0 position
let count_split_lines = 1;
let desc_width = get_first_cell_width(&mut desc_table) + count_split_lines;
let mut desc_widths = GetColumnWidths(Vec::new());
desc_table.with(&mut desc_widths);
val_table.with(SetHorizontalCharOnFirstRow::new('┼', '┴', desc_width));
val_table.with(SetHorizontalChar::new('┼', '┴', 0, desc_widths.0[0]));
format!("{desc_table}\n{val_table}")
}
@@ -139,7 +136,7 @@ mod util {
let mut columns = get_columns(&vals);
let data = convert_records_to_dataset(&columns, vals);
if columns.is_empty() {
if columns.is_empty() && !data.is_empty() {
columns = vec![String::from("")];
}
@@ -211,11 +208,10 @@ mod util {
let path = PathMember::String {
val: header.to_owned(),
span: Span::unknown(),
optional: false,
};
item.clone()
.follow_cell_path(&[path], false)
.follow_cell_path(&[path], false, false)
.unwrap_or_else(|_| item.clone())
}
item => item.clone(),
@@ -264,30 +260,18 @@ mod peak2 {
}
mod table_column_width {
use tabled::{
papergrid::{records::Records, width::CfgWidthFunction},
Table,
};
use tabled::papergrid::{records::Records, Estimate};
pub fn get_first_cell_width<R: Records>(table: &mut Table<R>) -> usize {
let mut opt = GetFirstCellWidth(0);
table.with(&mut opt);
opt.0
}
pub struct GetColumnWidths(pub Vec<usize>);
struct GetFirstCellWidth(pub usize);
impl<R: Records> tabled::TableOption<R> for GetFirstCellWidth {
impl<R> tabled::TableOption<R> for GetColumnWidths
where
R: Records,
{
fn change(&mut self, table: &mut tabled::Table<R>) {
let w = table
.get_records()
.get_width((0, 0), CfgWidthFunction::default());
let pad = table
.get_config()
.get_padding(tabled::papergrid::Entity::Cell(0, 0));
let pad = pad.left.size + pad.right.size;
self.0 = w + pad;
let mut evaluator = tabled::papergrid::width::WidthEstimator::default();
evaluator.estimate(table.get_records(), table.get_config());
self.0 = evaluator.into();
}
}
}
@@ -298,65 +282,91 @@ mod global_horizontal_char {
Table, TableOption,
};
pub struct SetHorizontalCharOnFirstRow {
pub struct SetHorizontalChar {
c1: char,
c2: char,
pos: usize,
line: usize,
position: usize,
}
impl SetHorizontalCharOnFirstRow {
pub fn new(c1: char, c2: char, pos: usize) -> Self {
Self { c1, c2, pos }
impl SetHorizontalChar {
pub fn new(c1: char, c2: char, line: usize, position: usize) -> Self {
Self {
c1,
c2,
line,
position,
}
}
}
impl<R> TableOption<R> for SetHorizontalCharOnFirstRow
impl<R> TableOption<R> for SetHorizontalChar
where
R: Records,
{
fn change(&mut self, table: &mut Table<R>) {
if table.is_empty() {
return;
}
let shape = table.shape();
let is_last_line = self.line == (shape.0 * 2);
let mut row = self.line;
if is_last_line {
row = self.line - 1;
}
let mut evaluator = WidthEstimator::default();
evaluator.estimate(table.get_records(), table.get_config());
let widths: Vec<_> = evaluator.into();
let has_vertical = table.get_config().has_vertical(0, shape.1);
if has_vertical && self.pos == 0 {
let mut border = table.get_config().get_border((0, 0), shape);
border.left_top_corner = Some(self.c1);
table.get_config_mut().set_border((0, 0), border);
return;
}
let mut i = 1;
let mut i = 0;
#[allow(clippy::needless_range_loop)]
for (col, width) in widths.into_iter().enumerate() {
if self.pos < i + width {
let o = self.pos - i;
table
.get_config_mut()
.override_horizontal_border((0, col), self.c2, Begin(o));
return;
}
for column in 0..shape.1 {
let has_vertical = table.get_config().has_vertical(column, shape.1);
i += width;
let has_vertical = table.get_config().has_vertical(col, shape.1);
if has_vertical {
if self.pos == i {
let mut border = table.get_config().get_border((0, col), shape);
border.right_top_corner = Some(self.c1);
table.get_config_mut().set_border((0, col), border);
if self.position == i {
let mut border = table.get_config().get_border((row, column), shape);
if is_last_line {
border.left_bottom_corner = Some(self.c1);
} else {
border.left_top_corner = Some(self.c1);
}
table.get_config_mut().set_border((row, column), border);
return;
}
i += 1;
}
let width = widths[column];
if self.position < i + width {
let offset = self.position + 1 - i;
// let offset = width - offset;
table.get_config_mut().override_horizontal_border(
(self.line, column),
self.c2,
Begin(offset),
);
return;
}
i += width;
}
let has_vertical = table.get_config().has_vertical(shape.1, shape.1);
if self.position == i && has_vertical {
let mut border = table.get_config().get_border((row, shape.1), shape);
if is_last_line {
border.left_bottom_corner = Some(self.c1);
} else {
border.left_top_corner = Some(self.c1);
}
table.get_config_mut().set_border((row, shape.1), border);
}
}
}

View File

@@ -1,7 +1,7 @@
use nu_engine::{eval_block, eval_expression_with_input};
use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
@@ -16,15 +16,15 @@ impl Command for TimeIt {
}
fn usage(&self) -> &str {
"Time the running time of a block."
"Time the running time of a closure."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("timeit")
.required(
"command",
SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]),
"the command or block to run",
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"the closure to run",
)
.input_output_types(vec![
(Type::Any, Type::Duration),
@@ -45,36 +45,37 @@ impl Command for TimeIt {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let command_to_run = call.positional_nth(0);
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id);
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let mut stack = stack.captures_to_stack(&capture_block.captures);
// In order to provide the pipeline as a positional, it must be converted into a value.
// But because pipelines do not have Clone, this one has to be cloned as a value
// and then converted back into a pipeline for eval_block().
// So, the metadata must be saved here and restored at that point.
let input_metadata = input.metadata();
let input_val = input.into_value(call.head);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, input_val.clone());
}
}
// Get the start time after all other computation has been done.
let start_time = Instant::now();
if let Some(command_to_run) = command_to_run {
if let Some(block_id) = command_to_run.as_block() {
let block = engine_state.get_block(block_id);
eval_block(
engine_state,
stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)?
} else {
eval_expression_with_input(
engine_state,
stack,
command_to_run,
input,
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)?
}
} else {
PipelineData::empty()
}
eval_block(
engine_state,
&mut stack,
block,
input_val.into_pipeline_data_with_metadata(input_metadata),
redirect_stdout,
redirect_stderr,
)?
.into_value(call.head);
let end_time = Instant::now();
@@ -99,11 +100,6 @@ impl Command for TimeIt {
example: "http get https://www.nushell.sh/book/ | timeit { split chars }",
result: None,
},
Example {
description: "Times a command invocation",
example: "timeit ls -la",
result: None,
},
]
}
}
@@ -111,11 +107,11 @@ impl Command for TimeIt {
#[test]
// Due to difficulty in observing side-effects from time closures,
// checks that the closures have run correctly must use the filesystem.
fn test_time_block() {
fn test_time_closure() {
use nu_test_support::{nu, nu_repl_code, playground::Playground};
Playground::setup("test_time_block", |dirs, _| {
Playground::setup("test_time_closure", |dirs, _| {
let inp = [
r#"[2 3 4] | timeit {to nuon | save foo.txt }"#,
r#"[2 3 4] | timeit { to nuon | save foo.txt }"#,
"open foo.txt",
];
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
@@ -125,11 +121,11 @@ fn test_time_block() {
}
#[test]
fn test_time_block_2() {
fn test_time_closure_2() {
use nu_test_support::{nu, nu_repl_code, playground::Playground};
Playground::setup("test_time_block", |dirs, _| {
Playground::setup("test_time_closure", |dirs, _| {
let inp = [
r#"[2 3 4] | timeit {{result: $in} | to nuon | save foo.txt }"#,
r#"[2 3 4] | timeit {|e| {result: $e} | to nuon | save foo.txt }"#,
"open foo.txt",
];
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));

View File

@@ -36,6 +36,7 @@ pub fn create_default_context() -> EngineState {
All,
Any,
Append,
Collect,
Columns,
Compact,
Default,
@@ -56,7 +57,6 @@ pub fn create_default_context() -> EngineState {
GroupBy,
Headers,
Insert,
Join,
SplitBy,
Take,
Merge,
@@ -168,8 +168,6 @@ pub fn create_default_context() -> EngineState {
Encode,
DecodeBase64,
EncodeBase64,
DecodeHex,
EncodeHex,
DetectColumns,
Format,
FileSize,
@@ -183,6 +181,7 @@ pub fn create_default_context() -> EngineState {
Str,
StrCamelCase,
StrCapitalize,
StrCollect,
StrContains,
StrDistance,
StrDowncase,
@@ -387,7 +386,6 @@ pub fn create_default_context() -> EngineState {
MathPi,
MathTau,
MathEuler,
MathExp,
MathLn,
MathLog,
};
@@ -442,18 +440,17 @@ pub fn create_default_context() -> EngineState {
// Deprecated
bind_command! {
ExportOldAlias,
HashBase64,
LPadDeprecated,
MathEvalDeprecated,
OldAlias,
RPadDeprecated,
Source,
StrCollectDeprecated,
StrDatetimeDeprecated,
StrDecimalDeprecated,
StrFindReplaceDeprecated,
StrIntDeprecated,
StrFindReplaceDeprecated,
MathEvalDeprecated,
OldAlias,
ExportOldAlias,
};
working_set.render()
@@ -463,9 +460,5 @@ pub fn create_default_context() -> EngineState {
eprintln!("Error creating default context: {err:?}");
}
// Cache the table decl id so we don't have to look it up later
let table_decl_id = engine_state.find_decl("table".as_bytes(), &[]);
engine_state.table_decl_id = table_decl_id;
engine_state
}

View File

@@ -1,34 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, PipelineData, ShellError, Signature};
#[derive(Clone)]
pub struct StrCollectDeprecated;
impl Command for StrCollectDeprecated {
fn name(&self) -> &str {
"str collect"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Deprecated)
}
fn usage(&self) -> &str {
"Deprecated command."
}
fn run(
&self,
_: &EngineState,
_: &mut Stack,
call: &Call,
_: PipelineData,
) -> Result<PipelineData, ShellError> {
Err(nu_protocol::ShellError::DeprecatedCommand(
self.name().to_string(),
"str join".to_owned(),
call.head,
))
}
}

View File

@@ -23,6 +23,5 @@ pub fn deprecated_commands() -> HashMap<String, String> {
("str lpad".to_string(), "fill".to_string()),
("str rpad".to_string(), "fill".to_string()),
("benchmark".to_string(), "timeit".to_string()),
("str collect".to_string(), "str join".to_string()),
])
}

View File

@@ -1,4 +1,3 @@
mod collect;
mod deprecated_commands;
mod export_old_alias;
mod hash_base64;
@@ -12,7 +11,6 @@ mod str_decimal;
mod str_find_replace;
mod str_int;
pub use collect::StrCollectDeprecated;
pub use deprecated_commands::*;
pub use export_old_alias::ExportOldAlias;
pub use hash_base64::HashBase64;

View File

@@ -35,7 +35,10 @@ pub(crate) fn get_editor(
if let Some((a, b)) = editor.split_once(' ') {
Ok((
a.to_string(),
b.split(' ').map(|s| s.to_string()).collect::<Vec<String>>(),
b.split(' ')
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
))
} else {
Ok((editor, Vec::new()))

View File

@@ -24,7 +24,7 @@ impl Command for LetEnv {
.required("var_name", SyntaxShape::String, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Env)

View File

@@ -129,7 +129,7 @@ impl Command for Ls {
));
}
if is_empty_dir(&expanded) {
return Ok(Value::list(vec![], call_span).into_pipeline_data());
return Ok(Value::nothing(call_span).into_pipeline_data());
}
p.push("*");
}
@@ -141,7 +141,7 @@ impl Command for Ls {
if directory {
(PathBuf::from("."), call_span, false)
} else if is_empty_dir(current_dir(engine_state, stack)?) {
return Ok(Value::list(vec![], call_span).into_pipeline_data());
return Ok(Value::nothing(call_span).into_pipeline_data());
} else {
(PathBuf::from("./*"), call_span, false)
}

View File

@@ -87,8 +87,8 @@ impl Command for Mv {
if sources.is_empty() {
return Err(ShellError::GenericError(
"File(s) not found".into(),
"could not find any files matching this glob pattern".into(),
"Invalid file or pattern".into(),
"invalid file or pattern".into(),
Some(spanned_source.span),
None,
Vec::new(),

View File

@@ -1,10 +1,10 @@
use nu_engine::{current_dir, eval_block, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::util::BufferedReader;
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, RawStream, ShellError,
Signature, Spanned, SyntaxShape, Type, Value,
Category, Example, PipelineData, RawStream, ShellError, Signature, Spanned, SyntaxShape, Type,
Value,
};
use std::io::BufReader;
@@ -38,11 +38,6 @@ impl Command for Open {
Signature::build("open")
.input_output_types(vec![(Type::Nothing, Type::Any), (Type::String, Type::Any)])
.optional("filename", SyntaxShape::Filepath, "the filename to use")
.rest(
"filenames",
SyntaxShape::Filepath,
"optional additional files to open",
)
.switch("raw", "open file as raw binary", Some('r'))
.category(Category::FileSystem)
}
@@ -57,16 +52,24 @@ impl Command for Open {
let raw = call.has_flag("raw");
let call_span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let cwd = current_dir(engine_state, stack)?;
let req_path = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
let mut path_params = call.rest::<Spanned<String>>(engine_state, stack, 1)?;
let path = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
// FIXME: JT: what is this doing here?
let path = {
if let Some(path_val) = path {
Some(Spanned {
item: nu_utils::strip_ansi_string_unlikely(path_val.item),
span: path_val.span,
})
} else {
path
}
};
if let Some(filename) = req_path {
path_params.insert(0, filename);
let path = if let Some(path) = path {
path
} else {
let filename = match input {
// Collect a filename from the input
match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {
return Err(ShellError::MissingParameter {
param_name: "needs filename".to_string(),
@@ -80,142 +83,103 @@ impl Command for Open {
span: call.head,
});
}
}
};
let arg_span = path.span;
let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
let path = Path::new(path_no_whitespace);
if permission_denied(path) {
#[cfg(unix)]
let error_msg = match path.metadata() {
Ok(md) => format!(
"The permissions of {:o} does not allow access for this user",
md.permissions().mode() & 0o0777
),
Err(e) => e.to_string(),
};
path_params.insert(0, filename);
}
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
Err(ShellError::GenericError(
"Permission denied".into(),
error_msg,
Some(arg_span),
None,
Vec::new(),
))
} else {
#[cfg(feature = "sqlite")]
if !raw {
let res = SQLiteDatabase::try_from_path(path, arg_span, ctrlc.clone())
.map(|db| db.into_value(call.head).into_pipeline_data());
let mut output = vec![];
for path in path_params.into_iter() {
//FIXME: `open` should not have to do this
let path = {
Spanned {
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
if res.is_ok() {
return res;
}
};
}
let arg_span = path.span;
// let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
for path in nu_engine::glob_from(&path, &cwd, call_span, None)?.1 {
let path = path?;
let path = Path::new(&path);
if permission_denied(path) {
#[cfg(unix)]
let error_msg = match path.metadata() {
Ok(md) => format!(
"The permissions of {:o} does not allow access for this user",
md.permissions().mode() & 0o0777
),
Err(e) => e.to_string(),
};
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
let file = match std::fs::File::open(path) {
Ok(file) => file,
Err(err) => {
return Err(ShellError::GenericError(
"Permission denied".into(),
error_msg,
err.to_string(),
Some(arg_span),
None,
Vec::new(),
));
} else {
#[cfg(feature = "sqlite")]
if !raw {
let res = SQLiteDatabase::try_from_path(path, arg_span, ctrlc.clone())
.map(|db| db.into_value(call.head).into_pipeline_data());
if res.is_ok() {
return res;
}
}
let file = match std::fs::File::open(path) {
Ok(file) => file,
Err(err) => {
return Err(ShellError::GenericError(
"Permission denied".into(),
err.to_string(),
Some(arg_span),
None,
Vec::new(),
));
}
};
let buf_reader = BufReader::new(file);
let file_contents = PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(BufferedReader { input: buf_reader }),
ctrlc.clone(),
call_span,
None,
)),
stderr: None,
exit_code: None,
span: call_span,
metadata: None,
trim_end_newline: false,
};
let ext = if raw {
None
} else {
path.extension()
.map(|name| name.to_string_lossy().to_string())
};
if let Some(ext) = ext {
match engine_state.find_decl(format!("from {ext}").as_bytes(), &[]) {
Some(converter_id) => {
let decl = engine_state.get_decl(converter_id);
let command_output = if let Some(block_id) = decl.get_block_id() {
let block = engine_state.get_block(block_id);
eval_block(
engine_state,
stack,
block,
file_contents,
false,
false,
)
} else {
decl.run(
engine_state,
stack,
&Call::new(call_span),
file_contents,
)
};
output.push(command_output.map_err(|inner| {
ShellError::GenericError(
format!("Error while parsing as {ext}"),
format!("Could not parse '{}' with `from {}`", path.display(), ext),
Some(arg_span),
Some(format!("Check out `help from {}` or `help from` for more options or open raw data with `open --raw '{}'`", ext, path.display())),
vec![inner],
)
})?);
}
None => output.push(file_contents),
}
} else {
output.push(file_contents)
}
}
}
}
};
if output.is_empty() {
Ok(PipelineData::Empty)
} else if output.len() == 1 {
Ok(output.remove(0))
} else {
Ok(output.into_iter().flatten().into_pipeline_data(ctrlc))
let buf_reader = BufReader::new(file);
let output = PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(BufferedReader { input: buf_reader }),
ctrlc,
call_span,
None,
)),
stderr: None,
exit_code: None,
span: call_span,
metadata: None,
trim_end_newline: false,
};
let ext = if raw {
None
} else {
path.extension()
.map(|name| name.to_string_lossy().to_string())
};
if let Some(ext) = ext {
match engine_state.find_decl(format!("from {ext}").as_bytes(), &[]) {
Some(converter_id) => {
let decl = engine_state.get_decl(converter_id);
if let Some(block_id) = decl.get_block_id() {
let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, output, false, false)
} else {
decl.run(engine_state, stack, &Call::new(call_span), output)
}
.map_err(|inner| {
ShellError::GenericError(
format!("Error while parsing as {ext}"),
format!("Could not parse '{}' with `from {}`", path.display(), ext),
Some(arg_span),
Some(format!("Check out `help from {}` or `help from` for more options or open raw data with `open --raw '{}'`", ext, path.display())),
vec![inner],
)
})
}
None => Ok(output),
}
} else {
Ok(output)
}
}
}

View File

@@ -16,7 +16,7 @@ impl Command for Start {
}
fn usage(&self) -> &str {
"Open a folder, file or website in the default application or viewer."
"Open a folder,file or website in the default application or viewer."
}
fn search_terms(&self) -> Vec<&str> {

View File

@@ -1,11 +1,11 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
use super::utils;
#[derive(Clone)]
pub struct All;
@@ -45,7 +45,7 @@ impl Command for All {
},
Example {
description: "Check that each item is a string",
example: "[foo bar 2 baz] | all {|| ($in | describe) == 'string' }",
example: "[foo bar 2 baz] | all { ($in | describe) == 'string' }",
result: Some(Value::test_bool(false)),
},
Example {
@@ -60,7 +60,9 @@ impl Command for All {
},
]
}
// This is almost entirely a copy-paste of `any`'s run(), so make sure any changes to this are
// reflected in the other!! (Or, you could figure out a way for both of them to use
// the same function...)
fn run(
&self,
engine_state: &EngineState,
@@ -68,7 +70,51 @@ impl Command for All {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
utils::boolean_fold(engine_state, stack, call, input, false)
let span = call.head;
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block_id = capture_block.block_id;
let block = engine_state.get_block(block_id);
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id {
stack.add_var(var_id, value.clone());
}
let eval = eval_block(
&engine_state,
&mut stack,
block,
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match eval {
Err(e) => {
return Err(e);
}
Ok(pipeline_data) => {
if !pipeline_data.into_value(span).is_true() {
return Ok(Value::Bool { val: false, span }.into_pipeline_data());
}
}
}
}
Ok(Value::Bool { val: true, span }.into_pipeline_data())
}
}

View File

@@ -1,11 +1,11 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
use super::utils;
#[derive(Clone)]
pub struct Any;
@@ -45,7 +45,7 @@ impl Command for Any {
},
Example {
description: "Check that any item is a string",
example: "[1 2 3 4] | any {|| ($in | describe) == 'string' }",
example: "[1 2 3 4] | any { ($in | describe) == 'string' }",
result: Some(Value::test_bool(false)),
},
Example {
@@ -60,7 +60,9 @@ impl Command for Any {
},
]
}
// This is almost entirely a copy-paste of `all`'s run(), so make sure any changes to this are
// reflected in the other!! Or, you could figure out a way for both of them to use
// the same function...
fn run(
&self,
engine_state: &EngineState,
@@ -68,7 +70,51 @@ impl Command for Any {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
utils::boolean_fold(engine_state, stack, call, input, true)
let span = call.head;
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block_id = capture_block.block_id;
let block = engine_state.get_block(block_id);
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id {
stack.add_var(var_id, value.clone());
}
let eval = eval_block(
&engine_state,
&mut stack,
block,
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match eval {
Err(e) => {
return Err(e);
}
Ok(pipeline_data) => {
if pipeline_data.into_value(span).is_true() {
return Ok(Value::Bool { val: true, span }.into_pipeline_data());
}
}
}
}
Ok(Value::Bool { val: false, span }.into_pipeline_data())
}
}

View File

@@ -72,10 +72,10 @@ impl Command for Collect {
// for when we support `data | let x = $in;`
// remove the variables added earlier
for var_id in capture_block.captures.keys() {
stack_captures.remove_var(*var_id);
stack_captures.vars.remove(var_id);
}
if let Some(u) = saved_positional {
stack_captures.remove_var(u);
stack_captures.vars.remove(&u);
}
// add any new variables to the stack
stack.vars.extend(stack_captures.vars);

View File

@@ -109,7 +109,10 @@ fn dropcol(
let mut vals = vec![];
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
let fetcher =
input_val
.clone()
.follow_cell_path(&path.members, false, false)?;
cols.push(path.into_string());
vals.push(fetcher);
}
@@ -133,7 +136,10 @@ fn dropcol(
let mut vals = vec![];
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
let fetcher =
input_val
.clone()
.follow_cell_path(&path.members, false, false)?;
cols.push(path.into_string());
vals.push(fetcher);
}
@@ -149,7 +155,9 @@ fn dropcol(
let mut vals = vec![];
for cell_path in &keep_columns {
let result = v.clone().follow_cell_path(&cell_path.members, false)?;
let result = v
.clone()
.follow_cell_path(&cell_path.members, false, false)?;
cols.push(cell_path.into_string());
vals.push(result);

View File

@@ -74,7 +74,7 @@ with 'transpose' first."#
}),
},
Example {
example: "{major:2, minor:1, patch:4} | values | each {|| into string }",
example: "{major:2, minor:1, patch:4} | values | each { into string }",
description: "Produce a list of values in the record, converted to string",
result: Some(Value::List {
vals: vec![

View File

@@ -74,7 +74,7 @@ fn empty(
for val in input {
for column in &columns {
let val = val.clone();
match val.follow_cell_path(&column.members, false) {
match val.follow_cell_path(&column.members, false, false) {
Ok(Value::Nothing { .. }) => {}
Ok(_) => return Ok(Value::boolean(false, head).into_pipeline_data()),
Err(err) => return Err(err),

View File

@@ -119,7 +119,7 @@ impl Command for Find {
},
Example {
description: "Find value in records",
example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu""#,
example: r#"[[version name]; [0.1.0 nushell] [0.1.1 fish] [0.2.0 zsh]] | find -r "nu""#,
result: Some(Value::List {
vals: vec![Value::test_record(
vec!["version", "name"],
@@ -404,7 +404,6 @@ fn find_with_rest_and_highlight(
Err(_) => false,
},
Value::Binary { .. } => false,
Value::MatchPattern { .. } => false,
}) != invert
},
ctrlc,
@@ -485,7 +484,6 @@ fn find_with_rest_and_highlight(
Err(_) => false,
},
Value::Binary { .. } => false,
Value::MatchPattern { .. } => false,
}) != invert
}),
ctrlc.clone(),

View File

@@ -279,7 +279,7 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span, all: bool) ->
if !columns.is_empty() {
let cell_path =
column_requested.and_then(|x| match x.members.first() {
Some(PathMember::String { val, span: _, .. }) => Some(val),
Some(PathMember::String { val, span: _ }) => Some(val),
_ => None,
});

View File

@@ -43,7 +43,7 @@ If multiple cell paths are given, this will produce a list of values."#
.rest("rest", SyntaxShape::CellPath, "additional cell paths")
.switch(
"ignore-errors",
"ignore missing data (make all cell path members optional)",
"when there are empty cells, instead of erroring out, replace them with nothing",
Some('i'),
)
.switch(
@@ -62,20 +62,16 @@ If multiple cell paths are given, this will produce a list of values."#
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let mut cell_path: CellPath = call.req(engine_state, stack, 0)?;
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let ignore_errors = call.has_flag("ignore-errors");
let sensitive = call.has_flag("sensitive");
let ignore_errors = call.has_flag("ignore-errors");
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata();
if ignore_errors {
cell_path.make_optional();
}
if rest.is_empty() {
input
.follow_cell_path(&cell_path.members, call.head, !sensitive)
.follow_cell_path(&cell_path.members, call.head, !sensitive, ignore_errors)
.map(|x| x.into_pipeline_data())
} else {
let mut output = vec![];
@@ -85,7 +81,9 @@ If multiple cell paths are given, this will produce a list of values."#
let input = input.into_value(span);
for path in paths {
let val = input.clone().follow_cell_path(&path.members, !sensitive);
let val = input
.clone()
.follow_cell_path(&path.members, !sensitive, false);
output.push(val?);
}

View File

@@ -1,422 +0,0 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Config, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use std::cmp::max;
use std::collections::{HashMap, HashSet};
#[derive(Clone)]
pub struct Join;
enum JoinType {
Inner,
Left,
Right,
Outer,
}
enum IncludeInner {
No,
Yes,
}
type RowEntries<'a> = Vec<(&'a Vec<String>, &'a Vec<Value>)>;
const EMPTY_COL_NAMES: &Vec<String> = &vec![];
impl Command for Join {
fn name(&self) -> &str {
"join"
}
fn signature(&self) -> Signature {
Signature::build("join")
.required(
"right-table",
SyntaxShape::List(Box::new(SyntaxShape::Any)),
"The right table in the join",
)
.required(
"left-on",
SyntaxShape::String,
"Name of column in input (left) table to join on",
)
.optional(
"right-on",
SyntaxShape::String,
"Name of column in right table to join on. Defaults to same column as left table.",
)
.switch("inner", "Inner join (default)", Some('i'))
.switch("left", "Left-outer join", Some('l'))
.switch("right", "Right-outer join", Some('r'))
.switch("outer", "Outer join", Some('o'))
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
}
fn usage(&self) -> &str {
"Join two tables"
}
fn search_terms(&self) -> Vec<&str> {
vec!["sql"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let table_2: Value = call.req(engine_state, stack, 0)?;
let l_on: Value = call.req(engine_state, stack, 1)?;
let r_on: Value = call
.opt(engine_state, stack, 2)?
.unwrap_or_else(|| l_on.clone());
let span = call.head;
let join_type = join_type(call)?;
// FIXME: we should handle ListStreams properly instead of collecting
let collected_input = input.into_value(span);
match (&collected_input, &table_2, &l_on, &r_on) {
(
Value::List { vals: rows_1, .. },
Value::List { vals: rows_2, .. },
Value::String { val: l_on, .. },
Value::String { val: r_on, .. },
) => {
let result = join(rows_1, rows_2, l_on, r_on, join_type, span);
Ok(PipelineData::Value(result, None))
}
_ => Err(ShellError::UnsupportedInput(
"(PipelineData<table>, table, string, string)".into(),
format!(
"({:?}, {:?}, {:?} {:?})",
collected_input,
table_2.get_type(),
l_on.get_type(),
r_on.get_type(),
),
span,
span,
)),
}
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Join two tables",
example: "[{a: 1 b: 2}] | join [{a: 1 c: 3}] a",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["a".into(), "b".into(), "c".into()],
vals: vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::Int {
val: 2,
span: Span::test_data(),
},
Value::Int {
val: 3,
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
}]
}
}
fn join_type(call: &Call) -> Result<JoinType, nu_protocol::ShellError> {
match (
call.has_flag("inner"),
call.has_flag("left"),
call.has_flag("right"),
call.has_flag("outer"),
) {
(_, false, false, false) => Ok(JoinType::Inner),
(false, true, false, false) => Ok(JoinType::Left),
(false, false, true, false) => Ok(JoinType::Right),
(false, false, false, true) => Ok(JoinType::Outer),
_ => Err(ShellError::UnsupportedInput(
"Choose one of: --inner, --left, --right, --outer".into(),
"".into(),
call.head,
call.head,
)),
}
}
fn join(
left: &Vec<Value>,
right: &Vec<Value>,
left_join_key: &str,
right_join_key: &str,
join_type: JoinType,
span: Span,
) -> Value {
// Inner / Right Join
// ------------------
// Make look-up table from rows on left
// For each row r on right:
// If any matching rows on left:
// For each matching row l on left:
// Emit (l, r)
// Else if RightJoin:
// Emit (null, r)
// Left Join
// ----------
// Make look-up table from rows on right
// For each row l on left:
// If any matching rows on right:
// For each matching row r on right:
// Emit (l, r)
// Else:
// Emit (l, null)
// Outer Join
// ----------
// Perform Left Join procedure
// Perform Right Join procedure, but excluding rows in Inner Join
let config = Config::default();
let sep = ",";
let cap = max(left.len(), right.len());
let shared_join_key = if left_join_key == right_join_key {
Some(left_join_key)
} else {
None
};
// For the "other" table, create a map from value in `on` column to a list of the
// rows having that value.
let mut result: Vec<Value> = Vec::new();
let is_outer = matches!(join_type, JoinType::Outer);
let (this, this_join_key, other, other_keys, join_type) = match join_type {
JoinType::Left | JoinType::Outer => (
left,
left_join_key,
lookup_table(right, right_join_key, sep, cap, &config),
column_names(right),
// For Outer we do a Left pass and a Right pass; this is the Left
// pass.
JoinType::Left,
),
JoinType::Inner | JoinType::Right => (
right,
right_join_key,
lookup_table(left, left_join_key, sep, cap, &config),
column_names(left),
join_type,
),
};
join_rows(
&mut result,
this,
this_join_key,
other,
other_keys,
shared_join_key,
&join_type,
IncludeInner::Yes,
sep,
&config,
span,
);
if is_outer {
let (this, this_join_key, other, other_names, join_type) = (
right,
right_join_key,
lookup_table(left, left_join_key, sep, cap, &config),
column_names(left),
JoinType::Right,
);
join_rows(
&mut result,
this,
this_join_key,
other,
other_names,
shared_join_key,
&join_type,
IncludeInner::No,
sep,
&config,
span,
);
}
Value::List { vals: result, span }
}
// Join rows of `this` (a nushell table) to rows of `other` (a lookup-table
// containing rows of a nushell table).
#[allow(clippy::too_many_arguments)]
fn join_rows(
result: &mut Vec<Value>,
this: &Vec<Value>,
this_join_key: &str,
other: HashMap<String, RowEntries>,
other_keys: &Vec<String>,
shared_join_key: Option<&str>,
join_type: &JoinType,
include_inner: IncludeInner,
sep: &str,
config: &Config,
span: Span,
) {
for this_row in this {
if let Value::Record {
cols: this_cols,
vals: this_vals,
..
} = this_row
{
if let Some(this_valkey) = this_row.get_data_by_key(this_join_key) {
if let Some(other_rows) = other.get(&this_valkey.into_string(sep, config)) {
if matches!(include_inner, IncludeInner::Yes) {
for (other_cols, other_vals) in other_rows {
// `other` table contains rows matching `this` row on the join column
let (res_cols, res_vals) = match join_type {
JoinType::Inner | JoinType::Right => merge_records(
(other_cols, other_vals), // `other` (lookup) is the left input table
(this_cols, this_vals),
shared_join_key,
),
JoinType::Left => merge_records(
(this_cols, this_vals), // `this` is the left input table
(other_cols, other_vals),
shared_join_key,
),
_ => panic!("not implemented"),
};
result.push(Value::Record {
cols: res_cols,
vals: res_vals,
span,
})
}
}
} else if !matches!(join_type, JoinType::Inner) {
// `other` table did not contain any rows matching
// `this` row on the join column; emit a single joined
// row with null values for columns not present,
let other_vals = other_keys
.iter()
.map(|key| {
if Some(key.as_ref()) == shared_join_key {
this_row
.get_data_by_key(key)
.unwrap_or_else(|| Value::nothing(span))
} else {
Value::nothing(span)
}
})
.collect();
let (res_cols, res_vals) = match join_type {
JoinType::Inner | JoinType::Right => merge_records(
(other_keys, &other_vals),
(this_cols, this_vals),
shared_join_key,
),
JoinType::Left => merge_records(
(this_cols, this_vals),
(other_keys, &other_vals),
shared_join_key,
),
_ => panic!("not implemented"),
};
result.push(Value::Record {
cols: res_cols,
vals: res_vals,
span,
})
}
} // else { a row is missing a value for the join column }
};
}
}
// Return column names (i.e. ordered keys from the first row; we assume that
// these are the same for all rows).
fn column_names(table: &[Value]) -> &Vec<String> {
table
.iter()
.find_map(|val| match val {
Value::Record { cols, .. } => Some(cols),
_ => None,
})
.unwrap_or(EMPTY_COL_NAMES)
}
// Create a map from value in `on` column to a list of the rows having that
// value.
fn lookup_table<'a>(
rows: &'a Vec<Value>,
on: &str,
sep: &str,
cap: usize,
config: &Config,
) -> HashMap<String, RowEntries<'a>> {
let mut map = HashMap::<String, RowEntries>::with_capacity(cap);
for row in rows {
if let Value::Record { cols, vals, .. } = row {
if let Some(val) = &row.get_data_by_key(on) {
let valkey = val.into_string(sep, config);
map.entry(valkey).or_default().push((cols, vals));
}
};
}
map
}
// Merge `left` and `right` records, renaming keys in `right` where they clash
// with keys in `left`. If `shared_key` is supplied then it is the name of a key
// that should not be renamed (its values are guaranteed to be equal).
fn merge_records(
left: (&Vec<String>, &Vec<Value>),
right: (&Vec<String>, &Vec<Value>),
shared_key: Option<&str>,
) -> (Vec<String>, Vec<Value>) {
let ((l_keys, l_vals), (r_keys, r_vals)) = (left, right);
let cap = max(l_keys.len(), r_keys.len());
let mut seen = HashSet::with_capacity(cap);
let (mut res_keys, mut res_vals) = (Vec::with_capacity(cap), Vec::with_capacity(cap));
for (k, v) in l_keys.iter().zip(l_vals) {
res_keys.push(k.clone());
res_vals.push(v.clone());
seen.insert(k);
}
for (k, v) in r_keys.iter().zip(r_vals) {
let k_seen = seen.contains(k);
let k_shared = shared_key == Some(k);
// Do not output shared join key twice
if !(k_seen && k_shared) {
res_keys.push(if k_seen { format!("{}_", k) } else { k.clone() });
res_vals.push(v.clone());
}
}
(res_keys, res_vals)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Join {})
}
}

View File

@@ -1,6 +1,7 @@
mod all;
mod any;
mod append;
mod collect;
mod columns;
mod compact;
mod default;
@@ -19,7 +20,6 @@ mod group;
mod group_by;
mod headers;
mod insert;
mod join;
mod last;
mod length;
mod lines;
@@ -57,6 +57,7 @@ mod zip;
pub use all::All;
pub use any::Any;
pub use append::Append;
pub use collect::Collect;
pub use columns::Columns;
pub use compact::Compact;
pub use default::Default;
@@ -75,7 +76,6 @@ pub use group::Group;
pub use group_by::GroupBy;
pub use headers::Headers;
pub use insert::Insert;
pub use join::Join;
pub use last::Last;
pub use length::Length;
pub use lines::Lines;

View File

@@ -30,12 +30,6 @@ impl Command for ParEach {
),
(Type::Table(vec![]), Type::List(Box::new(Type::Any))),
])
.named(
"threads",
SyntaxShape::Int,
"the number of threads to use",
Some('t'),
)
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
@@ -91,27 +85,8 @@ impl Command for ParEach {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
fn create_pool(num_threads: usize) -> Result<rayon::ThreadPool, ShellError> {
match rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.build()
{
Err(e) => Err(e).map_err(|e| {
ShellError::GenericError(
"Error creating thread pool".into(),
e.to_string(),
Some(Span::unknown()),
None,
Vec::new(),
)
}),
Ok(pool) => Ok(pool),
}
}
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let threads: Option<usize> = call.get_flag(engine_state, stack, "threads")?;
let max_threads = threads.unwrap_or(0);
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let block_id = capture_block.block_id;
@@ -121,165 +96,156 @@ impl Command for ParEach {
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(create_pool(max_threads)?
.install(|| {
val.into_range_iter(ctrlc.clone())
.expect("unable to create a range iterator")
.par_bridge()
.map(move |x| {
let block = engine_state.get_block(block_id);
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val
.into_range_iter(ctrlc.clone())?
.par_bridge()
.map(move |x| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)
})),
PipelineData::Value(Value::List { vals: val, .. }, ..) => Ok(create_pool(max_threads)?
.install(|| {
val.par_iter()
.map(move |x| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)
})),
PipelineData::ListStream(stream, ..) => Ok(create_pool(max_threads)?.install(|| {
stream
.par_bridge()
.map(move |x| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
.into_pipeline_data(),
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)
})),
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
PipelineData::Value(Value::List { vals: val, .. }, ..) => Ok(val
.into_iter()
.par_bridge()
.map(move |x| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
PipelineData::ListStream(stream, ..) => Ok(stream
.par_bridge()
.map(move |x| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(chain_error_with_input(error, val_span)),
}
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => Ok(create_pool(max_threads)?.install(|| {
stream
.par_bridge()
.map(move |x| {
let x = match x {
Ok(x) => x,
Err(err) => {
return Value::Error {
error: Box::new(err),
}
.into_pipeline_data()
}
};
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
} => Ok(stream
.par_bridge()
.map(move |x| {
let x = match x {
Ok(x) => x,
Err(err) => {
return Value::Error {
error: Box::new(err),
}
.into_pipeline_data()
}
};
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(error),
}
.into_pipeline_data(),
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)
})),
}
match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error {
error: Box::new(error),
}
.into_pipeline_data(),
}
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
// This match allows non-iterables to be accepted,
// which is currently considered undesirable (Nov 2022).
PipelineData::Value(x, ..) => {
eprint!("value");
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {

View File

@@ -24,7 +24,7 @@ impl Command for Select {
])
.switch(
"ignore-errors",
"ignore missing data (make all cell path members optional)",
"when an error occurs, instead of erroring out, suppress the error message",
Some('i'),
)
.rest(
@@ -56,17 +56,11 @@ produce a table, a list will produce a list, and a record will produce a record.
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut columns: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let ignore_errors = call.has_flag("ignore-errors");
let columns: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let span = call.head;
let ignore_errors = call.has_flag("ignore-errors");
if ignore_errors {
for cell_path in &mut columns {
cell_path.make_optional();
}
}
select(engine_state, span, columns, input)
select(engine_state, span, columns, input, ignore_errors)
}
fn examples(&self) -> Vec<Example> {
@@ -103,6 +97,7 @@ fn select(
call_span: Span,
columns: Vec<CellPath>,
input: PipelineData,
ignore_errors: bool,
) -> Result<PipelineData, ShellError> {
let mut unique_rows: HashSet<usize> = HashSet::new();
@@ -111,8 +106,11 @@ fn select(
for column in columns {
let CellPath { ref members } = column;
match members.get(0) {
Some(PathMember::Int { val, span, .. }) => {
Some(PathMember::Int { val, span }) => {
if members.len() > 1 {
if ignore_errors {
return Ok(Value::nothing(call_span).into_pipeline_data());
}
return Err(ShellError::GenericError(
"Select only allows row numbers for rows".into(),
"extra after row number".into(),
@@ -167,14 +165,20 @@ fn select(
) => {
let mut output = vec![];
let mut columns_with_value = Vec::new();
let mut allempty = true;
for input_val in input_vals {
if !columns.is_empty() {
let mut cols = vec![];
let mut vals = vec![];
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
match input_val.clone().follow_cell_path(
&path.members,
false,
ignore_errors,
) {
Ok(fetcher) => {
allempty = false;
cols.push(path.into_string().replace('.', "_"));
vals.push(fetcher);
if !columns_with_value.contains(&path) {
@@ -192,11 +196,14 @@ fn select(
output.push(input_val)
}
}
Ok(output
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone())
.set_metadata(metadata))
if allempty {
Ok(Value::nothing(call_span).into_pipeline_data())
} else {
Ok(output
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone())
.set_metadata(metadata))
}
}
PipelineData::ListStream(stream, metadata, ..) => {
let mut values = vec![];
@@ -207,7 +214,10 @@ fn select(
let mut vals = vec![];
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
match x
.clone()
.follow_cell_path(&path.members, false, ignore_errors)
{
Ok(value) => {
cols.push(path.into_string().replace('.', "_"));
vals.push(value);
@@ -236,7 +246,10 @@ fn select(
for cell_path in columns {
// FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) {
match v
.clone()
.follow_cell_path(&cell_path.members, false, ignore_errors)
{
Ok(result) => {
cols.push(cell_path.into_string().replace('.', "_"));
vals.push(result);

View File

@@ -271,12 +271,22 @@ pub fn sort(
insensitive: bool,
natural: bool,
) -> Result<(), ShellError> {
match vec.first() {
Some(Value::Record {
if vec.is_empty() {
return Err(ShellError::GenericError(
"no values to work with".to_string(),
"".to_string(),
None,
Some("no values to work with".to_string()),
Vec::new(),
));
}
match &vec[0] {
Value::Record {
cols,
vals: _input_vals,
..
}) => {
} => {
let columns = cols.clone();
vec.sort_by(|a, b| process(a, b, &columns, span, insensitive, natural));
}

View File

@@ -244,7 +244,7 @@ fn sort_attributes(val: Value) -> Value {
fn generate_key(item: &ValueCounter) -> Result<String, ShellError> {
let value = sort_attributes(item.val_to_compare.clone()); //otherwise, keys could be different for Records
value_to_string(&value, Span::unknown(), 0, &None)
value_to_string(&value, Span::unknown())
}
fn generate_results_with_count(head: Span, uniq_values: Vec<ValueCounter>) -> Vec<Value> {

View File

@@ -107,11 +107,21 @@ impl Command for UniqBy {
}
fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), ShellError> {
if let Some(Value::Record {
if vec.is_empty() {
return Err(ShellError::GenericError(
"no values to work with".to_string(),
"".to_string(),
None,
Some("no values to work with".to_string()),
Vec::new(),
));
}
if let Value::Record {
cols,
vals: _input_vals,
span: val_span,
}) = vec.first()
} = &vec[0]
{
if columns.is_empty() {
// This uses the same format as the 'requires a column name' error in split_by.rs

View File

@@ -143,7 +143,7 @@ fn update(
ctrlc,
)
} else {
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.get(0) {
if let Some(PathMember::Int { val, span }) = cell_path.members.get(0) {
let mut input = input.into_iter();
let mut pre_elems = vec![];

View File

@@ -165,7 +165,7 @@ fn upsert(
ctrlc,
)
} else {
if let Some(PathMember::Int { val, span, .. }) = cell_path.members.get(0) {
if let Some(PathMember::Int { val, span }) = cell_path.members.get(0) {
let mut input = input.into_iter();
let mut pre_elems = vec![];

View File

@@ -1,9 +1,4 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, EngineState, Stack},
IntoPipelineData, PipelineData, ShellError, Span, Value,
};
use nu_protocol::{ShellError, Span};
pub fn chain_error_with_input(
error_source: ShellError,
@@ -14,66 +9,3 @@ pub fn chain_error_with_input(
}
error_source
}
pub fn boolean_fold(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
accumulator: bool,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block_id = capture_block.block_id;
let block = engine_state.get_block(block_id);
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
for value in input.into_interruptible_iter(ctrlc) {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var_id) = var_id {
stack.add_var(var_id, value.clone());
}
let eval = eval_block(
&engine_state,
&mut stack,
block,
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match eval {
Err(e) => {
return Err(e);
}
Ok(pipeline_data) => {
if pipeline_data.into_value(span).is_true() == accumulator {
return Ok(Value::Bool {
val: accumulator,
span,
}
.into_pipeline_data());
}
}
}
}
Ok(Value::Bool {
val: !accumulator,
span,
}
.into_pipeline_data())
}

View File

@@ -1,4 +1,4 @@
use super::delimited::{from_delimited_data, trim_from_str, DelimitedReaderConfig};
use super::delimited::{from_delimited_data, trim_from_str};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
@@ -24,34 +24,11 @@ impl Command for FromCsv {
"a character to separate columns, defaults to ','",
Some('s'),
)
.named(
"comment",
SyntaxShape::String,
"a comment character to ignore lines starting with it",
Some('c'),
)
.named(
"quote",
SyntaxShape::String,
"a quote character to ignore separators in strings, defaults to '\"'",
Some('q'),
)
.named(
"escape",
SyntaxShape::String,
"an escape character for strings containing the quote character",
Some('e'),
)
.switch(
"noheaders",
"don't treat the first row as column names",
Some('n'),
)
.switch(
"flexible",
"allow the number of fields in records to be variable",
None,
)
.switch("no-infer", "no field type inferencing", None)
.named(
"trim",
@@ -98,28 +75,28 @@ impl Command for FromCsv {
example: "open data.txt | from csv --noheaders",
result: None,
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv -n",
result: None,
},
Example {
description: "Convert semicolon-separated data to a table",
example: "open data.txt | from csv --separator ';'",
result: None,
},
Example {
description: "Convert comma-separated data to a table, ignoring lines starting with '#'",
example: "open data.txt | from csv --comment '#'",
result: None,
},
Example {
description: "Convert comma-separated data to a table, dropping all possible whitespaces around header names and field values",
description: "Convert semicolon-separated data to a table, dropping all possible whitespaces around header names and field values",
example: "open data.txt | from csv --trim all",
result: None,
},
Example {
description: "Convert comma-separated data to a table, dropping all possible whitespaces around header names",
description: "Convert semicolon-separated data to a table, dropping all possible whitespaces around header names",
example: "open data.txt | from csv --trim headers",
result: None,
},
Example {
description: "Convert comma-separated data to a table, dropping all possible whitespaces around field values",
description: "Convert semicolon-separated data to a table, dropping all possible whitespaces around field values",
example: "open data.txt | from csv --trim fields",
result: None,
},
@@ -135,41 +112,32 @@ fn from_csv(
) -> Result<PipelineData, ShellError> {
let name = call.head;
let separator = call
.get_flag(engine_state, stack, "separator")?
.map(|v: Value| v.as_char())
.transpose()?
.unwrap_or(',');
let comment = call
.get_flag(engine_state, stack, "comment")?
.map(|v: Value| v.as_char())
.transpose()?;
let quote = call
.get_flag(engine_state, stack, "quote")?
.map(|v: Value| v.as_char())
.transpose()?
.unwrap_or('"');
let escape = call
.get_flag(engine_state, stack, "escape")?
.map(|v: Value| v.as_char())
.transpose()?;
let no_infer = call.has_flag("no-infer");
let noheaders = call.has_flag("noheaders");
let flexible = call.has_flag("flexible");
let trim = trim_from_str(call.get_flag(engine_state, stack, "trim")?)?;
let separator: Option<Value> = call.get_flag(engine_state, stack, "separator")?;
let trim: Option<Value> = call.get_flag(engine_state, stack, "trim")?;
let config = DelimitedReaderConfig {
separator,
comment,
quote,
escape,
noheaders,
flexible,
no_infer,
trim,
let sep = match separator {
Some(Value::String { val: s, span }) => {
if s == r"\t" {
'\t'
} else {
let vec_s: Vec<char> = s.chars().collect();
if vec_s.len() != 1 {
return Err(ShellError::MissingParameter {
param_name: "single character separator".into(),
span,
});
};
vec_s[0]
}
}
_ => ',',
};
from_delimited_data(config, input, name)
let trim = trim_from_str(trim)?;
from_delimited_data(noheaders, no_infer, sep, trim, input, name)
}
#[cfg(test)]

View File

@@ -2,26 +2,16 @@ use csv::{ReaderBuilder, Trim};
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value};
fn from_delimited_string_to_value(
DelimitedReaderConfig {
separator,
comment,
quote,
escape,
noheaders,
flexible,
no_infer,
trim,
}: DelimitedReaderConfig,
s: String,
noheaders: bool,
no_infer: bool,
separator: char,
trim: Trim,
span: Span,
) -> Result<Value, csv::Error> {
let mut reader = ReaderBuilder::new()
.has_headers(!noheaders)
.flexible(flexible)
.delimiter(separator as u8)
.comment(comment.map(|c| c as u8))
.quote(quote as u8)
.escape(escape.map(|c| c as u8))
.trim(trim)
.from_reader(s.as_bytes());
@@ -66,30 +56,24 @@ fn from_delimited_string_to_value(
Ok(Value::List { vals: rows, span })
}
pub(super) struct DelimitedReaderConfig {
pub separator: char,
pub comment: Option<char>,
pub quote: char,
pub escape: Option<char>,
pub noheaders: bool,
pub flexible: bool,
pub no_infer: bool,
pub trim: Trim,
}
pub(super) fn from_delimited_data(
config: DelimitedReaderConfig,
pub fn from_delimited_data(
noheaders: bool,
no_infer: bool,
sep: char,
trim: Trim,
input: PipelineData,
name: Span,
) -> Result<PipelineData, ShellError> {
let (concat_string, _span, metadata) = input.collect_string_strict(name)?;
Ok(from_delimited_string_to_value(config, concat_string, name)
.map_err(|x| ShellError::DelimiterError {
msg: x.to_string(),
span: name,
})?
.into_pipeline_data_with_metadata(metadata))
Ok(
from_delimited_string_to_value(concat_string, noheaders, no_infer, sep, trim, name)
.map_err(|x| ShellError::DelimiterError {
msg: x.to_string(),
span: name,
})?
.into_pipeline_data_with_metadata(metadata),
)
}
pub fn trim_from_str(trim: Option<Value>) -> Result<Trim, ShellError> {

View File

@@ -249,12 +249,6 @@ fn convert_to_value(
"extra tokens in input file".into(),
expr.span,
)),
Expr::MatchPattern(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"Error when loading".into(),
"extra tokens in input file".into(),
expr.span,
)),
Expr::GlobPattern(val) => Ok(Value::String { val, span }),
Expr::ImportPattern(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
@@ -283,12 +277,6 @@ fn convert_to_value(
Ok(Value::List { vals: output, span })
}
Expr::MatchBlock(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"Error when loading".into(),
"match blocks not supported in nuon".into(),
expr.span,
)),
Expr::Nothing => Ok(Value::Nothing { span }),
Expr::Operator(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),

View File

@@ -85,23 +85,15 @@ fn parse_aligned_columns<'a>(
.iter()
.enumerate()
.map(|(i, (header_name, start_position))| {
let char_index_start = match l.char_indices().nth(*start_position) {
Some(idx) => idx.0,
None => *start_position,
};
let val = match headers.get(i + 1) {
Some((_, end)) => {
if *end < l.len() {
let char_index_end = match l.char_indices().nth(*end) {
Some(idx) => idx.0,
None => *end,
};
l.get(char_index_start..char_index_end)
l.get(*start_position..*end)
} else {
l.get(char_index_start..)
l.get(*start_position..)
}
}
None => l.get(char_index_start..),
None => l.get(*start_position..),
}
.unwrap_or("")
.trim()

View File

@@ -1,4 +1,4 @@
use super::delimited::{from_delimited_data, trim_from_str, DelimitedReaderConfig};
use super::delimited::{from_delimited_data, trim_from_str};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
@@ -18,34 +18,11 @@ impl Command for FromTsv {
fn signature(&self) -> Signature {
Signature::build("from tsv")
.input_output_types(vec![(Type::String, Type::Table(vec![]))])
.named(
"comment",
SyntaxShape::String,
"a comment character to ignore lines starting with it",
Some('c'),
)
.named(
"quote",
SyntaxShape::String,
"a quote character to ignore separators in strings, defaults to '\"'",
Some('q'),
)
.named(
"escape",
SyntaxShape::String,
"an escape character for strings containing the quote character",
Some('e'),
)
.switch(
"noheaders",
"don't treat the first row as column names",
Some('n'),
)
.switch(
"flexible",
"allow the number of fields in records to be variable",
None,
)
.switch("no-infer", "no field type inferencing", None)
.named(
"trim",
@@ -124,36 +101,12 @@ fn from_tsv(
) -> Result<PipelineData, ShellError> {
let name = call.head;
let comment = call
.get_flag(engine_state, stack, "comment")?
.map(|v: Value| v.as_char())
.transpose()?;
let quote = call
.get_flag(engine_state, stack, "quote")?
.map(|v: Value| v.as_char())
.transpose()?
.unwrap_or('"');
let escape = call
.get_flag(engine_state, stack, "escape")?
.map(|v: Value| v.as_char())
.transpose()?;
let no_infer = call.has_flag("no-infer");
let noheaders = call.has_flag("noheaders");
let flexible = call.has_flag("flexible");
let trim = trim_from_str(call.get_flag(engine_state, stack, "trim")?)?;
let trim: Option<Value> = call.get_flag(engine_state, stack, "trim")?;
let trim = trim_from_str(trim)?;
let config = DelimitedReaderConfig {
separator: '\t',
comment,
quote,
escape,
noheaders,
flexible,
no_infer,
trim,
};
from_delimited_data(config, input, name)
from_delimited_data(noheaders, no_infer, '\t', trim, input, name)
}
#[cfg(test)]

View File

@@ -35,10 +35,10 @@ impl Command for FromXml {
fn extra_usage(&self) -> &str {
r#"Every XML entry is represented via a record with tag, attribute and content fields.
To represent different types of entries different values are written to this fields:
1. Tag entry: `{tag: <tag name> attrs: {<attr name>: "<string value>" ...} content: [<entries>]}`
2. Comment entry: `{tag: '!' attrs: null content: "<comment string>"}`
3. Processing instruction (PI): `{tag: '?<pi name>' attrs: null content: "<pi content string>"}`
4. Text: `{tag: null attrs: null content: "<text>"}`.
1. Tag entry: {tag: <tag name> attrs: {<attr name>: "<string value>" ...} content: [<entries>]}
2. Comment entry: {tag: '!' attrs: null content: "<comment string>"}
3. Processing instruction (PI): {tag: '?<pi name>' attrs: null content: "<pi content string>"}
4. Text: {tag: null attrs: null content: "<text>"}.
Unlike to xml command all null values are always present and text is never represented via plain
string. This way content of every tag is always a table and is easier to parse"#
@@ -141,6 +141,7 @@ fn element_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Value {
let content: Vec<Value> = n
.children()
.into_iter()
.filter_map(|node| from_node_to_value(&node, info))
.collect();
let content = Value::list(content, span);
@@ -394,7 +395,7 @@ mod tests {
fn parses_empty_element() -> Result<(), roxmltree::Error> {
let source = "<nu></nu>";
assert_eq!(parse(source)?, content_tag("nu", indexmap! {}, &[]));
assert_eq!(parse(source)?, content_tag("nu", indexmap! {}, &vec![]));
Ok(())
}
@@ -408,7 +409,7 @@ mod tests {
content_tag(
"nu",
indexmap! {},
&[content_string("La era de los tres caballeros")]
&vec![content_string("La era de los tres caballeros")]
)
);
@@ -420,7 +421,7 @@ mod tests {
let source = "\
<nu>
<dev>Andrés</dev>
<dev>JT</dev>
<dev>Jonathan</dev>
<dev>Yehuda</dev>
</nu>";
@@ -430,9 +431,9 @@ mod tests {
"nu",
indexmap! {},
&vec![
content_tag("dev", indexmap! {}, &[content_string("Andrés")]),
content_tag("dev", indexmap! {}, &[content_string("JT")]),
content_tag("dev", indexmap! {}, &[content_string("Yehuda")])
content_tag("dev", indexmap! {}, &vec![content_string("Andrés")]),
content_tag("dev", indexmap! {}, &vec![content_string("Jonathan")]),
content_tag("dev", indexmap! {}, &vec![content_string("Yehuda")])
]
)
);
@@ -448,7 +449,7 @@ mod tests {
assert_eq!(
parse(source)?,
content_tag("nu", indexmap! {"version" => "2.0"}, &[])
content_tag("nu", indexmap! {"version" => "2.0"}, &vec![])
);
Ok(())
@@ -466,10 +467,10 @@ mod tests {
content_tag(
"nu",
indexmap! {"version" => "2.0"},
&[content_tag(
&vec![content_tag(
"version",
indexmap! {},
&[content_string("2.0")]
&vec![content_string("2.0")]
)]
)
);
@@ -485,7 +486,7 @@ mod tests {
assert_eq!(
parse(source)?,
content_tag("nu", indexmap! {"version" => "2.0", "age" => "25"}, &[])
content_tag("nu", indexmap! {"version" => "2.0", "age" => "25"}, &vec![])
);
Ok(())

View File

@@ -174,30 +174,6 @@ fn convert_yaml_value_to_nu_value(
Value::from(collected)
}
serde_yaml::Value::Tagged(t) => {
let tag = &t.tag;
let value = match &t.value {
serde_yaml::Value::String(s) => {
let val = format!("{} {}", tag, s).trim().to_string();
Value::String { val, span }
}
serde_yaml::Value::Number(n) => {
let val = format!("{} {}", tag, n).trim().to_string();
Value::String { val, span }
}
serde_yaml::Value::Bool(b) => {
let val = format!("{} {}", tag, b).trim().to_string();
Value::String { val, span }
}
serde_yaml::Value::Null => {
let val = format!("{}", tag).trim().to_string();
Value::String { val, span }
}
v => convert_yaml_value_to_nu_value(v, span, val_span)?,
};
value
}
serde_yaml::Value::Null => Value::nothing(span),
x => unimplemented!("Unsupported YAML case: {:?}", x),
})
@@ -338,63 +314,4 @@ mod test {
test_examples(FromYaml {})
}
#[test]
fn test_convert_yaml_value_to_nu_value_for_tagged_values() {
struct TestCase {
input: &'static str,
expected: Result<Value, ShellError>,
}
let test_cases: Vec<TestCase> = vec![
TestCase {
input: "Key: !Value ${TEST}-Test-role",
expected: Ok(Value::Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value ${TEST}-Test-role")],
span: Span::test_data(),
}),
},
TestCase {
input: "Key: !Value test-${TEST}",
expected: Ok(Value::Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value test-${TEST}")],
span: Span::test_data(),
}),
},
TestCase {
input: "Key: !Value",
expected: Ok(Value::Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value")],
span: Span::test_data(),
}),
},
TestCase {
input: "Key: !True",
expected: Ok(Value::Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!True")],
span: Span::test_data(),
}),
},
TestCase {
input: "Key: !123",
expected: Ok(Value::Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!123")],
span: Span::test_data(),
}),
},
];
for test_case in test_cases {
let doc = serde_yaml::Deserializer::from_str(test_case.input);
let v: serde_yaml::Value = serde_yaml::Value::deserialize(doc.last().unwrap()).unwrap();
let result = convert_yaml_value_to_nu_value(&v, Span::test_data(), Span::test_data());
assert!(result.is_ok());
assert!(result.ok().unwrap() == test_case.expected.ok().unwrap());
}
}
}

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