Compare commits

...

10 Commits

Author SHA1 Message Date
139342ac4a add rendered and json error messages in try/catch (#14082)
# Description

This PR adds a couple more options for dealing with try/catch errors. It
adds a `json` version of the error and a `rendered` version of the
error. It also respects the error_style configuration point.

![image](https://github.com/user-attachments/assets/32574f07-f511-40c0-8b57-de5f6f13a9c4)


# 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` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **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.
-->
2024-10-19 17:24:09 +02:00
63583d6072 allow group-by and split-by to work with other values (#14086)
# Description

This PR updates `group-by` and `split-by` to allow other nushell Values
to be used, namely bools.

### Before
```nushell
❯ [false, false, true, false, true, false] | group-by | table -e
Error: nu:🐚:cant_convert

  × Can't convert to string.
   ╭─[entry #1:1:2]
 1 │ [false, false, true, false, true, false] | group-by | table -e
   ·  ──┬──
   ·    ╰── can't convert bool to string
   ╰────
```
### After
```nushell
❯ [false, false, true, false, true, false] | group-by | table -e
╭───────┬───────────────╮
│       │ ╭───┬───────╮ │
│ false │ │ 0 │ false │ │
│       │ │ 1 │ false │ │
│       │ │ 2 │ false │ │
│       │ │ 3 │ false │ │
│       │ ╰───┴───────╯ │
│       │ ╭───┬──────╮  │
│ true  │ │ 0 │ true │  │
│       │ │ 1 │ true │  │
│       │ ╰───┴──────╯  │
╰───────┴───────────────╯
```

# 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` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **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.
-->
2024-10-19 17:24:09 +02:00
f172e42869 Reduce duplicate dependencies on the windows crate (#14105)
Nushell currently depends on three different versions of the `windows`
crate: `0.44.0`, `0.52.0`, and `0.54.0`. This PR bumps several
dependencies so that the `nu` binary only depends on `0.56.0`.

On my machine, this PR makes `cargo build` about 10% faster.

The polars plugin still uses its own version of the `windows` crate
though, which is not ideal. We'll need to bump the `polars` crate to fix
that, but it breaks a lot of our code. (`polars 1.0` release anyone?)
2024-10-19 17:24:09 +02:00
c3a6aca464 Update to rust 1.80.1 (#14106)
This can be merged on 10/17 once 1.82.0 is out.

---------

Co-authored-by: Wind <WindSoilder@outlook.com>
2024-10-19 17:24:09 +02:00
f5121a5c78 add like and not-like operators as synonyms for the regex operators =~ and !~ (#14072)
# Description

This PR adds `like` as a synonym for `=~` and `not-like` as a synonym
for `!~`. This is mainly a quality-of-life change to help those people
who think in sql.


![image](https://github.com/user-attachments/assets/a0b142cd-30c9-487d-b755-d6da0a0874ec)

closes #13261

# 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` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **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.
-->
2024-10-19 17:24:09 +02:00
e7f2aaf721 Add count to uniq search terms (#14108)
Adds "count" to uniq's search terms, to facilitate discovery of the
`--count` option
2024-10-19 17:24:09 +02:00
36909a2b73 use command: Don't create a variable with empty record if it doesn't define any constants (#14051)
# Description
Fixes: #13967

The key changes lays in `nu-protocol/src/module.rs`, when resolving
import pattern, nushell only needs to bring `$module` with a record
value if it defines any constants.

# User-Facing Changes
```nushell
module spam {}
use spam
```
Will no longer create a `$spam` variable with an empty record.

# Tests + Formatting
Adjusted some tests and added some tests.
2024-10-19 17:24:09 +02:00
d941e7324f run ensure_flag_arg_type for short flag values (#14074)
Closes #13654

# User-Facing Changes

- Short flags are now fully type-checked,
  including null and record signatures for literal arguments:

```nushell
def test [-v: record<l: int>] {};
test -v null # error
test -v {l: ""} # error

def test2 [-v: int] {};
let v = ""
test2 -v $v # error
```

- `polars unpivot` `--index`/`--on` and `into value --columns`
now accept `list` values
2024-10-19 17:24:09 +02:00
8756fedb3b Make plugin list read state from plugin registry file as well (#14085)
# Description

[Context on
Discord](https://discord.com/channels/601130461678272522/855947301380947968/1292279795035668583)

**This is a breaking change, due to the removal of `is_running`.**

Some users find the `plugin list` command confusing, because it doesn't
show anything different after running `plugin add` or `plugin rm`. This
modifies the `plugin list` command to also look at the plugin registry
file to give some idea of how the plugins in engine state differ from
those in the plugin registry file.

The following values of `status` are now produced instead of
`is_running`:

- `added`: The plugin is present in the plugin registry file, but not in
the engine.
- `loaded`: The plugin is present both in the plugin registry file and
in the engine, but is not running.
- `running`: The plugin is currently running, and the `pid` column
should contain its process ID.
- `modified`: The plugin state present in the plugin registry file is
different from the state in the engine.
- `removed`: The plugin is still loaded in the engine, but is not
present in the plugin registry file.
- `invalid`: The data in the plugin registry file couldn't be
deserialized, and the plugin most likely needs to be added again.

Example (`commands` omitted):

```
╭──────┬─────────────────────┬────────────┬───────────┬──────────┬─────────────────────────────────────────────────────┬─────────╮
│    # │        name         │  version   │  status   │   pid    │                      filename                       │  shell  │
├──────┼─────────────────────┼────────────┼───────────┼──────────┼─────────────────────────────────────────────────────┼─────────┤
│    0 │ custom_values       │ 0.1.0      │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_custom_values      │         │
│    1 │ dbus                │ 0.11.0     │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_dbus               │         │
│    2 │ example             │ 0.98.1     │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_example            │         │
│    3 │ explore_ir          │ 0.3.0      │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_explore_ir         │         │
│    4 │ formats             │ 0.98.1     │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_formats            │         │
│    5 │ gstat               │ 0.98.1     │ running   │   236662 │ /home/devyn/.cargo/bin/nu_plugin_gstat              │         │
│    6 │ inc                 │ 0.98.1     │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_inc                │         │
│    7 │ polars              │ 0.98.1     │ added     │          │ /home/devyn/.cargo/bin/nu_plugin_polars             │         │
│    8 │ query               │ 0.98.1     │ removed   │          │ /home/devyn/.cargo/bin/nu_plugin_query              │         │
│    9 │ stress_internals    │ 0.98.1     │ loaded    │          │ /home/devyn/.cargo/bin/nu_plugin_stress_internals   │         │
╰──────┴─────────────────────┴────────────┴───────────┴──────────┴─────────────────────────────────────────────────────┴─────────╯

```

# User-Facing Changes

To `plugin list`:

* **Breaking:** The `is_running` column is removed and replaced with
`status`. Use `status == running` to filter equivalently.
* The `--plugin-config` from other plugin management commands is now
supported.
* Added an `--engine` flag which behaves more or less like before, and
doesn't load the plugin registry file at all.
* Added a `--registry` flag which only checks the plugin registry file.
All plugins appear as `added` since there is no state to compare with.

Because the default is to check both, the `plugin list` command might be
a little bit slower. If you don't need to check the plugin registry
file, the `--engine` flag does not load the plugin registry file at all,
so it should be just as fast as before.

# Tests + Formatting

Added tests for `added` and `removed` statuses. `modified` and `invalid`
are a bit more tricky so I didn't try.

# After Submitting

- [ ] update documentation that references the `plugin list` command
- [ ] release notes
2024-10-19 17:24:09 +02:00
dba2f6e0f8 Implemented polars unnest (#14104)
# Description
Provides the ability to decomes struct columns into seperate columns for
each field:
<img width="655" alt="Screenshot 2024-10-16 at 09 57 22"
src="https://github.com/user-attachments/assets/6706bd36-8d38-4365-b58d-ba82f2d5ba9a">

# User-Facing Changes
- provides a new command `polars unnest` for decomposing struct fields
into separate columns.
2024-10-19 17:24:09 +02:00
33 changed files with 791 additions and 296 deletions

166
Cargo.lock generated
View File

@ -2611,15 +2611,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.12.1" version = "0.12.1"
@ -3025,7 +3016,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"reedline", "reedline",
"rstest", "rstest",
"sysinfo", "sysinfo 0.32.0",
"tempfile", "tempfile",
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",
@ -3187,7 +3178,7 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"serde_yaml", "serde_yaml",
"sha2", "sha2",
"sysinfo", "sysinfo 0.32.0",
"tabled", "tabled",
"tempfile", "tempfile",
"terminal_size", "terminal_size",
@ -3210,7 +3201,7 @@ dependencies = [
"v_htmlescape", "v_htmlescape",
"wax", "wax",
"which", "which",
"windows 0.54.0", "windows 0.56.0",
"winreg", "winreg",
] ]
@ -3356,7 +3347,7 @@ dependencies = [
"rmp-serde", "rmp-serde",
"serde", "serde",
"serde_json", "serde_json",
"windows 0.54.0", "windows 0.56.0",
] ]
[[package]] [[package]]
@ -3372,7 +3363,7 @@ dependencies = [
"nu-utils", "nu-utils",
"serde", "serde",
"typetag", "typetag",
"windows 0.54.0", "windows 0.56.0",
] ]
[[package]] [[package]]
@ -3477,8 +3468,8 @@ dependencies = [
"ntapi", "ntapi",
"once_cell", "once_cell",
"procfs", "procfs",
"sysinfo", "sysinfo 0.32.0",
"windows 0.54.0", "windows 0.56.0",
] ]
[[package]] [[package]]
@ -3761,15 +3752,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]] [[package]]
name = "objc-sys" name = "objc-sys"
version = "0.3.5" version = "0.3.5"
@ -4729,7 +4711,7 @@ dependencies = [
"rayon", "rayon",
"smartstring", "smartstring",
"stacker", "stacker",
"sysinfo", "sysinfo 0.30.13",
"version_check", "version_check",
] ]
@ -6115,10 +6097,23 @@ dependencies = [
"libc", "libc",
"ntapi", "ntapi",
"once_cell", "once_cell",
"rayon",
"windows 0.52.0", "windows 0.52.0",
] ]
[[package]]
name = "sysinfo"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791"
dependencies = [
"core-foundation-sys",
"libc",
"memchr",
"ntapi",
"rayon",
"windows 0.56.0",
]
[[package]] [[package]]
name = "tabled" name = "tabled"
version = "0.16.0" version = "0.16.0"
@ -6442,18 +6437,19 @@ dependencies = [
[[package]] [[package]]
name = "trash" name = "trash"
version = "3.3.1" version = "5.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c658458d46d9d5a153a3b5cdd88d8579ad50d4fb85d53961e4526c8fc7c55a57" checksum = "33caf2a9be1812a263a4bfce74d2de225fcde12ee7b77001361abd2b34ffdcc4"
dependencies = [ dependencies = [
"chrono", "chrono",
"libc", "libc",
"log", "log",
"objc", "objc2",
"objc2-foundation",
"once_cell", "once_cell",
"scopeguard", "scopeguard",
"url", "urlencoding",
"windows 0.44.0", "windows 0.56.0",
] ]
[[package]] [[package]]
@ -6642,6 +6638,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "utf-8" name = "utf-8"
version = "0.7.6" version = "0.7.6"
@ -7083,15 +7085,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.52.0" version = "0.52.0"
@ -7104,11 +7097,11 @@ dependencies = [
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.54.0" version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [ dependencies = [
"windows-core 0.54.0", "windows-core 0.56.0",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@ -7123,14 +7116,38 @@ dependencies = [
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.54.0" version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
dependencies = [ dependencies = [
"windows-implement",
"windows-interface",
"windows-result", "windows-result",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-implement"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
]
[[package]]
name = "windows-interface"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
]
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.1.2" version = "0.1.2"
@ -7180,21 +7197,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"
@ -7226,12 +7228,6 @@ dependencies = [
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.48.5"
@ -7250,12 +7246,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@ -7274,12 +7264,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@ -7304,12 +7288,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@ -7328,12 +7306,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@ -7346,12 +7318,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
@ -7370,12 +7336,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"

View File

@ -10,7 +10,7 @@ homepage = "https://www.nushell.sh"
license = "MIT" license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.79.0" rust-version = "1.80.1"
version = "0.99.1" version = "0.99.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -153,13 +153,13 @@ serde_yaml = "0.9"
sha2 = "0.10" sha2 = "0.10"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
syn = "2.0" syn = "2.0"
sysinfo = "0.30" sysinfo = "0.32"
tabled = { version = "0.16.0", default-features = false } tabled = { version = "0.16.0", default-features = false }
tempfile = "3.13" tempfile = "3.13"
terminal_size = "0.3" terminal_size = "0.3"
titlecase = "2.0" titlecase = "2.0"
toml = "0.8" toml = "0.8"
trash = "3.3" trash = "5.1"
umask = "2.1" umask = "2.1"
unicode-segmentation = "1.12" unicode-segmentation = "1.12"
unicode-width = "0.1" unicode-width = "0.1"
@ -176,7 +176,7 @@ uuid = "1.10.0"
v_htmlescape = "0.15.0" v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
which = "6.0.0" which = "6.0.0"
windows = "0.54" windows = "0.56"
windows-sys = "0.48" windows-sys = "0.48"
winreg = "0.52" winreg = "0.52"
@ -320,4 +320,4 @@ bench = false
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse` # Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
[[bench]] [[bench]]
name = "benchmarks" name = "benchmarks"
harness = false harness = false

View File

@ -67,7 +67,9 @@ impl Completer for OperatorCompletion {
], ],
Expr::String(_) => vec![ Expr::String(_) => vec![
("=~", "Contains regex match"), ("=~", "Contains regex match"),
("like", "Contains regex match"),
("!~", "Does not contain regex match"), ("!~", "Does not contain regex match"),
("not-like", "Does not contain regex match"),
( (
"++", "++",
"Appends two lists, a list and a value, two strings, or two binary values", "Appends two lists, a list and a value, two strings, or two binary values",

View File

@ -107,7 +107,11 @@ fn run_catch(
if let Some(catch) = catch { if let Some(catch) = catch {
stack.set_last_error(&error); stack.set_last_error(&error);
let error = error.into_value(span); let fancy_errors = match engine_state.get_config().error_style {
nu_protocol::ErrorStyle::Fancy => true,
nu_protocol::ErrorStyle::Plain => false,
};
let error = error.into_value(span, fancy_errors);
let block = engine_state.get_block(catch.block_id); let block = engine_state.get_block(catch.block_id);
// Put the error value in the positional closure var // Put the error value in the positional closure var
if let Some(var) = block.signature.get_positional(0) { if let Some(var) = block.signature.get_positional(0) {

View File

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

View File

@ -1,5 +1,8 @@
use itertools::Itertools; use itertools::{EitherOrBoth, Itertools};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{IntoValue, PluginRegistryItemData};
use crate::util::read_plugin_file;
#[derive(Clone)] #[derive(Clone)]
pub struct PluginList; pub struct PluginList;
@ -17,7 +20,7 @@ impl Command for PluginList {
[ [
("name".into(), Type::String), ("name".into(), Type::String),
("version".into(), Type::String), ("version".into(), Type::String),
("is_running".into(), Type::Bool), ("status".into(), Type::String),
("pid".into(), Type::Int), ("pid".into(), Type::Int),
("filename".into(), Type::String), ("filename".into(), Type::String),
("shell".into(), Type::String), ("shell".into(), Type::String),
@ -26,11 +29,54 @@ impl Command for PluginList {
.into(), .into(),
), ),
) )
.named(
"plugin-config",
SyntaxShape::Filepath,
"Use a plugin registry file other than the one set in `$nu.plugin-path`",
None,
)
.switch(
"engine",
"Show info for plugins that are loaded into the engine only.",
Some('e'),
)
.switch(
"registry",
"Show info for plugins from the registry file only.",
Some('r'),
)
.category(Category::Plugin) .category(Category::Plugin)
} }
fn description(&self) -> &str { fn description(&self) -> &str {
"List installed plugins." "List loaded and installed plugins."
}
fn extra_description(&self) -> &str {
r#"
The `status` column will contain one of the following values:
- `added`: The plugin is present in the plugin registry file, but not in
the engine.
- `loaded`: The plugin is present both in the plugin registry file and in
the engine, but is not running.
- `running`: The plugin is currently running, and the `pid` column should
contain its process ID.
- `modified`: The plugin state present in the plugin registry file is different
from the state in the engine.
- `removed`: The plugin is still loaded in the engine, but is not present in
the plugin registry file.
- `invalid`: The data in the plugin registry file couldn't be deserialized,
and the plugin most likely needs to be added again.
`running` takes priority over any other status. Unless `--registry` is used
or the plugin has not been loaded yet, the values of `version`, `filename`,
`shell`, and `commands` reflect the values in the engine and not the ones in
the plugin registry file.
See also: `plugin use`
"#
.trim()
} }
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {
@ -45,7 +91,7 @@ impl Command for PluginList {
result: Some(Value::test_list(vec![Value::test_record(record! { result: Some(Value::test_list(vec![Value::test_record(record! {
"name" => Value::test_string("inc"), "name" => Value::test_string("inc"),
"version" => Value::test_string(env!("CARGO_PKG_VERSION")), "version" => Value::test_string(env!("CARGO_PKG_VERSION")),
"is_running" => Value::test_bool(true), "status" => Value::test_string("running"),
"pid" => Value::test_int(106480), "pid" => Value::test_int(106480),
"filename" => if cfg!(windows) { "filename" => if cfg!(windows) {
Value::test_string(r"C:\nu\plugins\nu_plugin_inc.exe") Value::test_string(r"C:\nu\plugins\nu_plugin_inc.exe")
@ -67,58 +113,189 @@ impl Command for PluginList {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
let engine_mode = call.has_flag(engine_state, stack, "engine")?;
let registry_mode = call.has_flag(engine_state, stack, "registry")?;
// Group plugin decls by plugin identity let plugins_info = match (engine_mode, registry_mode) {
let decls = engine_state.plugin_decls().into_group_map_by(|decl| { // --engine and --registry together is equivalent to the default.
decl.plugin_identity() (false, false) | (true, true) => {
.expect("plugin decl should have identity") if engine_state.plugin_path.is_some() || custom_path.is_some() {
}); let plugins_in_engine = get_plugins_in_engine(engine_state);
let plugins_in_registry =
get_plugins_in_registry(engine_state, stack, call.head, &custom_path)?;
merge_plugin_info(plugins_in_engine, plugins_in_registry)
} else {
// Don't produce error when running nu --no-config-file
get_plugins_in_engine(engine_state)
}
}
(true, false) => get_plugins_in_engine(engine_state),
(false, true) => get_plugins_in_registry(engine_state, stack, call.head, &custom_path)?,
};
// Build plugins list Ok(plugins_info.into_value(call.head).into_pipeline_data())
let list = engine_state.plugins().iter().map(|plugin| { }
// Find commands that belong to the plugin }
let commands = decls.get(plugin.identity())
.into_iter() #[derive(Debug, Clone, IntoValue, PartialOrd, Ord, PartialEq, Eq)]
.flat_map(|decls| { struct PluginInfo {
decls.iter().map(|decl| Value::string(decl.name(), head)) name: String,
}) version: Option<String>,
.collect(); status: PluginStatus,
pid: Option<u32>,
let pid = plugin filename: String,
.pid() shell: Option<String>,
.map(|p| Value::int(p as i64, head)) commands: Vec<String>,
.unwrap_or(Value::nothing(head)); }
let shell = plugin #[derive(Debug, Clone, Copy, IntoValue, PartialOrd, Ord, PartialEq, Eq)]
.identity() #[nu_value(rename_all = "snake_case")]
.shell() enum PluginStatus {
.map(|s| Value::string(s.to_string_lossy(), head)) Added,
.unwrap_or(Value::nothing(head)); Loaded,
Running,
let metadata = plugin.metadata(); Modified,
let version = metadata Removed,
.and_then(|m| m.version) Invalid,
.map(|s| Value::string(s, head)) }
.unwrap_or(Value::nothing(head));
fn get_plugins_in_engine(engine_state: &EngineState) -> Vec<PluginInfo> {
let record = record! { // Group plugin decls by plugin identity
"name" => Value::string(plugin.identity().name(), head), let decls = engine_state.plugin_decls().into_group_map_by(|decl| {
"version" => version, decl.plugin_identity()
"is_running" => Value::bool(plugin.is_running(), head), .expect("plugin decl should have identity")
"pid" => pid, });
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
"shell" => shell, // Build plugins list
"commands" => Value::list(commands, head), engine_state
}; .plugins()
.iter()
Value::record(record, head) .map(|plugin| {
}).collect(); // Find commands that belong to the plugin
let commands = decls
Ok(Value::list(list, head).into_pipeline_data()) .get(plugin.identity())
.into_iter()
.flat_map(|decls| decls.iter().map(|decl| decl.name().to_owned()))
.sorted()
.collect();
PluginInfo {
name: plugin.identity().name().into(),
version: plugin.metadata().and_then(|m| m.version),
status: if plugin.pid().is_some() {
PluginStatus::Running
} else {
PluginStatus::Loaded
},
pid: plugin.pid(),
filename: plugin.identity().filename().to_string_lossy().into_owned(),
shell: plugin
.identity()
.shell()
.map(|path| path.to_string_lossy().into_owned()),
commands,
}
})
.sorted()
.collect()
}
fn get_plugins_in_registry(
engine_state: &EngineState,
stack: &mut Stack,
span: Span,
custom_path: &Option<Spanned<String>>,
) -> Result<Vec<PluginInfo>, ShellError> {
let plugin_file_contents = read_plugin_file(engine_state, stack, span, custom_path)?;
let plugins_info = plugin_file_contents
.plugins
.into_iter()
.map(|plugin| {
let mut info = PluginInfo {
name: plugin.name,
version: None,
status: PluginStatus::Added,
pid: None,
filename: plugin.filename.to_string_lossy().into_owned(),
shell: plugin.shell.map(|path| path.to_string_lossy().into_owned()),
commands: vec![],
};
if let PluginRegistryItemData::Valid { metadata, commands } = plugin.data {
info.version = metadata.version;
info.commands = commands
.into_iter()
.map(|command| command.sig.name)
.sorted()
.collect();
} else {
info.status = PluginStatus::Invalid;
}
info
})
.sorted()
.collect();
Ok(plugins_info)
}
/// If no options are provided, the command loads from both the plugin list in the engine and what's
/// in the registry file. We need to reconcile the two to set the proper states and make sure that
/// new plugins that were added to the plugin registry file show up.
fn merge_plugin_info(
from_engine: Vec<PluginInfo>,
from_registry: Vec<PluginInfo>,
) -> Vec<PluginInfo> {
from_engine
.into_iter()
.merge_join_by(from_registry, |info_a, info_b| {
info_a.name.cmp(&info_b.name)
})
.map(|either_or_both| match either_or_both {
// Exists in the engine, but not in the registry file
EitherOrBoth::Left(info) => PluginInfo {
status: match info.status {
PluginStatus::Running => info.status,
// The plugin is not in the registry file, so it should be marked as `removed`
_ => PluginStatus::Removed,
},
..info
},
// Exists in the registry file, but not in the engine
EitherOrBoth::Right(info) => info,
// Exists in both
EitherOrBoth::Both(info_engine, info_registry) => PluginInfo {
status: match (info_engine.status, info_registry.status) {
// Above all, `running` should be displayed if the plugin is running
(PluginStatus::Running, _) => PluginStatus::Running,
// `invalid` takes precedence over other states because the user probably wants
// to fix it
(_, PluginStatus::Invalid) => PluginStatus::Invalid,
// Display `modified` if the state in the registry is different somehow
_ if info_engine.is_modified(&info_registry) => PluginStatus::Modified,
// Otherwise, `loaded` (it's not running)
_ => PluginStatus::Loaded,
},
..info_engine
},
})
.sorted()
.collect()
}
impl PluginInfo {
/// True if the plugin info shows some kind of change (other than status/pid) relative to the
/// other
fn is_modified(&self, other: &PluginInfo) -> bool {
self.name != other.name
|| self.filename != other.filename
|| self.shell != other.shell
|| self.commands != other.commands
} }
} }

View File

@ -87,7 +87,7 @@ fixed with `plugin add`.
let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item); let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item);
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| { modify_plugin_file(engine_state, stack, call.head, &custom_path, |contents| {
if let Some(index) = contents if let Some(index) = contents
.plugins .plugins
.iter() .iter()

View File

@ -6,18 +6,17 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
pub(crate) fn modify_plugin_file( fn get_plugin_registry_file_path(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
span: Span, span: Span,
custom_path: Option<Spanned<String>>, custom_path: &Option<Spanned<String>>,
operate: impl FnOnce(&mut PluginRegistryFile) -> Result<(), ShellError>, ) -> Result<PathBuf, ShellError> {
) -> Result<(), ShellError> {
#[allow(deprecated)] #[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?; let cwd = current_dir(engine_state, stack)?;
let plugin_registry_file_path = if let Some(ref custom_path) = custom_path { if let Some(ref custom_path) = custom_path {
nu_path::expand_path_with(&custom_path.item, cwd, true) Ok(nu_path::expand_path_with(&custom_path.item, cwd, true))
} else { } else {
engine_state engine_state
.plugin_path .plugin_path
@ -28,8 +27,53 @@ pub(crate) fn modify_plugin_file(
span: Some(span), span: Some(span),
help: Some("you may be running `nu` with --no-config-file".into()), help: Some("you may be running `nu` with --no-config-file".into()),
inner: vec![], inner: vec![],
})? })
}; }
}
pub(crate) fn read_plugin_file(
engine_state: &EngineState,
stack: &mut Stack,
span: Span,
custom_path: &Option<Spanned<String>>,
) -> Result<PluginRegistryFile, ShellError> {
let plugin_registry_file_path =
get_plugin_registry_file_path(engine_state, stack, span, custom_path)?;
let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span);
// Try to read the plugin file if it exists
if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
PluginRegistryFile::read_from(
File::open(&plugin_registry_file_path).map_err(|err| ShellError::IOErrorSpanned {
msg: format!(
"failed to read `{}`: {}",
plugin_registry_file_path.display(),
err
),
span: file_span,
})?,
Some(file_span),
)
} else if let Some(path) = custom_path {
Err(ShellError::FileNotFound {
file: path.item.clone(),
span: path.span,
})
} else {
Ok(PluginRegistryFile::default())
}
}
pub(crate) fn modify_plugin_file(
engine_state: &EngineState,
stack: &mut Stack,
span: Span,
custom_path: &Option<Spanned<String>>,
operate: impl FnOnce(&mut PluginRegistryFile) -> Result<(), ShellError>,
) -> Result<(), ShellError> {
let plugin_registry_file_path =
get_plugin_registry_file_path(engine_state, stack, span, custom_path)?;
let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span); let file_span = custom_path.as_ref().map(|p| p.span).unwrap_or(span);

View File

@ -18,7 +18,7 @@ impl Command for IntoValue {
.input_output_types(vec![(Type::table(), Type::table())]) .input_output_types(vec![(Type::table(), Type::table())])
.named( .named(
"columns", "columns",
SyntaxShape::Table(vec![]), SyntaxShape::List(Box::new(SyntaxShape::Any)),
"list of columns to update", "list of columns to update",
Some('c'), Some('c'),
) )

View File

@ -101,7 +101,7 @@ fn all_columns(span: Span) -> Value {
let environment = { let environment = {
let mut env_rec = Record::new(); let mut env_rec = Record::new();
for val in p.environ() { for val in p.environ() {
if let Some((key, value)) = val.split_once('=') { if let Some((key, value)) = val.to_string_lossy().split_once('=') {
let is_env_var_a_list = { let is_env_var_a_list = {
{ {
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
@ -146,8 +146,8 @@ fn all_columns(span: Span) -> Value {
"root" => root, "root" => root,
"cwd" => cwd, "cwd" => cwd,
"exe_path" => exe_path, "exe_path" => exe_path,
"command" => Value::string(p.cmd().join(" "), span), "command" => Value::string(p.cmd().join(std::ffi::OsStr::new(" ")).to_string_lossy(), span),
"name" => Value::string(p.name(), span), "name" => Value::string(p.name().to_string_lossy(), span),
"environment" => environment, "environment" => environment,
}, },
span, span,

View File

@ -38,6 +38,14 @@ impl Command for GroupBy {
"Splits a list or table into groups, and returns a record containing those groups." "Splits a list or table into groups, and returns a record containing those groups."
} }
fn extra_description(&self) -> &str {
r#"the group-by command makes some assumptions:
- if the input data is not a string, the grouper will convert the key to string but the values will remain in their original format. e.g. with bools, "true" and true would be in the same group (see example).
- datetime is formatted based on your configuration setting. use `format date` to change the format.
- filesize is formatted based on your configuration setting. use `format filesize` to change the format.
- some nushell values are not supported, such as closures."#
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -114,6 +122,20 @@ impl Command for GroupBy {
}), }),
])), ])),
}, },
Example {
description: "Group bools, whether they are strings or actual bools",
example: r#"[true "true" false "false"] | group-by"#,
result: Some(Value::test_record(record! {
"true" => Value::test_list(vec![
Value::test_bool(true),
Value::test_string("true"),
]),
"false" => Value::test_list(vec![
Value::test_bool(false),
Value::test_string("false"),
]),
})),
}
] ]
} }
} }
@ -127,6 +149,7 @@ pub fn group_by(
let head = call.head; let head = call.head;
let grouper: Option<Value> = call.opt(engine_state, stack, 0)?; let grouper: Option<Value> = call.opt(engine_state, stack, 0)?;
let to_table = call.has_flag(engine_state, stack, "to-table")?; let to_table = call.has_flag(engine_state, stack, "to-table")?;
let config = engine_state.get_config();
let values: Vec<Value> = input.into_iter().collect(); let values: Vec<Value> = input.into_iter().collect();
if values.is_empty() { if values.is_empty() {
@ -137,7 +160,7 @@ pub fn group_by(
Some(grouper) => { Some(grouper) => {
let span = grouper.span(); let span = grouper.span();
match grouper { match grouper {
Value::CellPath { val, .. } => group_cell_path(val, values)?, Value::CellPath { val, .. } => group_cell_path(val, values, config)?,
Value::Closure { val, .. } => { Value::Closure { val, .. } => {
group_closure(values, span, *val, engine_state, stack)? group_closure(values, span, *val, engine_state, stack)?
} }
@ -149,7 +172,7 @@ pub fn group_by(
} }
} }
} }
None => group_no_grouper(values)?, None => group_no_grouper(values, config)?,
}; };
let value = if to_table { let value = if to_table {
@ -164,6 +187,7 @@ pub fn group_by(
fn group_cell_path( fn group_cell_path(
column_name: CellPath, column_name: CellPath,
values: Vec<Value>, values: Vec<Value>,
config: &nu_protocol::Config,
) -> Result<IndexMap<String, Vec<Value>>, ShellError> { ) -> Result<IndexMap<String, Vec<Value>>, ShellError> {
let mut groups = IndexMap::<_, Vec<_>>::new(); let mut groups = IndexMap::<_, Vec<_>>::new();
@ -176,18 +200,21 @@ fn group_cell_path(
continue; // likely the result of a failed optional access, ignore this value continue; // likely the result of a failed optional access, ignore this value
} }
let key = key.coerce_string()?; let key = key.to_abbreviated_string(config);
groups.entry(key).or_default().push(value); groups.entry(key).or_default().push(value);
} }
Ok(groups) Ok(groups)
} }
fn group_no_grouper(values: Vec<Value>) -> Result<IndexMap<String, Vec<Value>>, ShellError> { fn group_no_grouper(
values: Vec<Value>,
config: &nu_protocol::Config,
) -> Result<IndexMap<String, Vec<Value>>, ShellError> {
let mut groups = IndexMap::<_, Vec<_>>::new(); let mut groups = IndexMap::<_, Vec<_>>::new();
for value in values.into_iter() { for value in values.into_iter() {
let key = value.coerce_string()?; let key = value.to_abbreviated_string(config);
groups.entry(key).or_default().push(value); groups.entry(key).or_default().push(value);
} }
@ -203,12 +230,13 @@ fn group_closure(
) -> Result<IndexMap<String, Vec<Value>>, ShellError> { ) -> Result<IndexMap<String, Vec<Value>>, ShellError> {
let mut groups = IndexMap::<_, Vec<_>>::new(); let mut groups = IndexMap::<_, Vec<_>>::new();
let mut closure = ClosureEval::new(engine_state, stack, closure); let mut closure = ClosureEval::new(engine_state, stack, closure);
let config = engine_state.get_config();
for value in values { for value in values {
let key = closure let key = closure
.run_with_value(value.clone())? .run_with_value(value.clone())?
.into_value(span)? .into_value(span)?
.coerce_into_string()?; .to_abbreviated_string(config);
groups.entry(key).or_default().push(value); groups.entry(key).or_default().push(value);
} }

View File

@ -84,16 +84,16 @@ pub fn split_by(
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name = call.head; let name = call.head;
let config = engine_state.get_config();
let splitter: Option<Value> = call.opt(engine_state, stack, 0)?; let splitter: Option<Value> = call.opt(engine_state, stack, 0)?;
match splitter { match splitter {
Some(v) => { Some(v) => {
let splitter = Some(Spanned { let splitter = Some(Spanned {
item: v.coerce_into_string()?, item: v.to_abbreviated_string(config),
span: name, span: name,
}); });
Ok(split(splitter.as_ref(), input, name)?) Ok(split(splitter.as_ref(), input, name, config)?)
} }
// This uses the same format as the 'requires a column name' error in sort_utils.rs // This uses the same format as the 'requires a column name' error in sort_utils.rs
None => Err(ShellError::GenericError { None => Err(ShellError::GenericError {
@ -110,6 +110,7 @@ pub fn split(
column_name: Option<&Spanned<String>>, column_name: Option<&Spanned<String>>,
values: PipelineData, values: PipelineData,
span: Span, span: Span,
config: &nu_protocol::Config,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let grouper = if let Some(column_name) = column_name { let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone())) Grouper::ByColumn(Some(column_name.clone()))
@ -127,7 +128,7 @@ pub fn split(
}; };
match group_key { match group_key {
Some(group_key) => Ok(group_key.coerce_string()?), Some(group_key) => Ok(group_key.to_abbreviated_string(config)),
None => Err(ShellError::CantFindColumn { None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(), col_name: column_name.item.to_string(),
span: Some(column_name.span), span: Some(column_name.span),
@ -136,12 +137,12 @@ pub fn split(
} }
}; };
data_split(values, Some(&block), span) data_split(values, Some(&block), span, config)
} }
Grouper::ByColumn(None) => { Grouper::ByColumn(None) => {
let block = move |_, row: &Value| row.coerce_string(); let block = move |_, row: &Value| Ok(row.to_abbreviated_string(config));
data_split(values, Some(&block), span) data_split(values, Some(&block), span, config)
} }
} }
} }
@ -151,6 +152,7 @@ fn data_group(
values: &Value, values: &Value,
grouper: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>, grouper: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>,
span: Span, span: Span,
config: &nu_protocol::Config,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new(); let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
@ -158,7 +160,7 @@ fn data_group(
let group_key = if let Some(ref grouper) = grouper { let group_key = if let Some(ref grouper) = grouper {
grouper(idx, &value) grouper(idx, &value)
} else { } else {
value.coerce_string() Ok(value.to_abbreviated_string(config))
}; };
let group = groups.entry(group_key?).or_default(); let group = groups.entry(group_key?).or_default();
@ -179,6 +181,7 @@ pub fn data_split(
value: PipelineData, value: PipelineData,
splitter: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>, splitter: Option<&dyn Fn(usize, &Value) -> Result<String, ShellError>>,
dst_span: Span, dst_span: Span,
config: &nu_protocol::Config,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let mut splits = indexmap::IndexMap::new(); let mut splits = indexmap::IndexMap::new();
@ -188,7 +191,7 @@ pub fn data_split(
match v { match v {
Value::Record { val: grouped, .. } => { Value::Record { val: grouped, .. } => {
for (outer_key, list) in grouped.into_owned() { for (outer_key, list) in grouped.into_owned() {
match data_group(&list, splitter, span) { match data_group(&list, splitter, span, config) {
Ok(grouped_vals) => { Ok(grouped_vals) => {
if let Value::Record { val: sub, .. } = grouped_vals { if let Value::Record { val: sub, .. } = grouped_vals {
for (inner_key, subset) in sub.into_owned() { for (inner_key, subset) in sub.into_owned() {

View File

@ -46,7 +46,7 @@ impl Command for Uniq {
} }
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {
vec!["distinct", "deduplicate"] vec!["distinct", "deduplicate", "count"]
} }
fn run( fn run(

View File

@ -23,37 +23,33 @@ fn groups() {
#[test] #[test]
fn errors_if_given_unknown_column_name() { fn errors_if_given_unknown_column_name() {
let sample = r#" let sample = r#"{
{ "nu": {
"nu": { "committers": [
"committers": [ {"name": "Andrés N. Robalino"},
{"name": "Andrés N. Robalino"}, {"name": "JT Turner"},
{"name": "JT Turner"}, {"name": "Yehuda Katz"}
{"name": "Yehuda Katz"} ],
], "releases": [
"releases": [ {"version": "0.2"}
{"version": "0.2"} {"version": "0.8"},
{"version": "0.8"}, {"version": "0.9999999"}
{"version": "0.9999999"} ],
], "0xATYKARNU": [
"0xATYKARNU": [ ["Th", "e", " "],
["Th", "e", " "], ["BIG", " ", "UnO"],
["BIG", " ", "UnO"], ["punto", "cero"]
["punto", "cero"] ]
] }
} }
} "#;
"#;
let actual = nu!(pipeline(&format!( let actual = nu!(pipeline(&format!(
r#" r#"'{sample}'
'{sample}' | from json
| from json | group-by {{|| get nu.releases.missing_column }}"#
| group-by {{|| get nu.releases.version }}
"#
))); )));
assert!(actual.err.contains("cannot find column"));
assert!(actual.err.contains("can't convert list<string> to string"));
} }
#[test] #[test]

View File

@ -126,3 +126,20 @@ fn prints_only_if_last_pipeline() {
let actual = nu!("try { ['should not print'] | every 1 }; 'last value'"); let actual = nu!("try { ['should not print'] | every 1 }; 'last value'");
assert_eq!(actual.out, "last value"); assert_eq!(actual.out, "last value");
} }
#[test]
fn get_error_columns() {
let actual = nu!(" try { non_existent_command } catch {|err| $err} | columns | to json -r");
assert_eq!(
actual.out,
"[\"msg\",\"debug\",\"raw\",\"rendered\",\"json\"]"
);
}
#[test]
fn get_json_error() {
let actual = nu!("try { non_existent_command } catch {|err| $err} | get json | from json | update labels.span {{start: 0 end: 0}} | to json -r");
assert_eq!(
actual.out, "{\"msg\":\"External command failed\",\"labels\":[{\"text\":\"Command `non_existent_command` not found\",\"span\":{\"start\":0,\"end\":0}}],\"code\":\"nu::shell::external_command\",\"url\":null,\"help\":\"`non_existent_command` is neither a Nushell built-in or a known external command\",\"inner\":[]}"
);
}

View File

@ -220,8 +220,17 @@ fn eval_ir_block_impl<D: DebugContext>(
} }
Err(err) => { Err(err) => {
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
let fancy_errors = match ctx.engine_state.get_config().error_style {
nu_protocol::ErrorStyle::Fancy => true,
nu_protocol::ErrorStyle::Plain => false,
};
// If an error handler is set, branch there // If an error handler is set, branch there
prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span))); prepare_error_handler(
ctx,
error_handler,
Some(err.into_spanned(*span)),
fancy_errors,
);
pc = error_handler.handler_index; pc = error_handler.handler_index;
} else { } else {
// If not, exit the block with the error // If not, exit the block with the error
@ -246,6 +255,7 @@ fn prepare_error_handler(
ctx: &mut EvalContext<'_>, ctx: &mut EvalContext<'_>,
error_handler: ErrorHandler, error_handler: ErrorHandler,
error: Option<Spanned<ShellError>>, error: Option<Spanned<ShellError>>,
fancy_errors: bool,
) { ) {
if let Some(reg_id) = error_handler.error_register { if let Some(reg_id) = error_handler.error_register {
if let Some(error) = error { if let Some(error) = error {
@ -254,7 +264,10 @@ fn prepare_error_handler(
// Create the error value and put it in the register // Create the error value and put it in the register
ctx.put_reg( ctx.put_reg(
reg_id, reg_id,
error.item.into_value(error.span).into_pipeline_data(), error
.item
.into_value(error.span, fancy_errors)
.into_pipeline_data(),
); );
} else { } else {
// Set the register to empty // Set the register to empty

View File

@ -1067,30 +1067,27 @@ pub fn parse_internal_call(
if let Some(arg_shape) = flag.arg { if let Some(arg_shape) = flag.arg {
if let Some(arg) = spans.get(spans_idx + 1) { if let Some(arg) = spans.get(spans_idx + 1) {
let arg = parse_value(working_set, *arg, &arg_shape); let arg = parse_value(working_set, *arg, &arg_shape);
let (arg_name, val_expression) = ensure_flag_arg_type(
working_set,
flag.long.clone(),
arg.clone(),
&arg_shape,
spans[spans_idx],
);
if flag.long.is_empty() { if flag.long.is_empty() {
if let Some(short) = flag.short { if let Some(short) = flag.short {
call.add_named(( call.add_named((
Spanned { arg_name,
item: String::new(),
span: spans[spans_idx],
},
Some(Spanned { Some(Spanned {
item: short.to_string(), item: short.to_string(),
span: spans[spans_idx], span: spans[spans_idx],
}), }),
Some(arg), Some(val_expression),
)); ));
} }
} else { } else {
call.add_named(( call.add_named((arg_name, None, Some(val_expression)));
Spanned {
item: flag.long.clone(),
span: spans[spans_idx],
},
None,
Some(arg),
));
} }
spans_idx += 1; spans_idx += 1;
} else { } else {
@ -5021,8 +5018,8 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
b"<=" => Operator::Comparison(Comparison::LessThanOrEqual), b"<=" => Operator::Comparison(Comparison::LessThanOrEqual),
b">" => Operator::Comparison(Comparison::GreaterThan), b">" => Operator::Comparison(Comparison::GreaterThan),
b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual), b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
b"=~" => Operator::Comparison(Comparison::RegexMatch), b"=~" | b"like" => Operator::Comparison(Comparison::RegexMatch),
b"!~" => Operator::Comparison(Comparison::NotRegexMatch), b"!~" | b"not-like" => Operator::Comparison(Comparison::NotRegexMatch),
b"+" => Operator::Math(Math::Plus), b"+" => Operator::Math(Math::Plus),
b"++" => Operator::Math(Math::Append), b"++" => Operator::Math(Math::Append),
b"-" => Operator::Math(Math::Minus), b"-" => Operator::Math(Math::Minus),

View File

@ -1091,8 +1091,7 @@ fn set_pgrp_from_enter_foreground(pgrp: i64) -> Result<(), ShellError> {
fn set_pgrp_from_enter_foreground(_pgrp: i64) -> Result<(), ShellError> { fn set_pgrp_from_enter_foreground(_pgrp: i64) -> Result<(), ShellError> {
Err(ShellError::NushellFailed { Err(ShellError::NushellFailed {
msg: concat!( msg: concat!(
"EnterForeground asked plugin to join process group, but not supported on ", "EnterForeground asked plugin to join process group, but this is not supported on non UNIX platforms.",
cfg!(target_os)
) )
.into(), .into(),
}) })

View File

@ -35,6 +35,7 @@ miette = { workspace = true, features = ["fancy-no-backtrace"] }
num-format = { workspace = true } num-format = { workspace = true }
rmp-serde = { workspace = true, optional = true } rmp-serde = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true }
thiserror = "1.0" thiserror = "1.0"
typetag = "0.2" typetag = "0.2"
os_pipe = { workspace = true, features = ["io_safety"] } os_pipe = { workspace = true, features = ["io_safety"] }

View File

@ -115,8 +115,8 @@ impl Display for Operator {
Operator::Comparison(Comparison::NotEqual) => write!(f, "!="), Operator::Comparison(Comparison::NotEqual) => write!(f, "!="),
Operator::Comparison(Comparison::LessThan) => write!(f, "<"), Operator::Comparison(Comparison::LessThan) => write!(f, "<"),
Operator::Comparison(Comparison::GreaterThan) => write!(f, ">"), Operator::Comparison(Comparison::GreaterThan) => write!(f, ">"),
Operator::Comparison(Comparison::RegexMatch) => write!(f, "=~"), Operator::Comparison(Comparison::RegexMatch) => write!(f, "=~ or like"),
Operator::Comparison(Comparison::NotRegexMatch) => write!(f, "!~"), Operator::Comparison(Comparison::NotRegexMatch) => write!(f, "!~ or not-like"),
Operator::Comparison(Comparison::LessThanOrEqual) => write!(f, "<="), Operator::Comparison(Comparison::LessThanOrEqual) => write!(f, "<="),
Operator::Comparison(Comparison::GreaterThanOrEqual) => write!(f, ">="), Operator::Comparison(Comparison::GreaterThanOrEqual) => write!(f, ">="),
Operator::Comparison(Comparison::StartsWith) => write!(f, "starts-with"), Operator::Comparison(Comparison::StartsWith) => write!(f, "starts-with"),

View File

@ -139,6 +139,23 @@ impl LabeledError {
self self
} }
pub fn render_error_to_string(diag: impl miette::Diagnostic, fancy_errors: bool) -> String {
let theme = if fancy_errors {
miette::GraphicalTheme::unicode()
} else {
miette::GraphicalTheme::none()
};
let mut out = String::new();
miette::GraphicalReportHandler::new()
.with_width(80)
.with_theme(theme)
.render_report(&mut out, &diag)
.unwrap_or_default();
out
}
/// Create a [`LabeledError`] from a type that implements [`miette::Diagnostic`]. /// Create a [`LabeledError`] from a type that implements [`miette::Diagnostic`].
/// ///
/// # Example /// # Example

View File

@ -1477,13 +1477,16 @@ impl ShellError {
} }
} }
pub fn into_value(self, span: Span) -> Value { pub fn into_value(self, span: Span, fancy_errors: bool) -> Value {
let exit_code = self.external_exit_code(); let exit_code = self.external_exit_code();
let mut record = record! { let mut record = record! {
"msg" => Value::string(self.to_string(), span), "msg" => Value::string(self.to_string(), span),
"debug" => Value::string(format!("{self:?}"), span), "debug" => Value::string(format!("{self:?}"), span),
"raw" => Value::error(self, span), "raw" => Value::error(self.clone(), span),
// "labeled_error" => Value::string(LabeledError::from_diagnostic_and_render(self.clone()), span),
"rendered" => Value::string(ShellError::render_error_to_string(self.clone(), fancy_errors), span),
"json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span),
}; };
if let Some(code) = exit_code { if let Some(code) = exit_code {
@ -1502,6 +1505,21 @@ impl ShellError {
span, span,
) )
} }
pub fn render_error_to_string(diag: impl miette::Diagnostic, fancy_errors: bool) -> String {
let theme = if fancy_errors {
miette::GraphicalTheme::unicode()
} else {
miette::GraphicalTheme::none()
};
let mut out = String::new();
miette::GraphicalReportHandler::new()
.with_width(80)
.with_theme(theme)
.render_report(&mut out, &diag)
.unwrap_or_default();
out
}
} }
impl From<io::Error> for ShellError { impl From<io::Error> for ShellError {

View File

@ -161,20 +161,25 @@ impl Module {
} }
let span = self.span.unwrap_or(backup_span); let span = self.span.unwrap_or(backup_span);
let const_record = Value::record(
const_rows // only needs to bring `$module` with a record value if it defines any constants.
.into_iter() let constants = if const_rows.is_empty() {
.map(|(name, val)| (String::from_utf8_lossy(&name).to_string(), val)) vec![]
.collect(), } else {
span, vec![(
); final_name.clone(),
Value::record(
const_rows
.into_iter()
.map(|(name, val)| (String::from_utf8_lossy(&name).to_string(), val))
.collect(),
span,
),
)]
};
return ( return (
ResolvedImportPattern::new( ResolvedImportPattern::new(decls, vec![(final_name.clone(), self_id)], constants),
decls,
vec![(final_name.clone(), self_id)],
vec![(final_name, const_record)],
),
errors, errors,
); );
}; };

View File

@ -47,8 +47,8 @@ def get-all-operators [] { return [
[Comparison, <=, LessThanOrEqual, "Checks if a value is less than or equal to another.", 80] [Comparison, <=, LessThanOrEqual, "Checks if a value is less than or equal to another.", 80]
[Comparison, >, GreaterThan, "Checks if a value is greater than another.", 80] [Comparison, >, GreaterThan, "Checks if a value is greater than another.", 80]
[Comparison, >=, GreaterThanOrEqual, "Checks if a value is greater than or equal to another.", 80] [Comparison, >=, GreaterThanOrEqual, "Checks if a value is greater than or equal to another.", 80]
[Comparison, =~, RegexMatch, "Checks if a value matches a regular expression.", 80] [Comparison, '=~ or like', RegexMatch, "Checks if a value matches a regular expression.", 80]
[Comparison, !~, NotRegexMatch, "Checks if a value does not match a regular expression.", 80] [Comparison, '!~ or not-like', NotRegexMatch, "Checks if a value does not match a regular expression.", 80]
[Comparison, in, In, "Checks if a value is in a list or string.", 80] [Comparison, in, In, "Checks if a value is in a list or string.", 80]
[Comparison, not-in, NotIn, "Checks if a value is not in a list or string.", 80] [Comparison, not-in, NotIn, "Checks if a value is not in a list or string.", 80]
[Comparison, starts-with, StartsWith, "Checks if a string starts with another.", 80] [Comparison, starts-with, StartsWith, "Checks if a string starts with another.", 80]
@ -771,7 +771,7 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https
let modules = (try { modules $target_item --find $find }) let modules = (try { modules $target_item --find $find })
if not ($modules | is-empty) { return $modules } if not ($modules | is-empty) { return $modules }
if ($find | is-not-empty) { if ($find | is-not-empty) {
print -e $"No help results found mentioning: ($find)" print -e $"No help results found mentioning: ($find)"
return [] return []

View File

@ -32,6 +32,7 @@ mod sort_by_expr;
pub mod sql_context; pub mod sql_context;
pub mod sql_expr; pub mod sql_expr;
mod take; mod take;
mod unnest;
mod unpivot; mod unpivot;
mod with_column; mod with_column;
use filter::LazyFilter; use filter::LazyFilter;
@ -109,5 +110,6 @@ pub(crate) fn data_commands() -> Vec<Box<dyn PluginCommand<Plugin = PolarsPlugin
Box::new(LazyFilter), Box::new(LazyFilter),
Box::new(Shift), Box::new(Shift),
Box::new(Unique), Box::new(Unique),
Box::new(unnest::UnnestDF),
] ]
} }

View File

@ -0,0 +1,145 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, SyntaxShape,
};
use polars::df;
use crate::{
values::{CustomValueSupport, NuLazyFrame, PolarsPluginObject},
PolarsPlugin,
};
use crate::values::NuDataFrame;
#[derive(Clone)]
pub struct UnnestDF;
impl PluginCommand for UnnestDF {
type Plugin = PolarsPlugin;
fn name(&self) -> &str {
"polars unnest"
}
fn description(&self) -> &str {
"Decompose struct columns into separate columns for each of their fields. The new columns will be inserted into the dataframe at the location of the struct column."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest("cols", SyntaxShape::String, "columns to unnest")
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Unnest a dataframe",
example: r#"[[id person]; [1 {name: "Bob", age: 36}] [2 {name: "Betty", age: 63}]]
| polars into-df -s {id: i64, person: {name: str, age: u8}}
| polars unnest person
| polars get id name age
| polars sort-by id"#,
result: Some(
NuDataFrame::from(
df!(
"id" => [1, 2],
"name" => ["Bob", "Betty"],
"age" => [36, 63]
)
.expect("Should be able to create a simple dataframe"),
)
.into_value(Span::test_data()),
),
},
Example {
description: "Unnest a lazy dataframe",
example: r#"[[id person]; [1 {name: "Bob", age: 36}] [2 {name: "Betty", age: 63}]]
| polars into-df -s {id: i64, person: {name: str, age: u8}}
| polars into-lazy
| polars unnest person
| polars select (polars col id) (polars col name) (polars col age)
| polars collect
| polars sort-by id"#,
result: Some(
NuDataFrame::from(
df!(
"id" => [1, 2],
"name" => ["Bob", "Betty"],
"age" => [36, 63]
)
.expect("Should be able to create a simple dataframe"),
)
.into_value(Span::test_data()),
),
},
]
}
fn run(
&self,
plugin: &Self::Plugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
match PolarsPluginObject::try_from_pipeline(plugin, input, call.head)? {
PolarsPluginObject::NuDataFrame(df) => command_eager(plugin, engine, call, df),
PolarsPluginObject::NuLazyFrame(lazy) => command_lazy(plugin, engine, call, lazy),
_ => Err(ShellError::GenericError {
error: "Must be a dataframe or lazy dataframe".into(),
msg: "".into(),
span: Some(call.head),
help: None,
inner: vec![],
}),
}
.map_err(LabeledError::from)
}
}
fn command_eager(
plugin: &PolarsPlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
df: NuDataFrame,
) -> Result<PipelineData, ShellError> {
let cols = call.rest::<String>(0)?;
let polars = df.to_polars();
let result: NuDataFrame = polars
.unnest(cols)
.map_err(|e| ShellError::GenericError {
error: format!("Error unnesting dataframe: {e}"),
msg: "".into(),
span: Some(call.head),
help: None,
inner: vec![],
})?
.into();
result.to_pipeline_data(plugin, engine, call.head)
}
fn command_lazy(
plugin: &PolarsPlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
df: NuLazyFrame,
) -> Result<PipelineData, ShellError> {
let cols = call.rest::<String>(0)?;
let polars = df.to_polars();
let result: NuLazyFrame = polars.unnest(cols).into();
result.to_pipeline_data(plugin, engine, call.head)
}
#[cfg(test)]
mod test {
use crate::test::test_polars_plugin_command;
use super::*;
#[test]
fn test_examples() -> Result<(), ShellError> {
test_polars_plugin_command(&UnnestDF)
}
}

View File

@ -31,13 +31,13 @@ impl PluginCommand for UnpivotDF {
Signature::build(self.name()) Signature::build(self.name())
.required_named( .required_named(
"index", "index",
SyntaxShape::Table(vec![]), SyntaxShape::List(Box::new(SyntaxShape::Any)),
"column names for unpivoting", "column names for unpivoting",
Some('i'), Some('i'),
) )
.required_named( .required_named(
"on", "on",
SyntaxShape::Table(vec![]), SyntaxShape::List(Box::new(SyntaxShape::Any)),
"column names used as value columns", "column names used as value columns",
Some('o'), Some('o'),
) )

View File

@ -16,4 +16,4 @@ profile = "default"
# use in nushell, we may opt to use the bleeding edge stable version of rust. # use in nushell, we may opt to use the bleeding edge stable version of rust.
# I believe rust is on a 6 week release cycle and nushell is on a 4 week release cycle. # I believe rust is on a 6 week release cycle and nushell is on a 4 week release cycle.
# So, every two nushell releases, this version number should be bumped by one. # So, every two nushell releases, this version number should be bumped by one.
channel = "1.79.0" channel = "1.80.1"

View File

@ -238,13 +238,30 @@ fn complex_const_export() {
let actual = nu!(&inp.join("; ")); let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "eats"); assert_eq!(actual.out, "eats");
let inp = &[ let inp = &[MODULE_SETUP, "use spam", "'none' in $spam.eggs.bacon"];
MODULE_SETUP,
"use spam",
"($spam.eggs.bacon.none | is-empty)",
];
let actual = nu!(&inp.join("; ")); let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "true"); assert_eq!(actual.out, "false");
}
#[test]
fn only_nested_module_have_const() {
let setup = r#"
module spam {
export module eggs {
export module bacon {
export const viking = 'eats'
export module none {}
}
}
}
"#;
let inp = &[setup, "use spam", "$spam.eggs.bacon.viking"];
let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "eats");
let inp = &[setup, "use spam", "'none' in $spam.eggs.bacon"];
let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "false");
} }
#[test] #[test]
@ -261,20 +278,16 @@ fn complex_const_glob_export() {
let actual = nu!(&inp.join("; ")); let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "eats"); assert_eq!(actual.out, "eats");
let inp = &[MODULE_SETUP, "use spam *", "($eggs.bacon.none | is-empty)"]; let inp = &[MODULE_SETUP, "use spam *", "'none' in $eggs.bacon"];
let actual = nu!(&inp.join("; ")); let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "true"); assert_eq!(actual.out, "false");
} }
#[test] #[test]
fn complex_const_drill_export() { fn complex_const_drill_export() {
let inp = &[ let inp = &[MODULE_SETUP, "use spam eggs bacon none", "$none"];
MODULE_SETUP,
"use spam eggs bacon none",
"($none | is-empty)",
];
let actual = nu!(&inp.join("; ")); let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "true"); assert!(actual.err.contains("variable not found"));
} }
#[test] #[test]

View File

@ -12,7 +12,7 @@ fn plugin_list_shows_installed_plugins() {
plugins: [("nu_plugin_inc"), ("nu_plugin_custom_values")], plugins: [("nu_plugin_inc"), ("nu_plugin_custom_values")],
r#"(plugin list).name | str join ','"# r#"(plugin list).name | str join ','"#
); );
assert_eq!("inc,custom_values", out.out); assert_eq!("custom_values,inc", out.out);
assert!(out.status.success()); assert!(out.status.success());
} }
@ -34,15 +34,15 @@ fn plugin_keeps_running_after_calling_it() {
plugin: ("nu_plugin_inc"), plugin: ("nu_plugin_inc"),
r#" r#"
plugin stop inc plugin stop inc
(plugin list).0.is_running | print (plugin list).0.status == running | print
print ";" print ";"
"2.0.0" | inc -m | ignore "2.0.0" | inc -m | ignore
(plugin list).0.is_running | print (plugin list).0.status == running | print
"# "#
); );
assert_eq!( assert_eq!(
"false;true", out.out, "false;true", out.out,
"plugin list didn't show is_running = true" "plugin list didn't show status = running"
); );
assert!(out.status.success()); assert!(out.status.success());
} }
@ -244,7 +244,7 @@ fn plugin_gc_can_be_configured_to_stop_plugins_immediately() {
$env.config.plugin_gc = { default: { stop_after: 0sec } } $env.config.plugin_gc = { default: { stop_after: 0sec } }
"2.3.0" | inc -M "2.3.0" | inc -M
sleep 100ms sleep 100ms
(plugin list | where name == inc).0.is_running (plugin list | where name == inc).0.status == running
"# "#
); );
assert!(out.status.success()); assert!(out.status.success());
@ -261,7 +261,7 @@ fn plugin_gc_can_be_configured_to_stop_plugins_immediately() {
} }
"2.3.0" | inc -M "2.3.0" | inc -M
sleep 100ms sleep 100ms
(plugin list | where name == inc).0.is_running (plugin list | where name == inc).0.status == running
"# "#
); );
assert!(out.status.success()); assert!(out.status.success());
@ -281,7 +281,7 @@ fn plugin_gc_can_be_configured_to_stop_plugins_after_delay() {
while $cond { while $cond {
sleep 100ms sleep 100ms
$cond = ( $cond = (
(plugin list | where name == inc).0.is_running and (plugin list | where name == inc).0.status == running and
((date now) - $start) < 5sec ((date now) - $start) < 5sec
) )
} }
@ -310,7 +310,7 @@ fn plugin_gc_can_be_configured_to_stop_plugins_after_delay() {
while $cond { while $cond {
sleep 100ms sleep 100ms
$cond = ( $cond = (
(plugin list | where name == inc).0.is_running and (plugin list | where name == inc).0.status == running and
((date now) - $start) < 5sec ((date now) - $start) < 5sec
) )
} }
@ -333,7 +333,7 @@ fn plugin_gc_can_be_configured_as_disabled() {
r#" r#"
$env.config.plugin_gc = { default: { enabled: false, stop_after: 0sec } } $env.config.plugin_gc = { default: { enabled: false, stop_after: 0sec } }
"2.3.0" | inc -M "2.3.0" | inc -M
(plugin list | where name == inc).0.is_running (plugin list | where name == inc).0.status == running
"# "#
); );
assert!(out.status.success()); assert!(out.status.success());
@ -350,7 +350,7 @@ fn plugin_gc_can_be_configured_as_disabled() {
} }
} }
"2.3.0" | inc -M "2.3.0" | inc -M
(plugin list | where name == inc).0.is_running (plugin list | where name == inc).0.status == running
"# "#
); );
assert!(out.status.success()); assert!(out.status.success());
@ -367,7 +367,7 @@ fn plugin_gc_can_be_disabled_by_plugin() {
$env.config.plugin_gc = { default: { stop_after: 0sec } } $env.config.plugin_gc = { default: { stop_after: 0sec } }
example one 1 foo | ignore # ensure we've run the plugin with the new config example one 1 foo | ignore # ensure we've run the plugin with the new config
sleep 100ms sleep 100ms
(plugin list | where name == example).0.is_running (plugin list | where name == example).0.status == running
"# "#
); );
assert!(out.status.success()); assert!(out.status.success());

View File

@ -37,7 +37,7 @@ fn plugin_add_then_restart_nu() {
--config $nu.config-path --config $nu.config-path
--env-config $nu.env-path --env-config $nu.env-path
--plugin-config $nu.plugin-path --plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw' --commands 'plugin list --engine | get name | to json --raw'
) )
", example_plugin_path().display()) ", example_plugin_path().display())
); );
@ -69,7 +69,7 @@ fn plugin_add_in_nu_plugin_dirs_const() {
--config $nu.config-path --config $nu.config-path
--env-config $nu.env-path --env-config $nu.env-path
--plugin-config $nu.plugin-path --plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw' --commands 'plugin list --engine | get name | to json --raw'
) )
"#, "#,
dirname.display(), dirname.display(),
@ -103,7 +103,7 @@ fn plugin_add_in_nu_plugin_dirs_env() {
--config $nu.config-path --config $nu.config-path
--env-config $nu.env-path --env-config $nu.env-path
--plugin-config $nu.plugin-path --plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw' --commands 'plugin list --engine | get name | to json --raw'
) )
"#, "#,
dirname.display(), dirname.display(),
@ -199,7 +199,7 @@ fn plugin_rm_then_restart_nu() {
"--plugin-config", "--plugin-config",
"test-plugin-file.msgpackz", "test-plugin-file.msgpackz",
"--commands", "--commands",
"plugin list | get name | to json --raw", "plugin list --engine | get name | to json --raw",
]) ])
.assert() .assert()
.success() .success()
@ -364,7 +364,7 @@ fn warning_on_invalid_plugin_item() {
"--plugin-config", "--plugin-config",
"test-plugin-file.msgpackz", "test-plugin-file.msgpackz",
"--commands", "--commands",
"plugin list | get name | to json --raw", "plugin list --engine | get name | to json --raw",
]) ])
.output() .output()
.expect("failed to run nu"); .expect("failed to run nu");
@ -412,6 +412,43 @@ fn plugin_use_error_not_found() {
}) })
} }
#[test]
fn plugin_shows_up_in_default_plugin_list_after_add() {
let example_plugin_path = example_plugin_path();
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(r#"
plugin add '{}'
plugin list | get status | to json --raw
"#, example_plugin_path.display())
);
assert!(result.status.success());
assert_eq!(r#"["added"]"#, result.out);
}
#[test]
fn plugin_shows_removed_after_removing() {
let example_plugin_path = example_plugin_path();
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(r#"
plugin add '{}'
plugin list | get status | to json --raw
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin rm example; plugin list | get status | to json --raw'
)
"#, example_plugin_path.display())
);
assert!(result.status.success());
assert_eq!(r#"["removed"]"#, result.out);
}
#[test] #[test]
fn plugin_add_and_then_use() { fn plugin_add_and_then_use() {
let example_plugin_path = example_plugin_path(); let example_plugin_path = example_plugin_path();
@ -425,7 +462,7 @@ fn plugin_add_and_then_use() {
--config $nu.config-path --config $nu.config-path
--env-config $nu.env-path --env-config $nu.env-path
--plugin-config $nu.plugin-path --plugin-config $nu.plugin-path
--commands 'plugin use example; plugin list | get name | to json --raw' --commands 'plugin use example; plugin list --engine | get name | to json --raw'
) )
"#, example_plugin_path.display()) "#, example_plugin_path.display())
); );
@ -446,7 +483,7 @@ fn plugin_add_and_then_use_by_filename() {
--config $nu.config-path --config $nu.config-path
--env-config $nu.env-path --env-config $nu.env-path
--plugin-config $nu.plugin-path --plugin-config $nu.plugin-path
--commands 'plugin use '{0}'; plugin list | get name | to json --raw' --commands 'plugin use '{0}'; plugin list --engine | get name | to json --raw'
) )
"#, example_plugin_path.display()) "#, example_plugin_path.display())
); );
@ -471,7 +508,7 @@ fn plugin_add_then_use_with_custom_path() {
cwd: dirs.test(), cwd: dirs.test(),
r#" r#"
plugin use --plugin-config test-plugin-file.msgpackz example plugin use --plugin-config test-plugin-file.msgpackz example
plugin list | get name | to json --raw plugin list --engine | get name | to json --raw
"# "#
); );

View File

@ -1,6 +1,7 @@
use crate::repl::tests::{fail_test, run_test, run_test_contains, TestResult}; use crate::repl::tests::{fail_test, run_test, run_test_contains, TestResult};
use nu_test_support::nu; use nu_test_support::nu;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
#[test] #[test]
fn no_scope_leak1() -> TestResult { fn no_scope_leak1() -> TestResult {
@ -73,10 +74,21 @@ fn custom_switch1() -> TestResult {
) )
} }
#[test] #[rstest]
fn custom_flag_with_type_checking() -> TestResult { fn custom_flag_with_type_checking(
#[values(
("int", "\"3\""),
("int", "null"),
("record<i: int>", "{i: \"\"}"),
("list<int>", "[\"\"]")
)]
type_sig_value: (&str, &str),
#[values("--dry-run", "-d")] flag: &str,
) -> TestResult {
let (type_sig, value) = type_sig_value;
fail_test( fail_test(
r#"def florb [--dry-run: int] { $dry_run }; let y = "3"; florb --dry-run=$y"#, &format!("def florb [{flag}: {type_sig}] {{}}; let y = {value}; florb {flag} $y"),
"type_mismatch", "type_mismatch",
) )
} }

View File

@ -120,6 +120,11 @@ fn export_consts() -> TestResult {
) )
} }
#[test]
fn dont_export_module_name_as_a_variable() -> TestResult {
fail_test(r#"module spam { }; use spam; $spam"#, "variable not found")
}
#[test] #[test]
fn func_use_consts() -> TestResult { fn func_use_consts() -> TestResult {
run_test( run_test(