Compare commits

...

156 Commits

Author SHA1 Message Date
3b220e07e3 Bump version to 0.93.0 (#12709)
# Description

Bump version to `0.93.0`
2024-04-30 15:51:13 -07:00
16799a1d78 Bump reedline to 0.32.0 (#12708)
# Description

Follow `reedline` release to `0.32.0`, disable crates.io git patch
2024-04-30 15:32:54 -07:00
38bf3f6e1b Update the bundled readme in release archives (#12688)
Reflect the deprecation of `register`

cc @devyn

---------

Co-authored-by: Devyn Cairns <devyn.cairns@gmail.com>
2024-04-30 00:42:21 -07:00
e83123dcca Add fs feature to nix dependency (#12702)
# Description
Caught a compilation error using `cargo hack` -- `nu-utils` will not
compile without the `fs` feature enabled for `nix`.
2024-04-29 22:50:39 +00:00
648486400c Fix missing local socket feature (#12698)
# Description

So sorry to do this during the pre-release freeze, but my plugin crate
split PR broke local socket mode, because `nu-plugin-protocol` didn't
have the compile feature to advertise the `LocalSocket` protocol
feature.

This is a very simple, configuration-only bugfix that I think really
needs to be merged before the release, or else local socket mode won't
work at all.

# Tests + Formatting

There's an oversight in my testing that caused this to not be caught:
the engine really did have the feature, but it just wasn't advertising
it, so for `stress_internals` it was still able to use it successfully.
Post-release I'll try to make sure this is properly handled somehow.
2024-04-29 15:02:56 +08:00
59ee96c70d fixes a rust-analyzer warning (#12694)
# Description

Minor change but fixes a few rust-analyzer warnings.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-28 04:52:05 -05:00
365f5954ee restore query web --as-table to working order (#12693)
# Description

This PR fixes a problem introduced with PR
https://github.com/nushell/nushell/pull/12236. That PR accidentally
stopped `--as-table` from working.

Closes https://github.com/nushell/nushell/issues/12689

It works again.

![image](https://github.com/nushell/nushell/assets/343840/b517507f-6b92-4e39-a389-5c69907d77c0)

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-28 04:21:26 -05:00
24ecb84d97 update wix to include nu_plugin_polars (#12687)
# Description

Update wix/msi to include nu_plugin_polars. 🤞🏻 

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-27 16:00:12 -05:00
0c4d5330ee Split the plugin crate (#12563)
# Description

This breaks `nu-plugin` up into four crates:

- `nu-plugin-protocol`: just the type definitions for the protocol, no
I/O. If someone wanted to wire up something more bare metal, maybe for
async I/O, they could use this.
- `nu-plugin-core`: the shared stuff between engine/plugin. Less stable
interface.
- `nu-plugin-engine`: everything required for the engine to talk to
plugins. Less stable interface.
- `nu-plugin`: everything required for the plugin to talk to the engine,
what plugin developers use. Should be the most stable interface.

No changes are made to the interface exposed by `nu-plugin` - it should
all still be there. Re-exports from `nu-plugin-protocol` or
`nu-plugin-core` are used as required. Plugins shouldn't ever have to
use those crates directly.

This should be somewhat faster to compile as `nu-plugin-engine` and
`nu-plugin` can compile in parallel, and the engine doesn't need
`nu-plugin` and plugins don't need `nu-plugin-engine` (except for test
support), so that should reduce what needs to be compiled too.

The only significant change here other than splitting stuff up was to
break the `source` out of `PluginCustomValue` and create a new
`PluginCustomValueWithSource` type that contains that instead. One bonus
of that is we get rid of the option and it's now more type-safe, but it
also means that the logic for that stuff (actually running the plugin
for custom value ops) can live entirely within the `nu-plugin-engine`
crate.

# User-Facing Changes
- New crates.
- Added `local-socket` feature for `nu` to try to make it possible to
compile without that support if needed.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-27 12:08:12 -05:00
884d5312bb add tests to polars unique (#12683)
# Description

I would like to help with `polars` plugin development and add tests to
all the `polars` command's existing params.

Since I have never written any lines of Rust, even though the task of
creating tests is relatively simple, I would like to ask for feedback to
ensure I did everything correctly here.
2024-04-27 12:04:54 -05:00
76d1d70e83 Upgrade hustcer/setup-nu to v3.10 to fix macOS arm64 build errors (#12681)
Upgrade hustcer/setup-nu to
[v3.10](https://github.com/hustcer/setup-nu/releases/tag/v3.10) to fix
macOS arm64 build errors
Release Testing:
https://github.com/nushell/nightly/actions/runs/8856404620
2024-04-27 10:18:08 +08:00
02de69de92 Fix inconsistent print behavior (#12675)
# Description

I found a bunch of issues relating to the specialized reimplementation
of `print()` that's done in `nu-cli` and it just didn't seem necessary.
So I tried to unify the behavior reasonably. `PipelineData::print()`
already handles the call to `table` and it even has a `no_newline`
option.

One of the most major issues before was that we were using the value
iterator, and then converting to string, and then printing each with
newlines. This doesn't work well for an external stream, because its
iterator ends up creating `Value::binary()` with each buffer... so we
were doing lossy UTF-8 conversion on those and then printing them with
newlines, which was very weird:


![Screenshot_2024-04-26_02-02-29](https://github.com/nushell/nushell/assets/10729/131c2224-08ee-4582-8617-6ecbb3ce8da5)

You can see the random newline inserted in a break between buffers, but
this would be even worse if it were on a multibyte UTF-8 character. You
can produce this by writing a large amount of text to a text file, and
then doing `nu -c 'open file.txt'` - in my case I just wrote `^find .`;
it just has to be large enough to trigger a buffer break.

Using `print()` instead led to a new issue though, because it doesn't
abort on errors. This is so that certain commands can produce a stream
of errors and have those all printed. There are tests for e.g. `rm` that
depend on this behavior. I assume we want to keep that, so instead I
made my target `BufferedReader`, and had that fuse closed if an error
was encountered. I can't imagine we want to keep reading from a wrapped
I/O stream if an error occurs; more often than not the error isn't going
to magically resolve itself, it's not going to be a different error each
time, and it's just going to lead to an infinite stream of the same
error.

The test that broke without that was `open . | lines`, because `lines`
doesn't fuse closed on error. But I don't know if it's expected or not
for it to do that, so I didn't target that.

I think this PR makes things better but I'll keep looking for ways to
improve on how errors and streams interact, especially trying to
eliminate cases where infinite error loops can happen.

# User-Facing Changes
- **Breaking**: `BufferedReader` changes + no more public fields
- A raw I/O stream from e.g. `open` won't produce infinite errors
anymore, but I consider that to be a plus
- the implicit `print` on script output is the same as the normal one
now

# Tests + Formatting
Everything passes but I didn't add anything specific.
2024-04-27 00:25:11 +00:00
533603b72c Add deprecation warning to describe --collect-lazyrecords (#12667)
# Description
Missed a spot for lazy record deprecation. This adds a warning for the
`--collect-lazyrecords` flag on `describe`.
2024-04-26 16:36:30 +00:00
1ecbb3e09f Make exit code available in catch block (#12648)
# Description
Bandaid fix for #12643, where it is not possible to get the exit code of
a failed external command while also having the external command inherit
nushell's stdout and stderr. This changes `try` so that the exit code of
external command is available in the `catch` block via the usual
`$env.LAST_EXIT_CODE`.

# Tests + Formatting
Added one test.

# After Submitting
Rework I/O redirection and possibly exit codes.
2024-04-26 16:35:08 +00:00
d23a3737c0 make grid throw an error when not enough columns (#12672)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Resolves #12654. 

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

`grid` can now throw an error.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

Added relevant test.
2024-04-26 06:33:00 -05:00
822c434c12 Add a bit more delay before ps calls in plugin persistence tests (#12673)
# Description
I've found that sometimes on Linux, this test fails to find the created
process even after it should definitely be running.

Trying to add a little delay.
2024-04-26 06:24:57 -05:00
d126793290 Add plugin error propagation on write/flush (#12670)
# Description
Yet another attempt to fix the `stress_internals::test_wrong_version()`
test...

This time I think it's probably because we are getting a broken pipe
write error when we try to send `Hello` or perhaps something after it,
because the pipe has already been closed by the reader when it saw the
invalid version. In that case, an error should be available in state. It
probably makes more sense to send that back to the user rather than an
unhelpful I/O error.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-26 06:23:58 -05:00
adf38c7c76 Msgpack commands (#12664)
# Description

I thought about bringing `nu_plugin_msgpack` in, but that is MPL with a
clause that prevents other licenses, so rather than adapt that code I
decided to take a crack at just doing it straight from `rmp` to `Value`
without any `rmpv` in the middle. It seems like it's probably faster,
though I can't say for sure how much with the plugin overhead.

@IanManske I started on a `Read` implementation for `RawStream` but just
specialized to `from msgpack` here, but I'm thinking after release maybe
we can polish it up and make it a real one. It works!

# User-Facing Changes
New commands:

- `from msgpack`
- `from msgpackz`
- `to msgpack`
- `to msgpackz`

# Tests + Formatting
Pretty thorough tests added for the format deserialization, with a
roundtrip for serialization. Some example tests too for both `from
msgpack` and `to msgpack`.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`


# After Submitting
- [ ] update release notes
2024-04-26 06:23:16 -05:00
79ebf0c0a9 Fix an into bits example (#12668)
# Description
One example for `into bits` says it uses binary value when it actually
uses a filesize. This lead to issue #11412, but I never got around to
fixing the example until this PR.
2024-04-25 19:38:28 -05:00
f234a0ea33 each signature fix (#12666)
# Description
Removes the second `Int` parameter from the closure in the signature of
`each`. This parameter doesn't exist / isn't supported.
2024-04-25 22:47:30 +02:00
2466a39574 Fix example wording in seq date (#12665)
# Description

The previous messages said that the command printed dates separated by
newlines. But the current iteration of `seq date` returns a list.

# User-Facing Changes

Minor wording edit.

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-04-25 22:12:42 +02:00
1239e86320 Remove deprecated flags on run-external (#12659)
# Description
Removes the `run-external` flags that were deprecated in 0.92.0 with
#11934.
2024-04-25 14:17:21 +00:00
5b0546cfce Remove deprecated flags on commandline (#12658)
# Description
Removes the `commandline` flags and API that was deprecated in 0.91.0
with #11877.

# User-Facing Changes
Users need to migrate to the new `commandline` subcommands introduced in
0.91.0.
2024-04-25 14:16:42 +00:00
b6d765a2d8 nu-cmd-lang cleanup (#12609)
# Description
This PR does miscellaneous cleanup in some of the commands from
`nu-cmd-lang`.

# User-Facing Changes
None.

# After Submitting
Cleanup the other commands in `nu-cmd-lang`.
2024-04-25 14:16:12 +00:00
530162b4c4 Lazy record deprecation (#12656)
# Description
In this week's nushell meeting, we decided to go ahead with #12622 and
remove lazy records in 0.94.0. For 0.93.0, we will only deprecate `lazy
make`, and so this PR makes `lazy make` print a deprecation warning.

# User-Facing Changes
None, besides the deprecation warning.

# After Submitting
Remove lazy records.
2024-04-25 07:05:24 +08:00
25cbcb511d Rename plugin cache file ⇒ plugin registry file (#12634)
# Description
So far this seems like the winner of my poll on what the name should be.
I'll take this off draft once the poll expires, if this is indeed the
winner.
2024-04-24 17:40:39 -05:00
9996e4a1f8 Shrink the size of Expr (#12610)
# Description
Continuing from #12568, this PR further reduces the size of `Expr` from
64 to 40 bytes. It also reduces `Expression` from 128 to 96 bytes and
`Type` from 32 to 24 bytes.

This was accomplished by:
- for `Expr` with multiple fields (e.g., `Expr::Thing(A, B, C)`),
merging the fields into new AST struct types and then boxing this struct
(e.g. `Expr::Thing(Box<ABC>)`).
- replacing `Vec<T>` with `Box<[T]>` in multiple places. `Expr`s and
`Expression`s should rarely be mutated, if at all, so this optimization
makes sense.

By reducing the size of these types, I didn't notice a large performance
improvement (at least compared to #12568). But this PR does reduce the
memory usage of nushell. My config is somewhat light so I only noticed a
difference of 1.4MiB (38.9MiB vs 37.5MiB).

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-04-24 15:46:35 +00:00
c52884b3c8 Fix (and test) for a deadlock that can happen while waiting for protocol info (#12633)
# Description

The local socket PR introduced a `Waitable` type, which could either
hold a value or be waited on until a value is available. Unlike a
channel, it would always return that value once set.

However, one issue with this design was that there was no way to detect
whether a value would ever be written. This splits the writer into a
different type `WaitableMut`, so that when it is dropped, waiting
threads can fail (because they'll never get a value).

# Tests + Formatting

A test has been added to `stress_internals` to make sure this fails in
the right way.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-24 08:44:04 -05:00
0f645b3bb6 Futher improve messages related to error propagation on plugin calls (#12646)
# Description
Trying to give as much context as possible. Now there should be a
spanned error with the call span if possible, and the propagated error
as an inner error if there was one in every case.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-24 08:39:04 -05:00
1e453020b6 update to latest reedline (#12644)
# Description

Update to latest reedline main branch 4cf8c75d for testing before
release.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-24 07:40:04 -05:00
06c72df672 Bump serial_test from 3.0.0 to 3.1.0 (#12638) 2024-04-24 12:01:20 +00:00
9d65c47313 Bump rust-ini from 0.20.0 to 0.21.0 (#12637) 2024-04-24 11:55:20 +00:00
b576123b0a Accept filenames in other plugin management commands (#12639)
# Description

This allows the following commands to all accept a filename instead of a
plugin name:

- `plugin use`
- `plugin rm`
- `plugin stop`

Slightly complicated because of the need to also check against
`NU_PLUGIN_DIRS`, but I also fixed some issues with that at the same
time

Requested by @fdncred

# User-Facing Changes

The new commands are updated as described.

# Tests + Formatting

Tests for `NU_PLUGIN_DIRS` handling also made more robust.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Double check new docs to make sure they describe this capability
2024-04-24 06:28:45 -05:00
1633004643 Bump actions/checkout from 4.1.2 to 4.1.3 (#12635) 2024-04-24 14:10:34 +08:00
fbb4fad7ac Bump crate-ci/typos from 1.20.9 to 1.20.10 (#12636) 2024-04-24 00:26:06 +00:00
c9bc0c7d3e Fix error message propagation on plugin call failure (#12632)
# Description

This should fix the sometimes failing wrong version test for
stress_internals.

The plugin interface state stores an error if some kind of critical
error happened, and this error should be propagated to any future
operations on the interface, but this wasn't being propagated to plugin
calls that were already waiting.

During plugin registration, the wrong version error needs to be received
as a response to the `get_signature()` to show up properly, but this
would only happen if `get_signature()` started after the `Hello` was
already received and processed. That would be a race condition, which
this commit solves.

cc @sholderbach - this should fix the CI issue

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-23 17:30:51 -05:00
eec8645b9c update to latest reedline 455b9a3 (#12630)
# Description

This PR updates to the latest main branch in the reedline repo in order
to test the latest reedline changes.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-23 10:54:14 -05:00
1f4131532d Deprecate register and add plugin use (#12607)
# Description

Adds a new keyword, `plugin use`. Unlike `register`, this merely loads
the signatures from the plugin cache file. The file is configurable with
the `--plugin-config` option either to `nu` or to `plugin use` itself,
just like the other `plugin` family of commands. At the REPL, one might
do this to replace `register`:

```nushell
> plugin add ~/.cargo/bin/nu_plugin_foo
> plugin use foo
```

This will not work in a script, because `plugin use` is a keyword and
`plugin add` does not evaluate at parse time (intentionally). This means
we no longer run random binaries during parse.

The `--plugins` option has been added to allow running `nu` with certain
plugins in one step. This is used especially for the `nu_with_plugins!`
test macro, but I'd imagine is generally useful. The only weird quirk is
that it has to be a list, and we don't really do this for any of our
other CLI args at the moment.

`register` now prints a deprecation parse warning.

This should fix #11923, as we now have a complete alternative to
`register`.

# User-Facing Changes

- Add `plugin use` command
- Deprecate `register`
- Add `--plugins` option to `nu` to replace a common use of `register`

# Tests + Formatting

I think I've tested it thoroughly enough and every existing test passes.
Testing nu CLI options and alternate config files is a little hairy and
I wish there were some more generic helpers for this, so this will go on
my TODO list for refactoring.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Update plugins sections of book
- [ ] Release notes
2024-04-23 06:37:50 -05:00
5c7f7883c8 Add ErrSpan extension trait for Result (#12626)
# Description
This adds an extension trait to `Result` that wraps errors in `Spanned`,
saving the effort of calling `.map_err(|err| err.into_spanned(span))`
every time. This will hopefully make it even more likely that someone
will want to use a spanned `io::Error` and make it easier to remove the
impl for `From<io::Error> for ShellError` because that doesn't have span
information.
2024-04-23 10:39:55 +02:00
b0acc1d890 Avoid panic when pipe a variable to a custom command which have recursive call (#12491)
# Description
Fixes: #11351

And comment here is also fixed:
https://github.com/nushell/nushell/issues/11351#issuecomment-1996191537

The panic can happened if we pipe a variable to a custom command which
recursively called itself inside another block.

TBH, I think I figure out how it works to panic, but I'm not sure if
there is a potention issue if nushell don't mutate a block in such case.

# User-Facing Changes
Nan

# Tests + Formatting
Done

# After Submitting
Done

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2024-04-23 06:10:35 +08:00
bed236362a bump reedline to latest commit point on main (#12621)
https://github.com/nushell/reedline/pull/781

This will allow others to test and make sure that this reedline fix
works on other terminals and platforms...
2024-04-22 11:48:30 -07:00
797a90520c Improve the "input and output are the same file" error text (#12619)
# Description
Continuing from #12601, this PR improves the error message help text and
adds an example to `collect`.
2024-04-22 09:00:38 -05:00
20bf3c587f Update ratatui to deduplicate syn in build (#12606)
`ratatui` introduced a dependency on `stability` which until recently
used `syn 1.x`, with this update all our crates in the default `cargo
build` path are using `syn 2.x`
2024-04-22 14:48:33 +02:00
bae6d694ca Refactor using ClosureEval types (#12541)
# Description
Adds two new types in `nu-engine` for evaluating closures: `ClosureEval`
and `ClosureEvalOnce`. This removed some duplicate code and centralizes
our logic for setting up, running, and cleaning up closures. For
example, in the future if we are able to reduce the cloning necessary to
run a closure, then we only have to change the code related to these
types.

`ClosureEval` and `ClosureEvalOnce` are designed with a builder API.
`ClosureEval` is used to run a closure multiple times whereas
`ClosureEvalOnce` is used for a one-shot closure.

# User-Facing Changes
Should be none, unless I messed up one of the command migrations.
Actually, this will fix any unreported environment bugs for commands
that didn't reset the env after running a closure.
2024-04-22 14:15:09 +08:00
83720a9f30 Make the same file error more likely to appear (#12601)
# Description
When saving to a file we currently try to check if the data source in
the pipeline metadata is the same as the file we are saving to. If so,
we create an error, since reading and writing to a file at the same time
is currently not supported/handled gracefully. However, there are still
a few instances where this error is not properly triggered, and so this
PR attempts to reduce these cases. Inspired by #12599.

# Tests + Formatting
Added a few tests.

# After Submitting
Some commands still do not properly preserve metadata (e.g., `str trim`)
and so prevent us from detecting this error.
2024-04-22 01:12:13 +00:00
a60381a932 Added commands for working with the plugin cache. (#12576)
# Description
This pull request provides three new commands:
`polars store-ls` - moved from `polars ls`. It provides the list of all
object stored in the plugin cache
`polars store-rm` - deletes a cached object
`polars store-get` - gets an object from the cache. 

The addition of `polars store-get` required adding a reference_count to
cached entries. `polars get` is the only command that will increment
this value. `polars rm` will remove the value despite it's count. Calls
to PolarsPlugin::custom_value_dropped will decrement the value.

The prefix store- was chosen due to there already being a `polars cache`
command. These commands were not made sub-commands as there isn't a way
to display help for sub commands in plugins (e.g. `polars store`
displaying help) and I felt the store- seemed fine anyways.

The output of `polars store-ls` now shows the reference count for each
object.

# User-Facing Changes
polars ls has now moved to polars store-ls

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-21 19:43:43 -05:00
aad3ac11da update toolkit register pluginstoolkit add plugins (#12613)
# Description

Updates `toolkit.nu` to use `plugin add`.

There's no need to spawn a separate `nu` to do `register` anymore, since
`plugin add` is not a keyword
2024-04-21 18:23:12 -05:00
8b7696f4c1 stress_internals: exit(1) on io error (#12612)
# Description

The `stress_internals` tests can fail sometimes, but usually not on the
CI, because Nushell exits while the plugin is still trying to read or
maybe write something, leading to a broken pipe.

`nu-plugin` already exits with 1 without printing a message on a
protocol-level I/O error, so this just doing the same thing.

I think there's probably a way to correct the plugin handling so that we
wait for plugins to shut down before exiting and this doesn't happen,
but this is the quick fix in the meantime.
2024-04-21 23:48:09 +02:00
a900166e27 fix typo in the documentation of nuon::ToStyle (#12608)
follow-up to
- https://github.com/nushell/nushell/pull/12591

cc/ @fdncred 

# Description
there was a typo in the doc of `nuon::ToStyle`.

# User-Facing Changes

# Tests + Formatting

# After Submitting
2024-04-21 10:53:53 -05:00
2595f31541 Overhaul the plugin cache file with a new msgpack+brotli format (#12579)
# Description

- Plugin signatures are now saved to `plugin.msgpackz`, which is
brotli-compressed MessagePack.
- The file is updated incrementally, rather than writing all plugin
commands in the engine every time.
- The file always contains the result of the `Signature` call to the
plugin, even if commands were removed.
- Invalid data for a particular plugin just causes an error to be
reported, but the rest of the plugins can still be parsed

# User-Facing Changes

- The plugin file has a different filename, and it's not a nushell
script.
- The default `plugin.nu` file will be automatically migrated the first
time, but not other plugin config files.
- We don't currently provide any utilities that could help edit this
file, beyond `plugin add` and `plugin rm`
  - `from msgpackz`, `to msgpackz` could also help
- New commands: `plugin add`, `plugin rm`

# Tests + Formatting

Tests added for the format and for the invalid handling.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Check for documentation changes
- [ ] Definitely needs release notes
2024-04-21 07:36:26 -05:00
6cba7c6b40 Small refactor in cal (#12604)
Removed some unnecessary code found when looking at #12597

- **Simplify day of the week selection**
- **Use `Record` directly instead of `IndexMap`**
2024-04-21 10:41:26 +02:00
3b1d405b96 Remove the Value::Block case (#12582)
# Description
`Value` describes the types of first-class values that users and scripts
can create, manipulate, pass around, and store. However, `Block`s are
not first-class values in the language, so this PR removes it from
`Value`. This removes some unnecessary code, and this change should be
invisible to the user except for the change to `scope modules` described
below.

# User-Facing Changes
Breaking change: the output of `scope modules` was changed so that
`env_block` is now `has_env_block` which is a boolean value instead of a
`Block`.

# After Submitting
Update the language guide possibly.
2024-04-21 07:03:33 +02:00
5fd34320e9 add search_term "str extract" to parse command (#12600)
# Description

For a long time, I was searching for the `str extract` command to
extract regexes from strings. I often painfully used `str replace -r
'(.*)(pattern_to_find)(.*)' '$2'` for such purposes.
Only this morning did I realize that `parse` is what I needed for so
many times, which I had only used for parsing data in tables.
2024-04-21 07:01:42 +02:00
5e52bd77e0 Change cal --week-start examples + error message (#12597)
This PR fixes the example for `cal --week-start` and adds the list of
expected values to the error message.
2024-04-20 22:39:46 +02:00
cf8fcef9bf set the type of default NU_LIB_DIRS and NU_PLUGIN_DIRS to list<string> (#12573)
# Description
Fix: #12489

I believe the issue it's because the default value of `NU_LIB_DIRS` and
`NU_PLUGIN_DIRS` is a string, but it should be a list.

So if users don't set up these values in `env.nu`, we will get a
problem.
2024-04-20 10:04:41 -05:00
47867a58df Ab/version details (#12593)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->
Closes #12561

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Add version details in `version`'s output. The intended use is for
third-party tools to be able to quickly check version numbers without
having to the parsing from `(version).version` or `$env.NU_VERSION`.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

This adds 4 new values to the record from `version`:

```
...
│ major              │ 0                                               │
│ minor              │ 92                                              │
│ patch              │ 3                                               │
│ pre                │ a-value                                         │
...
```

`pre` is optional and won't be present most of the time I think.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

I ran the new command using `cargo run -- -c version`:

```
╭────────────────────┬─────────────────────────────────────────────────╮
│ version            │ 0.92.3                                          │
│ major              │ 0                                               │
│ minor              │ 92                                              │
│ patch              │ 3                                               │
│ branch             │                                                 │
│ commit_hash        │                                                 │
│ build_os           │ macos-aarch64                                   │
│ build_target       │ aarch64-apple-darwin                            │
│ rust_version       │ rustc 1.77.2 (25ef9e3d8 2024-04-09)             │
│ rust_channel       │ 1.77.2-aarch64-apple-darwin                     │
│ cargo_version      │ cargo 1.77.2 (e52e36006 2024-03-26)             │
│ build_time         │ 2024-04-20 15:09:36 +02:00                      │
│ build_rust_channel │ release                                         │
│ allocator          │ mimalloc                                        │
│ features           │ default, sqlite, system-clipboard, trash, which │
│ installed_plugins  │                                                 │
╰────────────────────┴─────────────────────────────────────────────────╯
```

# 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.
-->

After this is merged I would like to write docs somewhere for scripts
writer to advise using these members instead of the string values. Where
should I put said docs ?
2024-04-20 10:01:51 -05:00
b274ec19fd improve nu --lsp command tooltips (#12589)
# Description

This PR improves the `nu --lsp` tooltips by using nu code blocks around
the examples and a few other places.

This is what it looks like in Zed.
![Screenshot 2024-04-19 at 8 20
53 PM](https://github.com/nushell/nushell/assets/343840/20d51dcc-f3b2-4f2b-9d43-5817dd3913df)

Here it is in Helix.

![image](https://github.com/nushell/nushell/assets/343840/a9e7d6b9-cd21-4a5a-9c88-9af17a2b2363)

This coloring is far from perfect, but it's what the tree-sitter-nu
queries generate.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-20 07:16:38 -05:00
be5ed3290c add "to nuon" enumeration of possible styles (#12591)
# Description
in order to change the style of the _serialized_ NUON data,
`nuon::to_nuon` takes three mutually exclusive arguments, `raw: bool`,
`tabs: Option<usize>` and `indent: Option<usize>` 🤔
this begs to use an enumeration with all possible alternatives, right?

this PR changes the signature of `nuon::to_nuon` to use `nuon::ToStyle`
which has three variants
- `Raw`: no newlines
- `Tabs(n: usize)`: newlines and `n` tabulations as indent
- `Spaces(n: usize)`: newlines and `n` spaces as indent

# User-Facing Changes
the signature of `nuon::to_nuon` changes from
```rust
to_nuon(
    input: &Value,
    raw: bool,
    tabs: Option<usize>,
    indent: Option<usize>,
    span: Option<Span>,
) -> Result<String, ShellError>
```
to
```rust
to_nuon(
    input: &Value,
    style: ToStyle,
    span: Option<Span>
) -> Result<String, ShellError>
```

# Tests + Formatting

# After Submitting
2024-04-20 11:40:52 +02:00
187b87c61c Don't allow skip on external stream (#12559)
# Description
Close: #12514

# User-Facing Changes
`^ls | skip 1` will raise an error
```nushell
❯ ^ls | skip 1
Error: nu:🐚:only_supports_this_input_type

  × Input type not supported.
   ╭─[entry #1:1:2]
 1 │ ^ls | skip 1
   ·  ─┬   ──┬─
   ·   │     ╰── only list, binary or range input data is supported
   ·   ╰── input type: raw data
   ╰────
```

# Tests + Formatting
Sorry I can't add it because of the issue:
https://github.com/nushell/nushell/issues/12558

# After Submitting
Nan
2024-04-19 14:54:59 +00:00
f2169c8022 Switch plugin msgpack protocol to named format (#12580)
# Description
In conflict with the documentation, the msgpack serializer for plugins
is actually using the compact format, which doesn't name struct fields,
and is instead dependent on their ordering, rendering them as tuples.
This is not a good idea for a robust protocol even if it makes the
serialization and deserialization faster.

I expect this to have some impact on performance, but I think the
robustness is probably worth it.

Deserialization always accepts either format, so this shouldn't cause
too many incompatibilities.

# User-Facing Changes
This does technically change the protocol, but it makes it reflect the
documentation. It shouldn't break deserialization, so plugins shouldn't
necessarily need a recompile.

Performance is likely worse and I should benchmark the difference.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-19 07:50:51 -05:00
9fb59a6f43 Removed the polars dtypes command (#12577)
# Description
The polars dtype command is largerly redundant since the introduction of
the schema command. The schema command also has the added benefit that
it's output can be used as a parameter to other schema commands:

```nushell
[[a b]; [5 6] [5 7]] | polars into-df -s ($df | polars schema
```

# User-Facing Changes
`polars dtypes` has been removed. Users should use `polars schema`
instead.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-19 07:01:47 -05:00
55edef5dda create nuon crate from from nuon and to nuon (#12553)
# Description
playing with the NUON format in Rust code in some plugins, we agreed
with the team it was a great time to create a standalone NUON format to
allow Rust devs to use this Nushell file format.

> **Note**
> this PR almost copy-pastes the code from
`nu_commands/src/formats/from/nuon.rs` and
`nu_commands/src/formats/to/nuon.rs` to `nuon/src/from.rs` and
`nuon/src/to.rs`, with minor tweaks to make then standalone functions,
e.g. remove the rest of the command implementations

### TODO
- [x] add tests
- [x] add documentation

# User-Facing Changes
devs will have access to a new crate, `nuon`, and two functions,
`from_nuon` and `to_nuon`
```rust
from_nuon(
    input: &str,
    span: Option<Span>,
) -> Result<Value, ShellError>
```
```rust
to_nuon(
    input: &Value,
    raw: bool,
    tabs: Option<usize>,
    indent: Option<usize>,
    span: Option<Span>,
) -> Result<String, ShellError>
```

# Tests + Formatting
i've basically taken all the tests from
`crates/nu-command/tests/format_conversions/nuon.rs` and converted them
to use `from_nuon` and `to_nuon` instead of Nushell commands
- i've created a `nuon_end_to_end` to run both conversions with an
optional middle value to check that all is fine

> **Note** 
> the `nuon::tests::read_code_should_fail_rather_than_panic` test does
give different results locally and in the CI...
> i've left it ignored with comments to help future us :)

# After Submitting
mention that in the release notes for sure!!
2024-04-19 13:54:16 +02:00
fac2f43aa4 Add an example Nushell plugin written in Nushell itself (#12574)
# Description

As suggested by @fdncred.

It's neat that this is possible, but the particularly useful part of
this is that we can actually
test it because it doesn't have any external dependencies, unlike the
python plugin.

Right now this just implements exactly the same behavior as the python
plugin, but we could have it
exercise a few more things.

Also fixes a couple of bugs:

- `.nu` plugins were not run with `nu --stdin`, so they couldn't take
input.
- `register` couldn't be called if `--no-config-file` was set, because
it would error on trying to
  update the plugin file.

# User-Facing Changes

- `nu_plugin_nu_example` plugin added.
- `register` now works in `--no-config-file` mode.

# Tests + Formatting
Tests added for `nu_plugin_nu_example`.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Add the version bump to the release script just like for python
2024-04-19 09:53:30 +03:00
6d2cb4382a Fix circular source causing Nushell to crash (#12262)
# Description

EngineState now tracks the script currently running, instead of the
parent directory of the script. This also provides an easy way to expose
the current running script to the user (Issue #12195).

Similarly, StateWorkingSet now tracks scripts instead of directories.
`parsed_module_files` and `currently_parsed_pwd` are merged into one
variable, `scripts`, which acts like a stack for tracking the current
running script (which is on the top of the stack).

Circular import check is added for `source` operations, in addition to
module import. A simple testcase is added for circular source.

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->


<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

It shouldn't have any user facing changes.
2024-04-19 09:38:08 +03:00
351bff8233 fix(shell_integration): set window title on startup (#12569)
Should close #10833 — though I'd imagine that should have already been
closed.

# Description

Very minor tweak, but it was quite noticeable when using Zellij which
relies on OSC 2 to set pane titles. Before the change:

![image](https://github.com/nushell/nushell/assets/6251883/b944bbce-2040-4886-9955-3c5b57d368e9)

Note that the default `Pane #1` is still showing for the untouched
shell, but running a command like `htop` or `ls` correctly sets the
title during / afterwards.

After this PR:

![image](https://github.com/nushell/nushell/assets/6251883/dd513cfe-923c-450f-b0f2-c66938b0d6f0)

There are now no-longer any unset titles — even if the shell hasn't been
touched.

**As an aside:** I feel quite strongly that (at least OSC 2) shell
integration should be enabled by default, as it is for every other Linux
shell I've used, but I'm not sure which issues that caused that the
default config refers to? Which terminals are broken by shell
integration, and could some of the shell integrations be turned on by
default after splitting things into sub-options as suggested in #11301 ?

# User-Facing Changes

You'll just have shell integrations working from right after the shell
has been launched, instead of needing to run something first.

# Tests + Formatting

Not quite sure how to test this one? Are there any other tests that
currently exist for shell integration? I couldn't quite track them
down...

# After Submitting

Let me know if you think this needs any user-facing docs changes!
2024-04-18 20:30:37 -05:00
7fe2e60af7 add ability to set metadata (#12564)
# Description

This PR adds the ability to set metadata. This is especially useful for
activating LS_COLORS when using table literals.


![image](https://github.com/nushell/nushell/assets/343840/feef6433-f592-43ea-890a-38cb2df35686)

You can also set the filepath metadata, although I'm not really user how
useful this is. We may end up removing this option entirely.
```nushell
❯ "crates" | metadata set --datasource-filepath $'(pwd)/crates' | metadata
╭────────┬───────────────────────────────────╮
│ source │ /Users/fdncred/src/nushell/crates │
╰────────┴───────────────────────────────────╯
```

No file paths are checked. You could also do this.
```nushell
❯ "crates" | metadata set --datasource-filepath $'a/b/c/d/crates' | metadata
╭────────┬────────────────╮
│ source │ a/b/c/d/crates │
╰────────┴────────────────╯
```

The command name and parameter names are still WIP. We could change
them.

There are currently 3 kinds of metadata in nushell.
```rust
pub enum DataSource {
    Ls,
    HtmlThemes,
    FilePath(PathBuf),
}
```

I've skipped adding `HtmlThemes` because it seems to be specific to our
`to html` command only.
2024-04-19 09:03:59 +08:00
999dfdf936 Fix the error output in the python plugin to match LabeledError (#12575)
# Description

Forgot to update this after updating the format of `LabeledError`.

# Tests + Formatting

Not applicable to the python plugin.
2024-04-18 20:01:35 -05:00
cc7b5c5a26 Only mark collected dataframes as from_lazy=false when collect is called from the collect command. (#12571)
I had previously changed NuLazyFrame::collect to set the NuDataFrame's
from_lazy field to false to prevent conversion back to a lazy frame. It
appears there are cases where this should happen. Instead, I am only
setting from_lazy=false inside the `polars collect` command.

[Related discord
message](https://discord.com/channels/601130461678272522/1227612017171501136/1230600465159421993)

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-18 17:10:38 -05:00
9a265847e2 Box ImportPattern in Expr (#12568)
# Description
Adds a `Box` around the `ImportPattern` in `Expr` which decreases the
size of `Expr` from 152 to 64 bytes (and `Expression` from 216 to 128
bytes). This seems to speed up parsing a little bit according to the
benchmarks (main is top, PR is bottom):
```
benchmarks                       fastest       │ slowest       │ median        │ mean          │ samples │ iters
benchmarks                       fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ parser_benchmarks                           │               │               │               │         │
├─ parser_benchmarks                           │               │               │               │         │
│  ├─ parse_default_config_file  2.287 ms      │ 4.532 ms      │ 2.311 ms      │ 2.437 ms      │ 100     │ 100
│  ├─ parse_default_config_file  2.255 ms      │ 2.781 ms      │ 2.281 ms      │ 2.312 ms      │ 100     │ 100
│  ╰─ parse_default_env_file     421.8 µs      │ 824.6 µs      │ 494.3 µs      │ 527.5 µs      │ 100     │ 100
│  ╰─ parse_default_env_file     402 µs        │ 486.6 µs      │ 414.8 µs      │ 416.2 µs      │ 100     │ 100

```
2024-04-18 17:57:01 +02:00
b088f395dc Update crate feature flags (#12566)
# Description
Remove unused/effect-less features, make sure we show all relevant
features in `version`

# User-Facing Changes
- **Remove unused feature `wasi`**
- will cause failure to build should you enable it. Otherwise no effect
- **Include feat `system-clipboard` in `version`**
2024-04-18 16:33:41 +02:00
6ccd547d81 Add ListItem type for Expr::List (#12529)
# Description
This PR adds a `ListItem` enum to our set of AST types. It encodes the
two possible expressions inside of list expression: a singular item or a
spread. This is similar to the existing `RecordItem` enum. Adding
`ListItem` allows us to remove the existing `Expr::Spread` case which
was previously used for list spreads. As a consequence, this guarantees
(via the type system) that spreads can only ever occur inside lists,
records, or as command args.

This PR also does a little bit of cleanup in relevant parser code.
2024-04-18 13:21:05 +02:00
57b0c722c6 Upgrading nu-cmd-dataframe to polars 0.39 (#12554)
#Description
Upgrading nu-cmd-dataframe to polars 0.39

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-17 12:50:17 -05:00
13160b3ec3 Replace subtraction of Instants and Durations with saturating subtractions (#12549)
# Description
Duration can not be negative, and an underflow causes a panic.

This should fix #12539 as from what I can tell that bug was caused in
`nu-explore:📟:events` from subtracting durations, but I figured
this might be more widespread, and saturating to zero generally makes
sense.

I also added the relevant clippy lint to try to prevent this from
happening in the future. I can't think of a reason we would ever want to
subtract durations without checking first.

cc @fdncred

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-17 07:25:16 -05:00
410f3c5c8a Upgrading nu_plugin_polars to polars 0.39.1 (#12551)
# Description
Upgrading nu_plugin_polars to polars 0.39.1

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-17 06:35:09 -05:00
b296d6ee3c Improve safety of get_unchecked_str in nu_system::macos (#12550)
# Description
The implementation of this function had a few issues before:

- It didn't check that the `cp` pointer is actually ahead of the `start`
pointer, so `len` could potentially underflow and wrap around, which
would be a violation of memory safety
- It used `Vec::from_raw_parts` even though the buffer is borrowed, not
owned. Although `std::mem::forget` is used later to ensure the
destructor doesn't run, there is a risk that the destructor would run if
a panic happened during `String::from_utf8_unchecked`, which would lead
to a `free()` of a pointer we don't own
2024-04-17 17:10:05 +08:00
9a739d9f0d Bump rmp-serde from 1.1.2 to 1.2.0 (#12547)
Bumps [rmp-serde](https://github.com/3Hren/msgpack-rust) from 1.1.2 to
1.2.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/3Hren/msgpack-rust/commits/rmp-serde/v1.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rmp-serde&package-manager=cargo&previous-version=1.1.2&new-version=1.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-17 16:44:54 +08:00
8cba6678b3 Bump crate-ci/typos from 1.20.7 to 1.20.9 (#12543) 2024-04-17 01:26:46 +00:00
055aae9f5c Impl FusedIterator for record iterators (#12542)
This is good practice as all our iterators will never return a value
after reaching `None`

The benefit should be minimal as only `Iterator::fuse` is directly
specialized and itself rarely used (sometimes in `itertools` adaptors)

Thus it is mostly a documentation thing
2024-04-17 00:34:16 +02:00
a67dad3d15 Minor housekeeping in the parser (#12540)
- **Move lone `check_name` to the alias place**
- **Restrict visibility of `check_call` helper**
2024-04-17 00:33:50 +02:00
43814dcb0f use abbreviated string instead of debug string for DatetimeParseErrors (#12517)
Resolves #12444. Prevents debug string from being printed out.

---------

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2024-04-16 23:19:03 +02:00
cc781a1ecd Make group-by return errors in closure (#12508)
# Description
When a closure if provided to `group-by`, errors that occur in the
closure are currently ignored. That is, `group-by` will fall back and
use the `"error"` key if an error occurs. For example, the code snippet
below will group all `ls` entries under the `"error"` column.
```nushell
ls | group-by { get nope } 
```

This PR changes `group-by` to instead bubble up any errors triggered
inside the closure. In addition, this PR also does some refactoring and
cleanup inside `group-by`.

# User-Facing Changes
Errors are now returned from the closure provided to `group-by` instead
of falling back to the `"error"` group/key.
2024-04-16 21:52:21 +02:00
a7a5ec31be Fixing NuLazyFrame/NuDataFrame conversion issues (#12538)
# Description

@maxim-uvarov brought up another case where converting back and forth
between eager and lazy dataframes was not working correctly:

```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars append -c ([[a b]; [6 2] [1 4] [4 1]] | polars into-df)
Error: nu:🐚:cant_convert

  × Can't convert to NuDataFrame.
   ╭─[entry #1:1:49]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars append -c ([[a b]; [6 2] [1 4] [4 1]] | polars into-df)
   ·                                                 ──────┬──────
   ·                                                       ╰── can't convert NuLazyFrameCustomValue to NuDataFrame
   ╰────
```

This pull request fixes this case and glaringly obvious similar cases I
could find.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-16 11:16:37 -05:00
48e4448e55 Add a panic unwind handler during plugin calls (#12526)
# Description
If a panic happens during a plugin call, because it always happens
outside of the main thread, it currently just hangs Nushell because the
plugin stays running without ever producing a response to the call.

This adds a panic handler that calls `exit(1)` after the unwind finishes
to the plugin runner. The panic error is still printed to stderr as
always, and waiting for the unwind to finish helps to ensure that
anything on the stack with `Drop` behavior that needed to run still
runs, at least on that thread.

# User-Facing Changes
Panics now look like this, which is what they looked like before the
plugin behavior was moved to a separate thread:

```
thread 'plugin runner (primary)' panicked at crates/nu_plugin_example/src/commands/main.rs:45:9:
Test panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: Failed to receive response to plugin call

```

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-16 15:00:32 +00:00
e6fbf7d01d Unify working_set.error usage. (#12531)
# Description
A little refactor that use `working_set.error` rather than
`working_set.parse_errors.push`, which is reported here:
https://github.com/nushell/nushell/pull/12238

> Inconsistent error reporting. Usage of both working_set.error() and
working_set.parse_errors.push(). Using ParseError::Expected for an
invalid variable name when there's ParseError::VariableNotValid (from
parser.rs:5237). Checking variable names manually when there's
is_variable() (from parser.rs:2905).

# User-Facing Changes
NaN

# Tests + Formatting
Done
2024-04-16 15:47:10 +02:00
62555c997e remove useless path.rs (#12534)
# Description
Sorry for introducing a useless file in previous pr, I have renamed it
from `glob.rs` to `path.rs`, but forget removing it.
2024-04-16 06:41:43 -05:00
03317ff92e Python plugin: remove unnecessary fields from signature (#12533)
# Description
Remove a couple of legacy fields (`input_type`, `output_type`), and
`var_id` which is optional and not required for deserialization.

I think until I document this in the plugin protocol ref, most people
will probably be using this example to get started, so it should be as
correct as possible

# After Submitting
- [ ] TODO: document `Signature` in plugin protocol reference
2024-04-16 06:40:04 -05:00
1661bb68f9 Cleaning up to_pipe_line_data and cache_and_to_value, making them part of CustomValueSupport (#12528)
# Description

This is just some cleanup. I moved to_pipeline_data and to_cache_value
to the CustomValueSupport trait, where I should've put them to begin
with.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-16 06:35:52 -05:00
c9e9b138eb Improve with-env robustness (#12523)
# Description
Work for #7149

- **Error `with-env` given uneven count in list form**
- **Fix `with-env` `CantConvert` to record**
- **Error `with-env` when given protected env vars**
- **Deprecate list/table input of vars to `with-env`**
- **Remove examples for deprecated input**

# User-Facing Changes

## Deprecation of the following forms

```
> with-env [MYENV "my env value"] { $env.MYENV }
my env value

> with-env [X Y W Z] { $env.X }
Y

> with-env [[X W]; [Y Z]] { $env.W }
Z
```

## recommended standardized form

```
# Set by key-value record
> with-env {X: "Y", W: "Z"} { [$env.X $env.W] }
╭───┬───╮
│ 0 │ Y │
│ 1 │ Z │
╰───┴───╯
```

## (Side effect) Repeated definitions in an env shorthand are now
disallowed

```
> FOO=bar FOO=baz $env
Error: nu:🐚:column_defined_twice

  × Record field or table column used twice: FOO
   ╭─[entry #1:1:1]
 1 │ FOO=bar FOO=baz $env
   · ─┬─     ─┬─
   ·  │       ╰── field redefined here
   ·  ╰── field first defined here
   ╰────
```
2024-04-16 19:08:58 +08:00
5f818eaefe Ensure that lazy frames converted via to-lazy are not converted back to eager frames later in the pipeline. (#12525)
# Description
@maxim-uvarov discovered the following error:
```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars sort-by a | polars unique --subset [a]
Error:   × Error using as series
   ╭─[entry #1:1:68]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars sort-by a | polars unique --subset [a]
   ·                                                                    ──────┬──────
   ·                                                                          ╰── dataframe has more than one column
   ╰────
 ```
 
During investigation, I discovered the root cause was that the lazy frame was incorrectly converted back to a eager dataframe. In order to keep this from happening, I explicitly set that the dataframe did not come from an eager frame. This causes the conversion logic to not attempt to convert the dataframe later in the pipeline.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-15 18:29:42 -05:00
078ba5aabe Disallow setting the PWD via load-env input (#12522)
# Description
Fixes #12520


# User-Facing Changes
Breaking change:

Any operation parsing input with `PWD` to set the environment will now
fail with `ShellError::AutomaticEnvVarSetManually`

Furthermore transactions containing the special env-vars will be
rejected before executing any modifications. Prevoiusly this was
changing valid variables before while leaving valid variables after the
violation untouched.

## `PWD` handling.

Now failing

```
{PWD: "/trolling"} | load-env
``` 

already failing 

```
load-env {PWD: "/trolling"}
``` 

## Error management



```
> load-env {MY_VAR1: foo, PWD: "/trolling", MY_VAR2: bar}
Error: nu:🐚:automatic_env_var_set_manually

  × PWD cannot be set manually.
   ╭─[entry #1:1:2]
 1 │  load-env {MY_VAR1: foo, PWD: "/trolling", MY_VAR2: bar}
   ·  ────┬───
   ·      ╰── cannot set 'PWD' manually
   ╰────
  help: The environment variable 'PWD' is set automatically by Nushell and cannot be set manually.
```

### Before:
```
> $env.MY_VAR1
foo
> $env.MY_VAR2
Error: nu:🐚:name_not_found
....
```
### After:
```
> $env.MY_VAR1
Error: nu:🐚:name_not_found
....
> $env.MY_VAR2
Error: nu:🐚:name_not_found
....
```

# After Submitting
We need to check if any integrations rely on this hack.
2024-04-15 21:09:58 +02:00
c06ef201b7 Local socket mode and foreground terminal control for plugins (#12448)
# Description

Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.

This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.

This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.

This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.

TODO:

- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
  - Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
  - Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
  - `nu_plugin_stress_internals` added

# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 18:28:18 +00:00
67e7eec7da Add Record::into_columns (#12324)
# Description
Add `Record::into_columns` to complement `Record::columns` and
`Record::into_values`.
2024-04-14 22:43:47 +02:00
af72a18785 Improve error messages for plugin protocol by removing #[serde(untagged)] (#12510)
# Description

In the plugin protocol, I had used `#[serde(untagged)]` on the `Stream`
variant to make it smaller and include all of the stream messages at the
top level, but unfortunately this causes serde to make really unhelpful
errors if anything fails to decode anywhere:

```
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: data did not match any variant of untagged enum PluginOutput
```

If you are trying to develop something using the plugin protocol
directly, this error is incredibly unhelpful. Even as a user, this
basically just says 'something is wrong'. With this change, the errors
are much better:

```
Error: nu:🐚:plugin_failed_to_decode

  × Plugin failed to decode: unknown variant `PipelineDatra`, expected one of `Error`, `Signature`, `Ordering`, `PipelineData` at line 2 column 37
```

The only downside is it means I have to duplicate all of the
`StreamMessage` variants manually, but there's only 4 of them and
they're small.

This doesn't actually change the protocol at all - everything is still
identical on the wire.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-14 15:55:18 +00:00
50b2dac8d7 Set Up Config in LSP Mode (#12454)
# Description

When starting the LSP server, the configuration file and environment
file are used to configure the LSP engine unless --no-config-file is
provided.

This PR provides an improvement that is related to #10794 

CC: @fdncred
2024-04-14 07:32:30 -05:00
2ae9ad8676 Copy-on-write for record values (#12305)
# Description
This adds a `SharedCow` type as a transparent copy-on-write pointer that
clones to unique on mutate.

As an initial test, the `Record` within `Value::Record` is shared.

There are some pretty big wins for performance. I'll post benchmark
results in a comment. The biggest winner is nested access, as that would
have cloned the records for each cell path follow before and it doesn't
have to anymore.

The reusability of the `SharedCow` type is nice and I think it could be
used to clean up the previous work I did with `Arc` in `EngineState`.
It's meant to be a mostly transparent clone-on-write that just clones on
`.to_mut()` or `.into_owned()` if there are actually multiple
references, but avoids cloning if the reference is unique.

# User-Facing Changes
- `Value::Record` field is a different type (plugin authors)

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] use for `EngineState`
- [ ] use for `Value::List`
2024-04-14 01:42:03 +00:00
b508d1028c Fixes #12482 by pointing help links for ndjson to a non-spam source (take 2) (#12509)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

* Fixes #12482
* Initial PR failed due to CI issues at the time. Subsequent rebase
failed, so creating new PR.

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Use https://github.com/ndjson/ndjson-spec for help links instead of
former spam site

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Link changed for `help to ndjson` and `help from ndjson`.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`


# 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-04-13 18:59:43 -05:00
040f10e19d Added a PluginTest method that will call custom_value_to_base_value (#12502)
# Description

Added a method for getting the base value for a PluginCustomValue. 

cc: @devyn

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:03:44 -05:00
10a9a17b8c Two consecutive calls to into-lazy should not fail (#12505)
# Description

From @maxim-uvarov's
[post](https://discord.com/channels/601130461678272522/1227612017171501136/1228656319704203375).

When calling `to-lazy` back to back in a pipeline, an error should not
occur:

```
> [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars into-lazy
Error: nu:🐚:cant_convert

  × Can't convert to NuDataFrame.
   ╭─[entry #1:1:30]
 1 │ [[a b]; [6 2] [1 4] [4 1]] | polars into-lazy | polars into-lazy
   ·                              ────────┬───────
   ·                                      ╰── can't convert NuLazyFrameCustomValue to NuDataFrame
   ╰────
 ```

This pull request ensures that custom value's of NuLazyFrameCustomValue are properly converted when passed in.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:00:46 -05:00
b9dd47ebb7 Polars 0.38 upgrade (#12506)
# Description
Polars 0.38 upgrade for both the dataframe crate and the polars plugin.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 13:00:04 -05:00
211d9c685c Fix clippy lint (#12504)
Just fixes a clippy lint.
2024-04-13 16:19:32 +00:00
0110345755 making ls and du supports rest parameters. (#12327)
# Description
Close: #12147
Close: #11796 

About the change: it make pattern handling into a function:
`ls_for_one_pattern`(for ls), `du_for_one_pattern`(for du). Then
iterates on user input pattern, call these core function, and chaining
these iterator to one pipelinedata.
2024-04-13 15:03:17 +00:00
56cdee1fd8 Refactor first and last (#12478)
# Description

- Refactors `first` and `last` using `Vec::truncate` and `Vec::drain`.
- `std::mem::take` was also used to eliminate a few `Value` clones.
- The `NeedsPositiveValue` error now uses the span of the `rows`
argument instead of the call head span.
- `last` now errors on an empty stream to match `first` which does
error.
-  Made metadata preservation more consistent.

# User-Facing Changes
Breaking change: `last` now errors on an empty stream to match `first`
which does error.
2024-04-13 14:58:54 +00:00
1bded8572c Ensure that two columns named index don't exist when converting a Dataframe to a nu Value. (#12501)
# Description
@maxim-uvarov discovered an issue with the current implementation. When
executing [[index a]; [1 1]] | polars into-df, a plugin_failed_to_decode
error occurs. This happens because a Record is created with two columns
named "index" as an index column is added during conversion. This pull
request addresses the problem by not adding an index column if there is
already a column named "index" in the dataframe.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-13 06:33:29 -05:00
f975c9923a Handle relative paths correctly on polars to-(parquet|jsonl|arrow|etc) commands (#12486)
# Description

All polars commands that output a file were not handling relative paths
correctly.

A command like
``` [[a b]; [6 2] [1 4] [4 1]] | polars into-df | polars to-parquet foo.json``` 
was outputting the foo.json to the directory of the plugin executable. 

This pull request pulls in nu-path and using it for resolving the file paths.

Related discussion
https://discord.com/channels/601130461678272522/1227612017171501136/1227889870358183966

# User-Facing Changes
None

# Tests + Formatting
Done, added tests for each of the polars to-* commands.

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 19:30:37 -05:00
b7fb0af967 bump nushell to latest reedline (#12497)
# Description

This bumps nushell to the latest reedline main brach which includes the
new bashism !term.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-12 16:56:40 -04:00
3a11b9d69d Also use old mac workers for plugins job (#12495)
Repeat from #12493

Now we see failures on main
2024-04-12 15:43:36 -04:00
50fb8243c8 Added a short flag -c to polars append --col (#12487)
# Description
`dfr append --col` had a short version -c. This polar requests adds the
short flag back.

Reference Conversation:
https://discord.com/channels/601130461678272522/1227612017171501136/1227902980628676688

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 10:55:36 -05:00
741e3c3d8f Return value instead of stream from kill (#12480)
# Description
The `kill` command returns a stream with a single value. This PR changes
it to simply return the value.

# User-Facing Changes
Technically a breaking change.
2024-04-12 10:44:27 -05:00
3eb9c2a565 drop refactor (#12479)
# Description
Refactors `drop` using `Vec::truncate` and adds a `NeedsPositiveValue`
error.

# User-Facing Changes
Breaking change: `drop` now errors if the number of rows/columns is
negative.
2024-04-12 10:44:00 -05:00
de41345bf2 better logging for shell_integration ansi escapes + better plugin perf logging (#12494)
# Description

This PR just adds better logging for shell_integration and tweaks the
ansi escapes so they're closer to where the action happens. I also added
some perf log entries to help better understand plugin file load and
eval performance.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-12 10:11:41 -05:00
b9c2f9ee56 displaying span information, creation time, and size with polars ls (#12472)
# Description
`polars ls` is already different that `dfr ls`. Currently it just shows
the cache key, columns, rows, and type. I have added:
- creation time
- size
- span contents
-  span start and end

<img width="1471" alt="Screenshot 2024-04-10 at 17 27 06"
src="https://github.com/nushell/nushell/assets/56345/545918b7-7c96-4c25-bc01-b9e2b659a408">

# Tests + Formatting
Done

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-12 09:23:46 -05:00
872945ae8e Bump version to 0.92.3 (#12476) 2024-04-12 08:00:43 -05:00
cd8f04041c Switch the CI to use macos-13 instead of macos-latest, due to failures (#12493)
# Description

The CI has been failing lately on macOS, most likely because it's
running out of memory now that
the `polars` plugin has been included and it's quite large on top of
`dfr`.

This PR just makes use use the `macos-13` runner instead of
`macos-latest` because the latter has
only half as much RAM because it's running on the Apple M1 platform.

---------

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2024-04-12 13:37:35 +02:00
3a39e3df22 Bump our Rust version to stable (#12471)
This was prompted by CVE-2024-24576

- https://nvd.nist.gov/vuln/detail/CVE-2024-24576
- https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html
-
https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/

Affected is launching commands on Windows with arbitrary arguments,
which is the case for Nushell's external invocation on Windows

Rust has fixed this quoting vulnerability in 1.77.2 (latest stable at
time of commit)

We will thus use this version for our builds and recommend all our
packaging/distribution maintainers to use this version of Rust when
building Nushell.
2024-04-10 23:41:38 +02:00
39156930f5 fix std log (#12470)
related to
- https://github.com/nushell/nushell/pull/12196

# Description
while i'm 100% okey with the original intent behind
https://github.com/nushell/nushell/pull/12196, i think the PR did
introduce two unintended things:
- extra parentheses that make the `log.nu` module look like Lisp lol
- a renaming of the `NU_LOG_LEVEL` environment variable to
`NU_log-level`. this breaks previous usage of `std log` and, as it's not
mentionned at all in the PR, i thought it was not intentional 😋

# User-Facing Changes
users can now control `std log` with `$env.NU_LOG_LEVEL`

# Tests + Formatting
the "log" tests have been fixed as well.

# After Submitting
2024-04-10 17:30:58 -04:00
83674909f1 Lex whitespace in input-output types. (#12339)
# Description
Fixes #12264.

# User-Facing Changes


Multiple input-output types can break across lines like command params.

# Tests + Formatting

 E2E parser tests
2024-04-10 16:28:54 +02:00
18ddf95d44 Force timeit to not capture stdout (#12465)
# Description
Fixes:  #11996

After this change `let t = timeit ^ls` will list current directory to
stdout.
```
❯ let t = timeit ^ls
CODE_OF_CONDUCT.md      Cargo.lock              Cross.toml              README.md               aaa                     benches                 devdocs                 here11                  scripts                 target                  toolkit.nu              wix
CONTRIBUTING.md         Cargo.toml              LICENSE                 a.txt                   assets                  crates                  docker                  rust-toolchain.toml     src                     tests                   typos.toml
```

If user don't want such behavior, he can redirect the stdout to `std
null-stream` easily
```
> use std
> let t = timeit { ^ls o> (std null-device) }
```

# User-Facing Changes
NaN

# Tests + Formatting
Done

# After Submitting
Nan

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-04-10 13:31:29 +00:00
81c61f3243 Showing full help when running the polars command (#12462)
Displays the full help message for all sub commands.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-10 07:26:33 -05:00
efc1cfa939 Move dataframes support to a plugin (#12220)
WIP

This PR covers migration crates/nu-cmd-dataframes to a new plugin
./crates/nu_plugin_polars

## TODO List

Other:
- [X] Fix examples
- [x] Fix Plugin Test Harness
- [X] Move Cache to Mutex<BTreeMap>
- [X] Logic for disabling/enabling plugin GC based off whether items are
cached.
- [x] NuExpression custom values
- [X] Optimize caching (don't cache every object creation). 
- [x] Fix dataframe operations (in NuDataFrameCustomValue::operations)
- [x] Added plugin_debug! macro that for checking an env variable
POLARS_PLUGIN_DEBUG

Fix duplicated commands:
- [x] There are two polars median commands, one for lazy and one for
expr.. there should only be one that works for both. I temporarily
called on polars expr-median (inside expressions_macros.rs)
- [x] polars quantile (lazy, and expr). the expr one is temporarily
expr-median
- [x] polars is-in (renamed one series-is-in)

Commands:
- [x] AppendDF
- [x] CastDF
- [X] ColumnsDF
- [x] DataTypes
- [x] Summary
- [x] DropDF
- [x] DropDuplicates
- [x] DropNulls
- [x] Dummies
- [x] FilterWith
- [X] FirstDF
- [x] GetDF
- [x] LastDF
- [X] ListDF
- [x] MeltDF
- [X] OpenDataFrame
- [x] QueryDf
- [x] RenameDF
- [x] SampleDF
- [x] SchemaDF
- [x] ShapeDF
- [x] SliceDF
- [x] TakeDF
- [X] ToArrow
- [x] ToAvro
- [X] ToCSV
- [X] ToDataFrame
- [X] ToNu
- [x] ToParquet
- [x] ToJsonLines
- [x] WithColumn
- [x] ExprAlias
- [x] ExprArgWhere
- [x] ExprCol
- [x] ExprConcatStr
- [x] ExprCount
- [x] ExprLit
- [x] ExprWhen
- [x] ExprOtherwise
- [x] ExprQuantile
- [x] ExprList
- [x] ExprAggGroups
- [x] ExprCount
- [x] ExprIsIn
- [x] ExprNot
- [x] ExprMax
- [x] ExprMin
- [x] ExprSum
- [x] ExprMean
- [x] ExprMedian
- [x] ExprStd
- [x] ExprVar
- [x] ExprDatePart
- [X] LazyAggregate
- [x] LazyCache
- [X] LazyCollect
- [x] LazyFetch
- [x] LazyFillNA
- [x] LazyFillNull
- [x] LazyFilter
- [x] LazyJoin
- [x] LazyQuantile
- [x] LazyMedian
- [x] LazyReverse
- [x] LazySelect
- [x] LazySortBy
- [x] ToLazyFrame
- [x] ToLazyGroupBy
- [x] LazyExplode
- [x] LazyFlatten
- [x] AllFalse
- [x] AllTrue
- [x] ArgMax
- [x] ArgMin
- [x] ArgSort
- [x] ArgTrue
- [x] ArgUnique
- [x] AsDate
- [x] AsDateTime
- [x] Concatenate
- [x] Contains
- [x] Cumulative
- [x] GetDay
- [x] GetHour
- [x] GetMinute
- [x] GetMonth
- [x] GetNanosecond
- [x] GetOrdinal
- [x] GetSecond
- [x] GetWeek
- [x] GetWeekDay
- [x] GetYear
- [x] IsDuplicated
- [x] IsIn
- [x] IsNotNull
- [x] IsNull
- [x] IsUnique
- [x] NNull
- [x] NUnique
- [x] NotSeries
- [x] Replace
- [x] ReplaceAll
- [x] Rolling
- [x] SetSeries
- [x] SetWithIndex
- [x] Shift
- [x] StrLengths
- [x] StrSlice
- [x] StrFTime
- [x] ToLowerCase
- [x] ToUpperCase
- [x] Unique
- [x] ValueCount

---------

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-09 19:31:43 -05:00
cbbccaa722 Bump crate-ci/typos from 1.20.3 to 1.20.7 (#12456) 2024-04-10 00:28:30 +00:00
d735607ac8 Isolate tests from user config (#12437)
# Description
This is an attempt to isolate the unit tests from whatever might be in
the user's config. If the
user's config is broken in some way or incompatible with this version
(for example, especially if
there are plugins that aren't built for this version), tests can
spuriously fail.

This makes tests more reliably pass the same way they would on CI even
if the user has config, and
should also make them run faster.

I think this is _good enough_, but I still think we should have a
specific config dir env variable for nushell specifically (rather than
having to use `XDG_CONFIG_HOME`, which would mess with other things) and
then we can just have `nu-test-support` set that to a temporary dir
containing the shipped default config files.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-10 06:27:46 +08:00
d7ba8872bf Rename IoStream to OutDest (#12433)
# Description
I spent a while trying to come up with a good name for what is currently
`IoStream`. Looking back, this name is not the best, because it:
1. Implies that it is a stream, when it all it really does is specify
the output destination for a stream/pipeline.
2. Implies that it handles input and output, when it really only handles
output.

So, this PR renames `IoStream` to `OutDest` instead, which should be
more clear.
2024-04-09 16:48:32 +00:00
6536fa5ff7 Ensure currently_parsed_cwd is set for config files (#12338)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Fixes #7849, #11465 based on @kubouch's suggestion in
https://github.com/nushell/nushell/issues/11465#issuecomment-1883847806.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Can source files relative to `env.nu` or `config.nu` like in #6150.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

Adds test that previously failed.

# 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-04-09 10:06:41 -04:00
14b0ff3f05 Add --no-newline option to nu (#12410)
# Description
I have `nu` set as my shell in my editor, which allows me to easily pipe
selections of text to things like `str pascal-case` or even more complex
string operation pipelines, which I find super handy. However, the only
annoying thing is that I pretty much always have to add `| print -n` at
the end, because `nu` adds a newline when it prints the resulting value.

This adds a `--no-newline` option to stop that from happening, and then
you don't need to pipe to `print -n` anymore, you can just have your
shell command for your editor contain that flag.

# User-Facing Changes
- Add `--no-newline` command line option

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-09 10:04:00 -04:00
00b3a07efe Add GetSpanContents engine call (#12439)
# Description
This allows plugins to view the source code of spans.

Requested by @ayax79 for implementing `polars ls`. Note that this won't
really help you find the location of the span. I'm planning to add
another engine call that will return information more similar to what
shows up in the miette diagnostics, with filename / line number / some
context, but I'll want to refactor some of the existing logic to make
that happen, so it was easier to just do this first. I hope this is
enough to at least have something somewhat useful show up for `polars
ls`.

# User-Facing Changes
- Example plugin: added `example view span` command

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Add to plugin protocol reference
2024-04-09 10:02:17 -04:00
9a2a6ab52c Update MSRV following rust-toolchain.toml (#12455)
Also update the `rust-version` in `Cargo.toml` following the update to
`rust-toolchain.toml` in #12258

# Testing

Added a CI check to verify any future PRs trying to update one will also
have to update the other. (using `std-lib-and-python-virtualenv` job as
this already includes a fresh `nu` binary for a little toml munching
script)
2024-04-09 08:25:45 -04:00
40f72e80c3 try to be a bit more precise with repl logging (#12449)
# Description

This PR tries to be a bit more precise with the repl logging when
starting nushell with `nu --log-level debug`. It adds a few more `perf`
lines and changes some of the text of others.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-08 05:36:54 -05:00
56c9587cc3 Bump similar from 2.4.0 to 2.5.0 (#12375) 2024-04-07 21:50:11 +00:00
625a9144cf Bump h2 from 0.3.24 to 0.3.26 (#12413) 2024-04-07 21:48:18 +00:00
773dafa8ac Fix negative value file size for "into filesize" (issue #12396) (#12443)
# Description
Add support for using negative values file size for `into filesize`.
This will help in sorting the file size if negative values are also
passed.

**Before**

![image](https://github.com/nushell/nushell/assets/43441496/e115b4b3-7526-4379-8dc0-f4f4e44839a1)
**After**

![image](https://github.com/nushell/nushell/assets/43441496/4a75fb40-ebe6-46eb-b9d2-55f37db7a6fa)

# User-Facing Changes
- User can now sort negative filesize also

# Tests + Formatting
- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting

---------

Co-authored-by: Priyank Singh <priyank.singh@soroco.com>
2024-04-07 16:50:11 +00:00
e234f3ea7b Fix typo in help stor import (#12442)
Changed `export` for `import`

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
`help stor import` showed a help string that was probably copy-pasted
from `stor export`
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

# User-Facing Changes
Now `help stor import` shows a correct description of the operation that
it is doing
<!-- 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-07 08:09:24 -05:00
67c8b0db69 Mention print in the echo help text (#12436)
# Description
Edits the `echo` help text to mention the `print` command.

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-04-06 20:24:00 -05:00
6b4cbe7a98 explain refactor (#12432)
# Description
Refactors the `explain` command. Part of this is fixing the `type`
column which used to always be `string` for each row.
2024-04-06 18:55:22 -05:00
70520000d2 Use nu-cmd-lang default context for plugin tests (#12434)
# Description
@ayax79 added `nu-cmd-lang` as a dep for `nu-plugin-test-support` in
order to get access to `let`. Since we have the dep anyway now, we might
as well just add all of the lang commands - there aren't very many of
them and it would be less confusing than only `let` working.

# User-Facing Changes
- Can use some more core nu language features in plugin tests, like
loops and `do`

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Might need to change something about the plugin testing section of
the book, since I think it says something about there only being the
plugin command itself available
2024-04-06 23:28:08 +00:00
01c79bbefc Implement De-/Serialize for Record manually (#12365)
# Description
This decouples the serialized representation of `Record` from its
internal implementation. It now gets treated as a map type in `serde`.

This has several benefits:
- more efficient representation (not showing inner fields)
- human readable e.g. as a JSON record
- no breaking changes when refactoring the `Record` internals in the
future (see #12326, or potential introduction of `indexmap::IndexMap`
for large N)
- we now deny the creation of invalid records a non-cooperating plugin
could produce
  - guaranteed key-value correspondence
  - checking for unique keys

# Breaking change to the plugin protocol:
Now expects a record/map directly as the `Record.val` field instead of a
serialization of it.
2024-04-07 07:21:03 +08:00
db1dccc762 Don't check if stderr empty in test_xdg_config_symlink (#12435)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Fixes a problem introduced in
https://github.com/nushell/nushell/pull/12420 where one of the config
path tests (`test_xdg_config_symlink`) fails if, on MacOS, a developer
already has config files in the default location for that platform.

This happened because the test is making sure the
`xdg_config_home_invalid` error isn't reported, but to do that, it
asserts that stderr is empty, which it is not if in the case mentioned
above, because Nushell warns that the default location
(`~/.config`/`~/Library/Application Support`) is not empty but
`XDG_CONFIG_HOME` is empty.

If someone with a Mac could test this, that'd be great.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

None, this is for contributors.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-06 18:01:16 -04:00
1c5bd21ebc Added let command to PluginTest (#12431)
# Description
The `let` command is needed for many example tests. This pull request
adds the `let` command to the EngineState of Test Plugin.

cc: @devyn 

# User-Facing Changes
No user changes. Plugin tests can now have examples with the let
keyword.

Co-authored-by: Jack Wright <jack.wright@disqo.com>
2024-04-06 20:11:41 +00:00
fe99729cdb Prevent panic on date overflow (#12427)
# Description
Fixes #12095 where date math using `chrono` can panic on overflow. It
looks like there's only one place that needed fixing.
2024-04-06 15:16:49 +00:00
03667bdf8c Fix merging child stack into parent (#12426)
# Description
Fixes #12423 where changes to mutable variables are not properly
persisted after a REPL entry.
2024-04-06 15:03:22 +00:00
eb36dbb091 Fix #12416 by canonicalizing XDG_CONFIG_HOME before comparing to config_dir() (#12420)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Fixes #12416. Turns out that in src/main.rs, `XDG_CONFIG_HOME` wasn't
being canonicalized before being compared to `nu_path::config_dir()` to
check if `XDG_CONFIG_HOME` was set to an invalid value. This has been
rectified now.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Setting `XDG_CONFIG_HOME` to a symlink should work now.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

I manually tested it and the error has disappeared:

New behavior (this branch):

![image](https://github.com/nushell/nushell/assets/45539777/062d1cc5-551c-431c-b138-d3da8de018bd)

Old behavior (main):

![image](https://github.com/nushell/nushell/assets/45539777/22c4b5a3-3fd0-4ab6-9cf0-ae25488645ba)

Thanks to a pointer from Devyn, I've now added tests to make sure the
`xdg_config_home_invalid` error doesn't pop up when `XDG_CONFIG_HOME` is
a symlink (and does when it's actually invalid).

Turns out two of the tests in `test_config_path` tried modifying
`XDG_CONFIG_HOME` using `playground.with_env` but used `nu!`, so the
subprocess didn't actually use the modified value of `XDG_CONFIG_HOME`.
When I added them, I was unaware that the `.with_env` didn't actually
modify the environment. This has now been rectified.

# 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-04-06 09:09:03 -05:00
e211e96d33 Fix nushell#10591: encode returns error with utf-16le and utf-16be encodings (nushell#10591) (#12411)
# Description

This closes (nushell#10591)

The Command encode's help text says that utf-16le and utf-16be encodings
are not supported, however you could still use these encodings and they
didn't work properly, since they returned the bytes UTF-8 encoded:
```bash
"䆺ש" | encode utf-16
Length: 5 (0x5) bytes | printable whitespace ascii_other non_ascii
00000000: e4 86 ba d7 a9 ×××××
 ```
# User-Facing Changes

The Command encode's help text was updated and now when trying to encode with utf-16le and utf-16be returns an error:
![screenshot](https://github.com/nushell/nushell/assets/119532691/c346dc57-8b42-4dfc-93d5-638b0041d89f)

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 09:07:55 -05:00
7a7d43344e Range refactor (#12405)
# Description
Currently, `Range` is a struct with a `from`, `to`, and `incr` field,
which are all type `Value`. This PR changes `Range` to be an enum over
`IntRange` and `FloatRange` for better type safety / stronger compile
time guarantees.

Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774
Fixes: #11773 Fixes: #11769.

# User-Facing Changes
Hopefully none, besides bug fixes.

Although, the `serde` representation might have changed.
2024-04-06 09:04:56 -05:00
75fedcc8dd prevent select (negative number) from hanging shell (#12393)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
Resolves #11756.
Resolves #12346. 

As per description, shell no longer hangs:
```
~/CodingProjects/nushell> [1 2 3] | select (-2) 
Error: nu:🐚:cant_convert

  × Can't convert to cell path.
   ╭─[entry #1:1:18]
 1 │ [1 2 3] | select (-2)
   ·                  ──┬─
   ·                    ╰── can't convert negative number to cell path
   ╰────
```


<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->

Added relevant test 🚀 

# 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.
-->

Possibly support `get` `get`ting negative numbers, as per #12346
discussion. Alternatively, we can consider adding a cellpath for
negative indexing?
2024-04-06 09:03:05 -05:00
ed4927441f Bump rust-embed from 8.2.0 to 8.3.0 (#12374) 2024-04-06 13:59:01 +00:00
6fba5f5ec7 Bump shadow-rs from 0.26.1 to 0.27.1 (#12372) 2024-04-06 13:58:16 +00:00
12b897b149 Make auto-cd check for permissions (#12342)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
I was playing around with auto-cd and realised it didn't check for
permissions before cd'ing. This PR fixes that.

```
~/CodingProjects/nushell> /root                                                                           
Error: nu:🐚:io_error

  × I/O error
  help: Cannot change directory to /root: You are neither the owner, in the group, nor the super user and do not have permission
```

This PR also refactors some of the filesystem utilities to nu-utils,
specifically the permissions checking and users.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-06 08:56:46 -05:00
16cbed7d6e Fix some of the tests in tests::shell (#12417)
# Description
Some of the tests in `tests::shell` were using `sh` unnecessarily, and
had `#[cfg(not(windows))]` when they should be testable on Windows if
`sh` is not used.

I also found that they were using `.expect()` incorrectly, under the
assumption that that would check their output, when really an
`assert_eq!` on the output is needed to do that. So these tests weren't
even really working properly before.

# User-Facing Changes
None

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 11:57:47 +08:00
0e36c43c64 Add BufWriter to ChildStdin on the plugin interface (#12419)
# Description
This speeds up writing messages to the plugin, because otherwise every
individual piece of the messages (not even the entire message) is
written with one syscall, leading to a lot of back and forth with the
kernel.

I learned this by running `strace` to debug something and saw a ton of
`write()` calls.

```nushell
# Before
1..10 | each { timeit { example seq 1 10000 | example sum } } | math avg
269ms 779µs 149ns
# After
> 1..10 | each { timeit { example seq 1 10000 | example sum } } | math avg
39ms 636µs 643ns
```

# User-Facing Changes
- Performance improvement

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-06 11:52:27 +08:00
2562e306b6 Improve handling of custom values in plugin examples (#12409)
# Description
Requested by @ayax79. This makes the custom value behavior more correct,
by calling the methods on the plugin to handle the custom values in
examples rather than the methods on the custom values themselves. This
helps for handle-type custom values (like what he's doing with
dataframes).

- Equality checking in `PluginTest::test_examples()` changed to use
`PluginInterface::custom_value_partial_cmp()`
- Base value rendering for `PluginSignature` changed to use
`Plugin::custom_value_to_base_value()`
- Had to be moved closer to `serve_plugin` for this reason, so the test
for writing signatures containing custom values was removed
- That behavior should still be tested to some degree, since if custom
values are not handled, signatures will fail to parse, so all of the
other tests won't work.

# User-Facing Changes

- `Record::sort_cols()` method added to share functionality required by
`PartialCmp`, and it might also be slightly faster
- Otherwise, everything should mostly be the same but better. Plugins
that don't implement special handling for custom values will still work
the same way, because the default implementation is just a pass-through
to the `CustomValue` methods.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-05 21:57:20 -05:00
c82dfce246 Fix deadlock on PluginCustomValue drop (#12418)
# Description
Because the plugin interface reader thread can be responsible for
sending a drop notification, it's possible for it to end up in a
deadlock where it's waiting for the response to the drop notification
call.

I decided that the best way to address this is to just discard the
response and not wait for it. It's not really important to synchronize
with the response to `Dropped`, so this is probably faster anyway.

cc @ayax79, this is your issue where polars is getting stuck

# User-Facing Changes
- A bug fix
- Custom value plugin: `custom-value handle update` command

# Tests + Formatting

Tried to add a test with a long pipeline with a lot of drops and run it
over and over to reproduce the deadlock.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2024-04-05 21:57:00 -05:00
82b7548c0c Tweak release workflow after 0.92.1 lessons (#12401)
Encountered repeated build failure that vanished after clearing the
existing build caches.

- Don't `fail-fast` (this is highly annoying, if a severe problem is
detected, there is still the possibility to intervene manually)
- Don't cache the build process, we do it rarely so any potential
speed-up is offset by the uncertainty if this affects the artefact
2024-04-06 10:20:01 +08:00
0884d1a5ce Fix testing.nu import of std log (#12392)
# Description

`use std/log.nu` does not work, have to `use std log`

# User-Facing Changes

Fix the testing script. Bug fix.
2024-04-05 20:29:19 -05:00
00b576b7f1 Fix stop suggesting --trash when already enabled (issue #12361) (#12362)
fixes #12361

Looking at the condition, `TRASH_SUPPORTED && (trash || (rm_always_trash
&& !permanent))`, this code path seems only to run when `--trash` is
enabled and `--permanent` is disabled.

This suggests that the `--trash` suggestion is a mistake and should have
suggested `--permanent`.
2024-04-05 20:28:40 -05:00
88ff622b16 Make view source more robust (#12359)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Resolves #11800.

```
~/CodingProjects/nushell> def "url expand" [$urls:any = []]: [string -> string, list -> table] {    
:::   let urls = ($in | default $urls)
:::   def expand-link [] {
:::     http head --redirect-mode manual $in | where name == location | get value.0
:::   }
:::   match ($urls | describe) {
:::     string => { $urls | expand-link }
:::     $type if ($type =~ list) => { $urls | wrap link | insert expanded {|url| $url.link | expand-link}}
:::   }
::: }; view source "url expand"
def "url expand" [ $urls: any = [] ]: [string -> string, list<any> -> table] {
  let urls = ($in | default $urls)
  def expand-link [] {
    http head --redirect-mode manual $in | where name == location | get value.0
  }
  match ($urls | describe) {
    string => { $urls | expand-link }
    $type if ($type =~ list) => { $urls | wrap link | insert expanded {|url| $url.link | expand-link}}
  }
}
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

`view source` now 
- adds quotes to commands with spaces
- shows default argument values
- shows type signatures

# 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 std testing; testing run-tests --path
crates/nu-std"` 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-04-05 20:28:15 -05:00
394487b3a7 Bump version to 0.92.2 (#12402) 2024-04-05 10:24:00 -04:00
acc3ca9de7 Update list of supported formats in dfr open error message. (#12408)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
The error message when using `dfr open --type` shows an outdated list of
supported formats.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
User is now informed that jsonl and avro formats are supported.

# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->
Done.

# 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.
-->
No doc changes.
2024-04-05 06:47:08 -05:00
694 changed files with 39644 additions and 10818 deletions

View File

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

12
.github/workflows/check-msrv.nu vendored Normal file
View File

@ -0,0 +1,12 @@
let toolchain_spec = open rust-toolchain.toml | get toolchain.channel
let msrv_spec = open Cargo.toml | get package.rust-version
# This check is conservative in the sense that we use `rust-toolchain.toml`'s
# override to ensure that this is the upper-bound for the minimum supported
# rust version
if $toolchain_spec != $msrv_spec {
print -e "Mismatching rust compiler versions specified in `Cargo.toml` and `rust-toolchain.toml`"
print -e $"Cargo.toml: ($msrv_spec)"
print -e $"rust-toolchain.toml: ($toolchain_spec)"
exit 1
}

View File

@ -10,7 +10,7 @@ env:
NUSHELL_CARGO_PROFILE: ci
NU_LOG_LEVEL: DEBUG
# If changing these settings also change toolkit.nu
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -D clippy::unchecked_duration_subtraction"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
@ -24,7 +24,11 @@ jobs:
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
# revisiting this when 20.04 is closer to EOL (April 2025)
platform: [windows-latest, macos-latest, ubuntu-20.04]
#
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
# removed and we're only building the `polars` plugin instead
platform: [windows-latest, macos-13, ubuntu-20.04]
feature: [default, dataframe]
include:
- feature: default
@ -34,13 +38,13 @@ jobs:
exclude:
- platform: windows-latest
feature: dataframe
- platform: macos-latest
- platform: macos-13
feature: dataframe
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
@ -85,7 +89,7 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
@ -117,7 +121,7 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
@ -130,6 +134,9 @@ jobs:
- name: Standard library tests
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
- name: Ensure that Cargo.toml MSRV and rust-toolchain.toml use the same version
run: nu .github/workflows/check-msrv.nu
- name: Setup Python
uses: actions/setup-python@v5
with:
@ -158,12 +165,16 @@ jobs:
strategy:
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04]
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
# instead of 14 GB) which is too little for us right now.
#
# Failure occuring with clippy for rust 1.77.2
platform: [windows-latest, macos-13, ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0

View File

@ -27,7 +27,7 @@ jobs:
# if: github.repository == 'nushell/nightly'
steps:
- name: Checkout
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.3
if: github.repository == 'nushell/nightly'
with:
ref: main
@ -36,7 +36,7 @@ jobs:
token: ${{ secrets.WORKFLOW_TOKEN }}
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
if: github.repository == 'nushell/nightly'
with:
version: 0.91.0
@ -123,7 +123,7 @@ jobs:
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
with:
ref: main
fetch-depth: 0
@ -139,7 +139,7 @@ jobs:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
@ -235,7 +235,7 @@ jobs:
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
with:
ref: main
fetch-depth: 0
@ -251,7 +251,7 @@ jobs:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
@ -310,12 +310,12 @@ jobs:
- name: Waiting for Release
run: sleep 1800
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
with:
ref: main
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0

View File

@ -134,9 +134,15 @@ print $'(char nl)All executable files:'; hr-line
print (ls -f ($executable | into glob)); sleep 1sec
print $'(char nl)Copying release files...'; hr-line
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
"To use the included Nushell plugins, register the binaries with the `plugin add` command to tell Nu where to find the plugin.
Then you can use `plugin use` to load the plugin into your session.
For example:
> register ./nu_plugin_query" | save $'($dist)/README.txt' -f
> plugin add ./nu_plugin_query
> plugin use query
For more information, refer to https://www.nushell.sh/book/plugins.html
" | save $'($dist)/README.txt' -f
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
print $'(char nl)Check binary release version detail:'; hr-line

View File

@ -18,6 +18,7 @@ jobs:
name: Std
strategy:
fail-fast: false
matrix:
target:
- aarch64-apple-darwin
@ -72,20 +73,21 @@ jobs:
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0
@ -161,20 +163,21 @@ jobs:
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4.1.2
- uses: actions/checkout@v4.1.3
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3.9
uses: hustcer/setup-nu@v3.10
with:
version: 0.91.0

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.3
- name: Check spelling
uses: crate-ci/typos@v1.20.3
uses: crate-ci/typos@v1.20.10

1444
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.74.1"
version = "0.92.1"
rust-version = "1.77.2"
version = "0.93.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -31,6 +31,7 @@ members = [
"crates/nu-cmd-base",
"crates/nu-cmd-extra",
"crates/nu-cmd-lang",
"crates/nu-cmd-plugin",
"crates/nu-cmd-dataframe",
"crates/nu-command",
"crates/nu-color-config",
@ -40,6 +41,9 @@ members = [
"crates/nu-pretty-hex",
"crates/nu-protocol",
"crates/nu-plugin",
"crates/nu-plugin-core",
"crates/nu-plugin-engine",
"crates/nu-plugin-protocol",
"crates/nu-plugin-test-support",
"crates/nu_plugin_inc",
"crates/nu_plugin_gstat",
@ -47,11 +51,14 @@ members = [
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu_plugin_formats",
"crates/nu_plugin_polars",
"crates/nu_plugin_stress_internals",
"crates/nu-std",
"crates/nu-table",
"crates/nu-term-grid",
"crates/nu-test-support",
"crates/nu-utils",
"crates/nuon",
]
[workspace.dependencies]
@ -59,6 +66,7 @@ alphanumeric-sort = "1.5"
ansi-str = "0.8"
base64 = "0.22"
bracoxide = "0.1.2"
brotli = "5.0"
byteorder = "1.5"
bytesize = "1.3"
calamine = "0.24.0"
@ -85,6 +93,7 @@ heck = "0.5.0"
human-date-parser = "0.1.1"
indexmap = "2.2"
indicatif = "0.17"
interprocess = "1.2.1"
is_executable = "1.0"
itertools = "0.12"
libc = "0.2"
@ -112,6 +121,7 @@ open = "5.1"
os_pipe = "1.1"
pathdiff = "0.2"
percent-encoding = "2"
pretty_assertions = "1.4"
print-positions = "0.6"
procfs = "0.16.0"
pwd = "1.3"
@ -121,13 +131,15 @@ quickcheck_macros = "1.0"
rand = "0.8"
ratatui = "0.26"
rayon = "1.10"
reedline = "0.31.0"
reedline = "0.32.0"
regex = "1.9.5"
rmp = "0.8"
rmp-serde = "1.2"
ropey = "1.6.1"
roxmltree = "0.19"
rstest = { version = "0.18", default-features = false }
rusqlite = "0.31"
rust-embed = "8.2.0"
rust-embed = "8.3.0"
same-file = "1.0"
serde = { version = "1.0", default-features = false }
serde_json = "1.0"
@ -162,24 +174,25 @@ windows = "0.54"
winreg = "0.52"
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.92.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.92.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.92.1" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.92.1", features = [
nu-cli = { path = "./crates/nu-cli", version = "0.93.0" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.93.0" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.93.0" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.93.0", optional = true }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.93.0", features = [
"dataframe",
], optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.92.1" }
nu-command = { path = "./crates/nu-command", version = "0.92.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.92.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.92.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.92.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.92.1" }
nu-path = { path = "./crates/nu-path", version = "0.92.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.92.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.92.1" }
nu-std = { path = "./crates/nu-std", version = "0.92.1" }
nu-system = { path = "./crates/nu-system", version = "0.92.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.92.1" }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.93.0" }
nu-command = { path = "./crates/nu-command", version = "0.93.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.93.0" }
nu-explore = { path = "./crates/nu-explore", version = "0.93.0" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.93.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.93.0" }
nu-path = { path = "./crates/nu-path", version = "0.93.0" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.93.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.93.0" }
nu-std = { path = "./crates/nu-std", version = "0.93.0" }
nu-system = { path = "./crates/nu-system", version = "0.93.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.93.0" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
@ -208,18 +221,21 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.92.1" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.93.0" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.93.0" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.93.0" }
assert_cmd = "2.0"
dirs-next = { workspace = true }
divan = "0.1.14"
pretty_assertions = "1.4"
pretty_assertions = { workspace = true }
rstest = { workspace = true, default-features = false }
serial_test = "3.0"
serial_test = "3.1"
tempfile = { workspace = true }
[features]
plugin = [
"nu-plugin",
"nu-plugin-engine",
"nu-cmd-plugin",
"nu-cli/plugin",
"nu-parser/plugin",
"nu-command/plugin",
@ -237,7 +253,6 @@ default-no-clipboard = [
"mimalloc",
]
stable = ["default"]
wasi = ["nu-cmd-lang/wasi"]
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
@ -245,7 +260,11 @@ wasi = ["nu-cmd-lang/wasi"]
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
system-clipboard = ["reedline/system_clipboard", "nu-cli/system-clipboard"]
system-clipboard = [
"reedline/system_clipboard",
"nu-cli/system-clipboard",
"nu-cmd-lang/system-clipboard",
]
# Stable (Default)
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]

View File

@ -1,6 +1,7 @@
use nu_cli::{eval_source, evaluate_commands};
use nu_parser::parse;
use nu_plugin::{Encoder, EncodingType, PluginCallResponse, PluginOutput};
use nu_plugin_core::{Encoder, EncodingType};
use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
use nu_protocol::{
engine::{EngineState, Stack},
eval_const::create_nu_constant,
@ -85,6 +86,7 @@ fn bench_command_with_custom_stack_and_engine(
&mut stack.clone(),
PipelineData::empty(),
None,
false,
)
.unwrap();
})
@ -104,6 +106,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
&mut stack,
PipelineData::empty(),
None,
false,
)
.unwrap();
@ -263,6 +266,7 @@ mod eval_commands {
&mut nu_protocol::engine::Stack::new(),
PipelineData::empty(),
None,
false,
)
.unwrap();
})

View File

@ -5,25 +5,26 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.92.1"
version = "0.93.0"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }
nu-command = { path = "../nu-command", version = "0.92.1" }
nu-test-support = { path = "../nu-test-support", version = "0.92.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.0" }
nu-command = { path = "../nu-command", version = "0.93.0" }
nu-test-support = { path = "../nu-test-support", version = "0.93.0" }
rstest = { workspace = true, default-features = false }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-path = { path = "../nu-path", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-utils = { path = "../nu-utils", version = "0.92.1" }
nu-color-config = { path = "../nu-color-config", version = "0.92.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.0" }
nu-engine = { path = "../nu-engine", version = "0.93.0" }
nu-path = { path = "../nu-path", version = "0.93.0" }
nu-parser = { path = "../nu-parser", version = "0.93.0" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.0", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
nu-utils = { path = "../nu-utils", version = "0.93.0" }
nu-color-config = { path = "../nu-color-config", version = "0.93.0" }
nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
@ -44,5 +45,5 @@ uuid = { workspace = true, features = ["v4"] }
which = { workspace = true }
[features]
plugin = []
plugin = ["nu-plugin-engine"]
system-clipboard = ["reedline/system_clipboard"]

View File

@ -1,5 +1,4 @@
use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)]
pub struct Commandline;
@ -11,45 +10,12 @@ impl Command for Commandline {
fn signature(&self) -> Signature {
Signature::build("commandline")
.input_output_types(vec![
(Type::Nothing, Type::Nothing),
(Type::String, Type::String),
])
.switch(
"cursor",
"Set or get the current cursor position",
Some('c'),
)
.switch(
"cursor-end",
"Set the current cursor position to the end of the buffer",
Some('e'),
)
.switch(
"append",
"appends the string to the end of the buffer",
Some('a'),
)
.switch(
"insert",
"inserts the string into the buffer at the cursor position",
Some('i'),
)
.switch(
"replace",
"replaces the current contents of the buffer (default)",
Some('r'),
)
.optional(
"cmd",
SyntaxShape::String,
"the string to perform the operation with",
)
.input_output_types(vec![(Type::Nothing, Type::String)])
.category(Category::Core)
}
fn usage(&self) -> &str {
"View or modify the current command line input buffer."
"View the current command line input buffer."
}
fn search_terms(&self) -> Vec<&str> {
@ -59,126 +25,11 @@ impl Command for Commandline {
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let span = cmd.span();
let cmd = cmd.coerce_into_string()?;
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor (-c)` is deprecated".into(),
msg: "Setting the current cursor position by `--cursor (-c)` is deprecated"
.into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline set-cursor`".into()),
inner: vec![],
},
);
match cmd.parse::<i64>() {
Ok(n) => {
repl.cursor_pos = if n <= 0 {
0usize
} else {
repl.buffer
.grapheme_indices(true)
.map(|(i, _c)| i)
.nth(n as usize)
.unwrap_or(repl.buffer.len())
}
}
Err(_) => {
return Err(ShellError::CantConvert {
to_type: "int".to_string(),
from_type: "string".to_string(),
span,
help: Some(format!(r#"string "{cmd}" does not represent a valid int"#)),
})
}
}
} else if call.has_flag(engine_state, stack, "append")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--append (-a)` is deprecated".into(),
msg: "Appending the string to the end of the buffer by `--append (-a)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --append (-a)`".into()),
inner: vec![],
},
);
repl.buffer.push_str(&cmd);
} else if call.has_flag(engine_state, stack, "insert")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--insert (-i)` is deprecated".into(),
msg: "Inserts the string into the buffer at the cursor position by `--insert (-i)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --insert (-i)`".into()),
inner: vec![],
},
);
let cursor_pos = repl.cursor_pos;
repl.buffer.insert_str(cursor_pos, &cmd);
repl.cursor_pos += cmd.len();
} else {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--replace (-r)` is deprecated".into(),
msg: "Replacing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline edit --replace (-r)`".into()),
inner: vec![],
},
);
repl.buffer = cmd;
repl.cursor_pos = repl.buffer.len();
}
Ok(Value::nothing(call.head).into_pipeline_data())
} else {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor-end")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor-end (-e)` is deprecated".into(),
msg: "Setting the current cursor position to the end of the buffer by `--cursor-end (-e)` is deprecated".into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline set-cursor --end (-e)`".into()),
inner: vec![],
},
);
repl.cursor_pos = repl.buffer.len();
Ok(Value::nothing(call.head).into_pipeline_data())
} else if call.has_flag(engine_state, stack, "cursor")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "`--cursor (-c)` is deprecated".into(),
msg: "Getting the current cursor position by `--cursor (-c)` is deprecated"
.into(),
span: Some(call.arguments_span()),
help: Some("Use `commandline get-cursor`".into()),
inner: vec![],
},
);
let char_pos = repl
.buffer
.grapheme_indices(true)
.chain(std::iter::once((repl.buffer.len(), "")))
.position(|(i, _c)| i == repl.cursor_pos)
.expect("Cursor position isn't on a grapheme boundary");
Ok(Value::string(char_pos.to_string(), call.head).into_pipeline_data())
} else {
Ok(Value::string(repl.buffer.to_string(), call.head).into_pipeline_data())
}
}
let repl = engine_state.repl_state.lock().expect("repl state mutex");
Ok(Value::string(repl.buffer.clone(), call.head).into_pipeline_data())
}
}

View File

@ -12,7 +12,7 @@ impl Command for KeybindingsDefault {
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Platform)
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::table())])
}
fn usage(&self) -> &str {

View File

@ -14,7 +14,7 @@ impl Command for KeybindingsList {
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::table())])
.switch("modifiers", "list of modifiers", Some('m'))
.switch("keycodes", "list of keycodes", Some('k'))
.switch("modes", "list of edit modes", Some('o'))

View File

@ -7,8 +7,8 @@ use nu_engine::eval_block;
use nu_parser::{flatten_pipeline_element, parse, FlatShape};
use nu_protocol::{
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, PipelineData, Span, Value,
engine::{Closure, EngineState, Stack, StateWorkingSet},
PipelineData, Span, Value,
};
use reedline::{Completer as ReedlineCompleter, Suggestion};
use std::{str, sync::Arc};
@ -25,7 +25,7 @@ impl NuCompleter {
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
Self {
engine_state,
stack: stack.reset_stdio().capture(),
stack: stack.reset_out_dest().capture(),
}
}
@ -63,15 +63,15 @@ impl NuCompleter {
fn external_completion(
&self,
block_id: BlockId,
closure: &Closure,
spans: &[String],
offset: usize,
span: Span,
) -> Option<Vec<SemanticSuggestion>> {
let block = self.engine_state.get_block(block_id);
let block = self.engine_state.get_block(closure.block_id);
let mut callee_stack = self
.stack
.gather_captures(&self.engine_state, &block.captures);
.captures_to_stack_preserve_out_dest(closure.captures.clone());
// Line
if let Some(pos_arg) = block.signature.required_positional.first() {
@ -210,13 +210,10 @@ impl NuCompleter {
// We got no results for internal completion
// now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer {
if let Some(external_result) = self.external_completion(
block_id,
&spans,
fake_offset,
new_span,
) {
if let Some(closure) = config.external_completer.as_ref() {
if let Some(external_result) =
self.external_completion(closure, &spans, fake_offset, new_span)
{
return external_result;
}
}
@ -360,9 +357,9 @@ impl NuCompleter {
}
// Try to complete using an external completer (if set)
if let Some(block_id) = config.external_completer {
if let Some(closure) = config.external_completer.as_ref() {
if let Some(external_result) = self.external_completion(
block_id,
closure,
&spans,
fake_offset,
new_span,

View File

@ -24,7 +24,7 @@ impl CustomCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
Self {
engine_state,
stack: stack.reset_stdio().capture(),
stack: stack.reset_out_dest().capture(),
decl_id,
line,
sort_by: SortBy::None,

View File

@ -68,9 +68,7 @@ impl Completer for VariableCompletion {
self.var_context.1.clone().into_iter().skip(1).collect();
if let Some(val) = env_vars.get(&target_var_str) {
for suggestion in
nested_suggestions(val.clone(), nested_levels, current_span)
{
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
suggestion.suggestion.value.as_bytes(),
@ -117,8 +115,7 @@ impl Completer for VariableCompletion {
nu_protocol::NU_VARIABLE_ID,
nu_protocol::Span::new(current_span.start, current_span.end),
) {
for suggestion in
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
{
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
@ -140,8 +137,7 @@ impl Completer for VariableCompletion {
// If the value exists and it's of type Record
if let Ok(value) = var {
for suggestion in
nested_suggestions(value, self.var_context.1.clone(), current_span)
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
{
if options.match_algorithm.matches_u8_insensitive(
options.case_sensitive,
@ -244,21 +240,21 @@ impl Completer for VariableCompletion {
// Find recursively the values for sublevels
// if no sublevels are set it returns the current value
fn nested_suggestions(
val: Value,
sublevels: Vec<Vec<u8>>,
val: &Value,
sublevels: &[Vec<u8>],
current_span: reedline::Span,
) -> Vec<SemanticSuggestion> {
let mut output: Vec<SemanticSuggestion> = vec![];
let value = recursive_value(val, sublevels);
let value = recursive_value(val, sublevels).unwrap_or_else(Value::nothing);
let kind = SuggestionKind::Type(value.get_type());
match value {
Value::Record { val, .. } => {
// Add all the columns as completion
for (col, _) in val.into_iter() {
for col in val.columns() {
output.push(SemanticSuggestion {
suggestion: Suggestion {
value: col,
value: col.clone(),
description: None,
style: None,
extra: None,
@ -311,56 +307,47 @@ fn nested_suggestions(
}
// Extracts the recursive value (e.g: $var.a.b.c)
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
// Go to next sublevel
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
if let Some((sublevel, next_sublevels)) = sublevels.split_first() {
let span = val.span();
match val {
Value::Record { val, .. } => {
for item in *val {
// Check if index matches with sublevel
if item.0.as_bytes().to_vec() == next_sublevel {
if let Some((_, value)) = val.iter().find(|(key, _)| key.as_bytes() == sublevel) {
// If matches try to fetch recursively the next
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
}
}
recursive_value(value, next_sublevels)
} else {
// Current sublevel value not found
return Value::nothing(span);
Err(span)
}
}
Value::LazyRecord { val, .. } => {
for col in val.column_names() {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
val.get_column_value(col).unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
if col.as_bytes() == *sublevel {
let val = val.get_column_value(col).map_err(|_| span)?;
return recursive_value(&val, next_sublevels);
}
}
// Current sublevel value not found
return Value::nothing(span);
Err(span)
}
Value::List { vals, .. } => {
for col in get_columns(vals.as_slice()) {
if col.as_bytes().to_vec() == next_sublevel {
return recursive_value(
Value::list(vals, span)
.get_data_by_key(&col)
.unwrap_or_default(),
sublevels.into_iter().skip(1).collect(),
);
if col.as_bytes() == *sublevel {
let val = val.get_data_by_key(&col).ok_or(span)?;
return recursive_value(&val, next_sublevels);
}
}
// Current sublevel value not found
return Value::nothing(span);
Err(span)
}
_ => return val,
_ => Ok(val.clone()),
}
} else {
Ok(val.clone())
}
val
}
impl MatchAlgorithm {

View File

@ -6,13 +6,15 @@ use nu_protocol::{
report_error, HistoryFileFormat, PipelineData,
};
#[cfg(feature = "plugin")]
use nu_protocol::{ParseError, Spanned};
use nu_protocol::{ParseError, PluginRegistryFile, Spanned};
#[cfg(feature = "plugin")]
use nu_utils::utils::perf;
use std::path::PathBuf;
#[cfg(feature = "plugin")]
const PLUGIN_FILE: &str = "plugin.nu";
const PLUGIN_FILE: &str = "plugin.msgpackz";
#[cfg(feature = "plugin")]
const OLD_PLUGIN_FILE: &str = "plugin.nu";
const HISTORY_FILE_TXT: &str = "history.txt";
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
@ -20,40 +22,150 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
#[cfg(feature = "plugin")]
pub fn read_plugin_file(
engine_state: &mut EngineState,
stack: &mut Stack,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
let start_time = std::time::Instant::now();
let mut plug_path = String::new();
// Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, plugin_file, storage_path);
use std::path::Path;
let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path {
let plugin_filename = plugin_path.to_string_lossy();
plug_path = plugin_filename.to_string();
if let Ok(contents) = std::fs::read(&plugin_path) {
eval_source(
use nu_protocol::{report_error_new, ShellError};
let span = plugin_file.as_ref().map(|s| s.span);
// Check and warn + abort if this is a .nu plugin file
if plugin_file
.as_ref()
.and_then(|p| Path::new(&p.item).extension())
.is_some_and(|ext| ext == "nu")
{
report_error_new(
engine_state,
stack,
&contents,
&plugin_filename,
PipelineData::empty(),
false,
&ShellError::GenericError {
error: "Wrong plugin file format".into(),
msg: ".nu plugin files are no longer supported".into(),
span,
help: Some("please recreate this file in the new .msgpackz format".into()),
inner: vec![],
},
);
}
return;
}
let mut start_time = std::time::Instant::now();
// Reading signatures from plugin registry file
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
perf(
&format!("read_plugin_file {}", &plug_path),
"add plugin file to engine_state",
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
start_time = std::time::Instant::now();
let plugin_path = engine_state.plugin_path.clone();
if let Some(plugin_path) = plugin_path {
// Open the plugin file
let mut file = match std::fs::File::open(&plugin_path) {
Ok(file) => file,
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
log::warn!("Plugin file not found: {}", plugin_path.display());
// Try migration of an old plugin file if this wasn't a custom plugin file
if plugin_file.is_none() && migrate_old_plugin_file(engine_state, storage_path)
{
let Ok(file) = std::fs::File::open(&plugin_path) else {
log::warn!("Failed to load newly migrated plugin file");
return;
};
file
} else {
return;
}
} else {
report_error_new(
engine_state,
&ShellError::GenericError {
error: format!(
"Error while opening plugin registry file: {}",
plugin_path.display()
),
msg: "plugin path defined here".into(),
span,
help: None,
inner: vec![err.into()],
},
);
return;
}
}
};
// Abort if the file is empty.
if file.metadata().is_ok_and(|m| m.len() == 0) {
log::warn!(
"Not reading plugin file because it's empty: {}",
plugin_path.display()
);
return;
}
// Read the contents of the plugin file
let contents = match PluginRegistryFile::read_from(&mut file, span) {
Ok(contents) => contents,
Err(err) => {
log::warn!("Failed to read plugin registry file: {err:?}");
report_error_new(
engine_state,
&ShellError::GenericError {
error: format!(
"Error while reading plugin registry file: {}",
plugin_path.display()
),
msg: "plugin path defined here".into(),
span,
help: Some(
"you might try deleting the file and registering all of your \
plugins again"
.into(),
),
inner: vec![],
},
);
return;
}
};
perf(
&format!("read plugin file {}", plugin_path.display()),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
start_time = std::time::Instant::now();
let mut working_set = StateWorkingSet::new(engine_state);
nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
if let Err(err) = engine_state.merge_delta(working_set.render()) {
report_error_new(engine_state, &err);
return;
}
perf(
&format!("load plugin file {}", plugin_path.display()),
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
}
}
#[cfg(feature = "plugin")]
@ -62,15 +174,30 @@ pub fn add_plugin_file(
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
use std::path::Path;
let working_set = StateWorkingSet::new(engine_state);
let cwd = working_set.get_cwd();
if let Some(plugin_file) = plugin_file {
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
engine_state.plugin_signatures = Some(path)
let path = Path::new(&plugin_file.item);
let path_dir = path.parent().unwrap_or(path);
// Just try to canonicalize the directory of the plugin file first.
if let Ok(path_dir) = canonicalize_with(path_dir, &cwd) {
// Try to canonicalize the actual filename, but it's ok if that fails. The file doesn't
// have to exist.
let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
let path = canonicalize_with(&path, &cwd).unwrap_or(path);
engine_state.plugin_path = Some(path)
} else {
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
report_error(&working_set, &e);
// It's an error if the directory for the plugin file doesn't exist.
report_error(
&working_set,
&ParseError::FileNotFound(
path_dir.to_string_lossy().into_owned(),
plugin_file.span,
),
);
}
} else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures
@ -78,7 +205,7 @@ pub fn add_plugin_file(
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
plugin_path.push(PLUGIN_FILE);
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
engine_state.plugin_signatures = Some(plugin_path);
engine_state.plugin_path = Some(plugin_path);
}
}
@ -91,6 +218,10 @@ pub fn eval_config_contents(
let config_filename = config_path.to_string_lossy();
if let Ok(contents) = std::fs::read(&config_path) {
// Set the current active file to the config file.
let prev_file = engine_state.file.take();
engine_state.file = Some(config_path.clone());
eval_source(
engine_state,
stack,
@ -100,6 +231,9 @@ pub fn eval_config_contents(
false,
);
// Restore the current active file.
engine_state.file = prev_file;
// Merge the environment in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
@ -127,3 +261,129 @@ pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> O
history_path
})
}
#[cfg(feature = "plugin")]
pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -> bool {
use nu_protocol::{
report_error_new, PluginExample, PluginIdentity, PluginRegistryItem,
PluginRegistryItemData, PluginSignature, ShellError,
};
use std::collections::BTreeMap;
let start_time = std::time::Instant::now();
let cwd = engine_state.current_work_dir();
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {
dir.push(storage_path);
nu_path::canonicalize_with(dir, &cwd).ok()
}) else {
return false;
};
let Ok(old_plugin_file_path) = nu_path::canonicalize_with(OLD_PLUGIN_FILE, &config_dir) else {
return false;
};
let old_contents = match std::fs::read(&old_plugin_file_path) {
Ok(old_contents) => old_contents,
Err(err) => {
report_error_new(
engine_state,
&ShellError::GenericError {
error: "Can't read old plugin file to migrate".into(),
msg: "".into(),
span: None,
help: Some(err.to_string()),
inner: vec![],
},
);
return false;
}
};
// Make a copy of the engine state, because we'll read the newly generated file
let mut engine_state = engine_state.clone();
let mut stack = Stack::new();
if !eval_source(
&mut engine_state,
&mut stack,
&old_contents,
&old_plugin_file_path.to_string_lossy(),
PipelineData::Empty,
false,
) {
return false;
}
// Now that the plugin commands are loaded, we just have to generate the file
let mut contents = PluginRegistryFile::new();
let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
for decl in engine_state.plugin_decls() {
if let Some(identity) = decl.plugin_identity() {
groups
.entry(identity.clone())
.or_default()
.push(PluginSignature {
sig: decl.signature(),
examples: decl
.examples()
.into_iter()
.map(PluginExample::from)
.collect(),
})
}
}
for (identity, commands) in groups {
contents.upsert_plugin(PluginRegistryItem {
name: identity.name().to_owned(),
filename: identity.filename().to_owned(),
shell: identity.shell().map(|p| p.to_owned()),
data: PluginRegistryItemData::Valid { commands },
});
}
// Write the new file
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
.map_err(|e| e.into())
.and_then(|file| contents.write_to(file, None))
{
report_error_new(
&engine_state,
&ShellError::GenericError {
error: "Failed to save migrated plugin file".into(),
msg: "".into(),
span: None,
help: Some("ensure `$nu.plugin-path` is writable".into()),
inner: vec![err],
},
);
return false;
}
if engine_state.is_interactive {
eprintln!(
"Your old plugin.nu file has been migrated to the new format: {}",
new_plugin_file_path.display()
);
eprintln!(
"The plugin.nu file has not been removed. If `plugin list` looks okay, \
you may do so manually."
);
}
perf(
"migrate old plugin file",
start_time,
file!(),
line!(),
column!(),
engine_state.get_config().use_ansi_coloring,
);
true
}

View File

@ -15,6 +15,7 @@ pub fn evaluate_commands(
stack: &mut Stack,
input: PipelineData,
table_mode: Option<Value>,
no_newline: bool,
) -> Result<Option<i64>> {
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
@ -60,7 +61,13 @@ pub fn evaluate_commands(
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
}
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
crate::eval_file::print_table_or_error(
engine_state,
stack,
pipeline_data,
&mut config,
no_newline,
)
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);

View File

@ -5,15 +5,16 @@ use nu_engine::{convert_env_values, current_dir, eval_block};
use nu_parser::parse;
use nu_path::canonicalize_with;
use nu_protocol::{
ast::Call,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error, Config, PipelineData, ShellError, Span, Value,
};
use nu_utils::stdout_write_all_and_flush;
use std::sync::Arc;
use std::{io::Write, sync::Arc};
/// Main function used when a file path is found as argument for nu
/// Entry point for evaluating a file.
///
/// If the file contains a main command, it is invoked with `args` and the pipeline data from `input`;
/// otherwise, the pipeline data is forwarded to the first command in the file, and `args` are ignored.
pub fn evaluate_file(
path: String,
args: &[String],
@ -21,7 +22,7 @@ pub fn evaluate_file(
stack: &mut Stack,
input: PipelineData,
) -> Result<()> {
// Translate environment variables from Strings to Values
// Convert environment variables from Strings to Values and store them in the engine state.
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
@ -74,8 +75,7 @@ pub fn evaluate_file(
);
std::process::exit(1);
});
engine_state.start_in_file(Some(file_path_str));
engine_state.file = Some(file_path.clone());
let parent = file_path.parent().unwrap_or_else(|| {
let working_set = StateWorkingSet::new(engine_state);
@ -104,17 +104,19 @@ pub fn evaluate_file(
let source_filename = file_path
.file_name()
.expect("internal error: script missing filename");
.expect("internal error: missing filename");
let mut working_set = StateWorkingSet::new(engine_state);
trace!("parsing file: {}", file_path_str);
let block = parse(&mut working_set, Some(file_path_str), &file, false);
// If any parse errors were found, report the first error and exit.
if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err);
std::process::exit(1);
}
// Look for blocks whose name starts with "main" and replace it with the filename.
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
if block.signature.name == "main" {
block.signature.name = source_filename.to_string_lossy().to_string();
@ -124,19 +126,21 @@ pub fn evaluate_file(
}
}
let _ = engine_state.merge_delta(working_set.delta);
// Merge the changes into the engine state.
engine_state
.merge_delta(working_set.delta)
.expect("merging delta into engine_state should succeed");
// Check if the file contains a main command.
if engine_state.find_decl(b"main", &[]).is_some() {
let args = format!("main {}", args.join(" "));
// Evaluate the file, but don't run main yet.
let pipeline_data =
eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty());
let pipeline_data = match pipeline_data {
Err(ShellError::Return { .. }) => {
// allows early exists before `main` is run.
// Allow early return before main is run.
return Ok(());
}
x => x,
}
.unwrap_or_else(|e| {
@ -145,12 +149,12 @@ pub fn evaluate_file(
std::process::exit(1);
});
// Print the pipeline output of the file.
// The pipeline output of a file is the pipeline output of its last command.
let result = pipeline_data.print(engine_state, stack, true, false);
match result {
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
std::process::exit(1);
}
@ -161,6 +165,9 @@ pub fn evaluate_file(
}
}
// Invoke the main command with arguments.
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
let args = format!("main {}", args.join(" "));
if !eval_source(
engine_state,
stack,
@ -185,6 +192,7 @@ pub(crate) fn print_table_or_error(
stack: &mut Stack,
mut pipeline_data: PipelineData,
config: &mut Config,
no_newline: bool,
) -> Option<i64> {
let exit_code = match &mut pipeline_data {
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
@ -200,29 +208,8 @@ pub(crate) fn print_table_or_error(
std::process::exit(1);
}
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
let command = engine_state.get_decl(decl_id);
if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config);
} else {
// The final call on table command, it's ok to set redirect_output to false.
let call = Call::new(Span::new(0, 0));
let table = command.run(engine_state, stack, &call, pipeline_data);
match table {
Ok(table) => {
print_or_exit(table, engine_state, config);
}
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
}
}
} else {
print_or_exit(pipeline_data, engine_state, config);
}
// We don't need to do anything special to print a table because print() handles it
print_or_exit(pipeline_data, engine_state, stack, no_newline);
// Make sure everything has finished
if let Some(exit_code) = exit_code {
@ -238,17 +225,21 @@ pub(crate) fn print_table_or_error(
}
}
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
for item in pipeline_data {
if let Value::Error { error, .. } = item {
fn print_or_exit(
pipeline_data: PipelineData,
engine_state: &EngineState,
stack: &mut Stack,
no_newline: bool,
) {
let result = pipeline_data.print(engine_state, stack, no_newline, false);
let _ = std::io::stdout().flush();
let _ = std::io::stderr().flush();
if let Err(error) = result {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &*error);
report_error(&working_set, &error);
let _ = std::io::stderr().flush();
std::process::exit(1);
}
let out = item.to_expanded_string("\n", config) + "\n";
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
}
}

View File

@ -32,4 +32,6 @@ pub use validation::NuValidator;
#[cfg(feature = "plugin")]
pub use config_files::add_plugin_file;
#[cfg(feature = "plugin")]
pub use config_files::migrate_old_plugin_file;
#[cfg(feature = "plugin")]
pub use config_files::read_plugin_file;

View File

@ -28,7 +28,7 @@ impl NuMenuCompleter {
Self {
block_id,
span,
stack: stack.reset_stdio().capture(),
stack: stack.reset_out_dest().capture(),
engine_state,
only_buffer_difference,
}

View File

@ -1,6 +1,6 @@
use crate::NushellPrompt;
use log::trace;
use nu_engine::get_eval_subexpression;
use nu_engine::ClosureEvalOnce;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
report_error, Config, PipelineData, Value,
@ -34,17 +34,13 @@ fn get_prompt_string(
engine_state: &EngineState,
stack: &mut Stack,
) -> Option<String> {
let eval_subexpression = get_eval_subexpression(engine_state);
stack
.get_env_var(engine_state, prompt)
.and_then(|v| match v {
Value::Closure { val, .. } => {
let block = engine_state.get_block(val.block_id);
let mut stack = stack.captures_to_stack(val.captures);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val =
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
let result = ClosureEvalOnce::new(engine_state, stack, val)
.run_with_input(PipelineData::Empty);
trace!(
"get_prompt_string (block) {}:{}:{}",
file!(),
@ -52,25 +48,7 @@ fn get_prompt_string(
column!()
);
ret_val
.map_err(|err| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
})
.ok()
}
Value::Block { val: block_id, .. } => {
let block = engine_state.get_block(block_id);
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
trace!(
"get_prompt_string (block) {}:{}:{}",
file!(),
line!(),
column!()
);
ret_val
result
.map_err(|err| {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);

View File

@ -23,7 +23,10 @@ use nu_protocol::{
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
Value, NU_VARIABLE_ID,
};
use nu_utils::utils::perf;
use nu_utils::{
filesystem::{have_permission, PermissionResult},
utils::perf,
};
use reedline::{
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
HistorySessionId, Reedline, SqliteBackedHistory, Vi,
@ -71,7 +74,8 @@ pub fn evaluate_repl(
let mut entry_num = 0;
let nu_prompt = NushellPrompt::new(config.shell_integration);
let shell_integration = config.shell_integration;
let nu_prompt = NushellPrompt::new(shell_integration);
let start_time = std::time::Instant::now();
// Translate environment variables from Strings to Values
@ -111,6 +115,11 @@ pub fn evaluate_repl(
engine_state.merge_env(&mut unique_stack, cwd)?;
}
let hostname = System::host_name();
if shell_integration {
shell_integration_osc_7_633_2(hostname.as_deref(), engine_state, &mut unique_stack);
}
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
// Regenerate the $nu constant to contain the startup time and any other potential updates
@ -144,7 +153,7 @@ pub fn evaluate_repl(
let temp_file_cloned = temp_file.clone();
let mut nu_prompt_cloned = nu_prompt.clone();
let iteration_panic_state = catch_unwind(AssertUnwindSafe(move || {
let iteration_panic_state = catch_unwind(AssertUnwindSafe(|| {
let (continue_loop, current_stack, line_editor) = loop_iteration(LoopContext {
engine_state: &mut current_engine_state,
stack: current_stack,
@ -153,6 +162,7 @@ pub fn evaluate_repl(
temp_file: &temp_file_cloned,
use_color,
entry_num: &mut entry_num,
hostname: hostname.as_deref(),
});
// pass the most recent version of the line_editor back
@ -229,6 +239,7 @@ struct LoopContext<'a> {
temp_file: &'a Path,
use_color: bool,
entry_num: &'a mut usize,
hostname: Option<&'a str>,
}
/// Perform one iteration of the REPL loop
@ -247,6 +258,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
temp_file,
use_color,
entry_num,
hostname,
} = ctx;
let cwd = get_guaranteed_cwd(engine_state, &stack);
@ -361,6 +373,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
.with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring)
.with_cursor_config(cursor_config);
perf(
"reedline builder",
start_time,
@ -382,6 +395,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
} else {
line_editor.disable_hints()
};
perf(
"reedline coloring/style_computer",
start_time,
@ -398,8 +412,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
report_error_new(engine_state, &e);
Reedline::create()
});
perf(
"reedline menus",
"reedline adding menus",
start_time,
file!(),
line!(),
@ -421,6 +436,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
} else {
line_editor
};
perf(
"reedline buffer_editor",
start_time,
@ -437,6 +453,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
warn!("Failed to sync history: {}", e);
}
}
perf(
"sync_history",
start_time,
@ -450,6 +467,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
start_time = std::time::Instant::now();
// Changing the line editor based on the found keybindings
line_editor = setup_keybindings(engine_state, line_editor);
perf(
"keybindings",
start_time,
@ -473,6 +491,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
&mut Stack::with_parent(stack_arc.clone()),
nu_prompt,
);
perf(
"update_prompt",
start_time,
@ -498,18 +517,30 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
let mut stack = Stack::unwrap_unique(stack_arc);
perf(
"line_editor setup",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let line_editor_input_time = std::time::Instant::now();
match input {
Ok(Signal::Success(s)) => {
let hostname = System::host_name();
let history_supports_meta = matches!(
engine_state.history_config().map(|h| h.file_format),
Some(HistoryFileFormat::Sqlite)
);
if history_supports_meta {
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor);
prepare_history_metadata(&s, hostname, engine_state, &mut line_editor);
}
// For pre_exec_hook
start_time = Instant::now();
// 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() {
@ -530,22 +561,57 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
}
}
perf(
"pre_execution_hook",
start_time,
file!(),
line!(),
column!(),
use_color,
);
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
repl.cursor_pos = line_editor.current_insertion_point();
repl.buffer = line_editor.current_buffer_contents().to_string();
drop(repl);
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(PRE_EXECUTE_MARKER);
perf(
"pre_execute_marker (133;C) ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
// Actual command execution logic starts from here
let start_time = Instant::now();
let cmd_execution_start_time = Instant::now();
match parse_operation(s.clone(), engine_state, &stack) {
Ok(operation) => match operation {
ReplOperation::AutoCd { cwd, target, span } => {
do_auto_cd(target, cwd, &mut stack, engine_state, span);
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
perf(
"post_execute_marker (133;D) ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
ReplOperation::RunCommand(cmd) => {
line_editor = do_run_cmd(
@ -555,14 +621,30 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
line_editor,
shell_integration,
*entry_num,
)
use_color,
);
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
perf(
"post_execute_marker (133;D) ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
// as the name implies, we do nothing in this case
ReplOperation::DoNothing => {}
},
Err(ref e) => error!("Error parsing operation: {e}"),
}
let cmd_duration = start_time.elapsed();
let cmd_duration = cmd_execution_start_time.elapsed();
stack.add_env_var(
"CMD_DURATION_MS".into(),
@ -582,7 +664,18 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
}
if shell_integration {
do_shell_integration_finalize_command(hostname, engine_state, &mut stack);
start_time = Instant::now();
shell_integration_osc_7_633_2(hostname, engine_state, &mut stack);
perf(
"shell_integration_finalize ansi escape sequences",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
@ -590,13 +683,35 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
perf(
"command_finished_marker ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
Ok(Signal::CtrlD) => {
// When exiting clear to a new line
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
perf(
"command_finished_marker ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
println!();
return (false, stack, line_editor);
@ -611,13 +726,24 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
// Alternatively only allow that expected failures let the REPL loop
}
if shell_integration {
start_time = Instant::now();
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
perf(
"command_finished_marker ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
}
perf(
"processing line editor input",
start_time,
line_editor_input_time,
file!(),
line!(),
column!(),
@ -625,7 +751,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
);
perf(
"finished repl loop",
"time between prompts in line editor loop",
loop_start_time,
file!(),
line!(),
@ -641,7 +767,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
///
fn prepare_history_metadata(
s: &str,
hostname: &Option<String>,
hostname: Option<&str>,
engine_state: &EngineState,
line_editor: &mut Reedline,
) {
@ -649,7 +775,7 @@ fn prepare_history_metadata(
let result = line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname.clone_from(hostname);
c.hostname = hostname.map(str::to_string);
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
@ -761,6 +887,16 @@ fn do_auto_cd(
path.to_string_lossy().to_string()
};
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
report_error_new(
engine_state,
&ShellError::IOError {
msg: format!("Cannot change directory to {path}: {reason}"),
},
);
return;
}
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
//FIXME: this only changes the current scope, but instead this environment variable
@ -812,6 +948,7 @@ fn do_run_cmd(
line_editor: Reedline,
shell_integration: bool,
entry_num: usize,
use_color: bool,
) -> Reedline {
trace!("eval source: {}", s);
@ -837,6 +974,7 @@ fn do_run_cmd(
}
if shell_integration {
let start_time = Instant::now();
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
@ -859,6 +997,15 @@ fn do_run_cmd(
}
}
}
perf(
"set title with command ansi escape sequence",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
eval_source(
@ -875,14 +1022,14 @@ fn do_run_cmd(
///
/// Output some things and set environment variables so shells with the right integration
/// can have more information about what is going on (after we have run a command)
/// can have more information about what is going on (both on startup and after we have
/// run a command)
///
fn do_shell_integration_finalize_command(
hostname: Option<String>,
fn shell_integration_osc_7_633_2(
hostname: Option<&str>,
engine_state: &EngineState,
stack: &mut Stack,
) {
run_ansi_sequence(&get_command_finished_marker(stack, engine_state));
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
@ -899,7 +1046,7 @@ fn do_shell_integration_finalize_command(
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
hostname.unwrap_or("localhost"),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },

View File

@ -360,9 +360,8 @@ fn find_matching_block_end_in_expr(
Expr::MatchBlock(_) => None,
Expr::Nothing => None,
Expr::Garbage => None,
Expr::Spread(_) => None,
Expr::Table(hdr, rows) => {
Expr::Table(table) => {
if expr_last == global_cursor_offset {
// cursor is at table end
Some(expr_first)
@ -371,11 +370,11 @@ fn find_matching_block_end_in_expr(
Some(expr_last)
} else {
// cursor is inside table
for inner_expr in hdr {
for inner_expr in table.columns.as_ref() {
find_in_expr_or_continue!(inner_expr);
}
for row in rows {
for inner_expr in row {
for row in table.rows.as_ref() {
for inner_expr in row.as_ref() {
find_in_expr_or_continue!(inner_expr);
}
}
@ -468,7 +467,7 @@ fn find_matching_block_end_in_expr(
None
}
Expr::List(inner_expr) => {
Expr::List(list) => {
if expr_last == global_cursor_offset {
// cursor is at list end
Some(expr_first)
@ -477,8 +476,9 @@ fn find_matching_block_end_in_expr(
Some(expr_last)
} else {
// cursor is inside list
for inner_expr in inner_expr {
find_in_expr_or_continue!(inner_expr);
for item in list {
let expr = item.expr();
find_in_expr_or_continue!(expr);
}
None
}

View File

@ -1,8 +1,9 @@
pub mod support;
use nu_cli::NuCompleter;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::engine::StateWorkingSet;
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest};
use std::path::PathBuf;
@ -177,7 +178,7 @@ fn dotnu_completions() {
#[ignore]
fn external_completer_trailing_space() {
// https://github.com/nushell/nushell/issues/6378
let block = "let external_completer = {|spans| $spans}";
let block = "{|spans| $spans}";
let input = "gh alias ".to_string();
let suggestions = run_external_completion(block, &input);
@ -847,12 +848,14 @@ fn alias_of_another_alias() {
match_suggestions(expected_paths, suggestions)
}
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
let completer = format!("$env.config.completions.external.completer = {completer}");
// Create a new engine
let (dir, _, mut engine_state, mut stack) = new_engine();
let (_, delta) = {
let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state);
let block = parse(&mut working_set, None, block.as_bytes(), false);
let block = parse(&mut working_set, None, completer.as_bytes(), false);
assert!(working_set.parse_errors.is_empty());
(block, working_set.render())
@ -860,16 +863,13 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
assert!(engine_state.merge_delta(delta).is_ok());
assert!(
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok()
);
// Merge environment into the permanent state
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
let latest_block_id = engine_state.num_blocks() - 1;
// Change config adding the external completer
let mut config = engine_state.get_config().clone();
config.external_completer = Some(latest_block_id);
engine_state.set_config(config);
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);

View File

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

View File

@ -5,8 +5,8 @@ use nu_parser::parse;
use nu_protocol::{
cli_error::{report_error, report_error_new},
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
engine::{Closure, EngineState, Stack, StateWorkingSet},
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
};
use std::sync::Arc;
@ -153,11 +153,11 @@ pub fn eval_hook(
// If it returns true (the default if a condition block is not specified), the hook should be run.
let do_run_hook = if let Some(condition) = val.get("condition") {
let other_span = condition.span();
if let Ok(block_id) = condition.coerce_block() {
match run_hook_block(
if let Ok(closure) = condition.as_closure() {
match run_hook(
engine_state,
stack,
block_id,
closure,
None,
arguments.clone(),
other_span,
@ -259,25 +259,8 @@ pub fn eval_hook(
stack.remove_var(*var_id);
}
}
Value::Block { val: block_id, .. } => {
run_hook_block(
engine_state,
stack,
*block_id,
input,
arguments,
source_span,
)?;
}
Value::Closure { val, .. } => {
run_hook_block(
engine_state,
stack,
val.block_id,
input,
arguments,
source_span,
)?;
run_hook(engine_state, stack, val, input, arguments, source_span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue {
@ -289,11 +272,8 @@ pub fn eval_hook(
}
}
}
Value::Block { val: block_id, .. } => {
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
}
Value::Closure { val, .. } => {
output = run_hook_block(engine_state, stack, val.block_id, input, arguments, span)?;
output = run_hook(engine_state, stack, val, input, arguments, span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue {
@ -310,20 +290,20 @@ pub fn eval_hook(
Ok(output)
}
fn run_hook_block(
fn run_hook(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
closure: &Closure,
optional_input: Option<PipelineData>,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<PipelineData, ShellError> {
let block = engine_state.get_block(block_id);
let block = engine_state.get_block(closure.block_id);
let input = optional_input.unwrap_or_else(PipelineData::empty);
let mut callee_stack = stack
.gather_captures(engine_state, &block.captures)
.captures_to_stack_preserve_out_dest(closure.captures.clone())
.reset_pipes();
for (idx, PositionalArg { var_id, .. }) in

View File

@ -1,9 +1,8 @@
use nu_protocol::{
ast::RangeInclusion,
engine::{EngineState, Stack, StateWorkingSet},
report_error, Range, ShellError, Span, Value,
};
use std::path::PathBuf;
use std::{ops::Bound, path::PathBuf};
pub fn get_init_cwd() -> PathBuf {
std::env::current_dir().unwrap_or_else(|_| {
@ -24,36 +23,22 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
type MakeRangeError = fn(&str, Span) -> ShellError;
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
let start = match &range.from {
Value::Int { val, .. } => isize::try_from(*val).unwrap_or_default(),
Value::Nothing { .. } => 0,
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
}
match range {
Range::IntRange(range) => {
let start = range.start().try_into().unwrap_or(0);
let end = match range.end() {
Bound::Included(v) => v as isize,
Bound::Excluded(v) => (v - 1) as isize,
Bound::Unbounded => isize::MAX,
};
let end = match &range.to {
Value::Int { val, .. } => {
if matches!(range.inclusion, RangeInclusion::Inclusive) {
isize::try_from(*val).unwrap_or(isize::max_value())
} else {
isize::try_from(*val).unwrap_or(isize::max_value()) - 1
}
}
Value::Nothing { .. } => isize::max_value(),
_ => {
return Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
})
}
};
Ok((start, end))
}
Range::FloatRange(_) => Err(|msg, span| ShellError::TypeMismatch {
err_message: msg.to_string(),
span,
}),
}
}
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
For more help: (https://nushell.sh/book/configuration.html#configurations-with-built-in-commands)";

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
version = "0.92.1"
version = "0.93.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,9 +13,9 @@ version = "0.92.1"
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.93.0" }
nu-parser = { path = "../nu-parser", version = "0.93.0" }
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
# Potential dependencies for extras
chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false }
@ -25,12 +25,12 @@ indexmap = { workspace = true }
num = { version = "0.4", optional = true }
serde = { workspace = true, features = ["derive"] }
# keep sqlparser at 0.39.0 until we can update polars
sqlparser = { version = "0.39.0", optional = true }
polars-io = { version = "0.37", features = ["avro"], optional = true }
polars-arrow = { version = "0.37", optional = true }
polars-ops = { version = "0.37", optional = true }
polars-plan = { version = "0.37", features = ["regex"], optional = true }
polars-utils = { version = "0.37", optional = true }
sqlparser = { version = "0.45", optional = true }
polars-io = { version = "0.39", features = ["avro"], optional = true }
polars-arrow = { version = "0.39", optional = true }
polars-ops = { version = "0.39", optional = true }
polars-plan = { version = "0.39", features = ["regex"], optional = true }
polars-utils = { version = "0.39", optional = true }
[dependencies.polars]
features = [
@ -65,11 +65,11 @@ features = [
]
default-features = false
optional = true
version = "0.37"
version = "0.39"
[features]
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.0" }

View File

@ -5,7 +5,7 @@ use polars::prelude::{
CsvEncoding, CsvReader, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader,
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
};
use polars_io::avro::AvroReader;
use polars_io::{avro::AvroReader, HiveOptions};
use std::{fs::File, io::BufReader, path::PathBuf};
#[derive(Clone)]
@ -121,7 +121,9 @@ fn command(
"jsonl" => from_jsonl(engine_state, stack, call),
"avro" => from_avro(engine_state, stack, call),
_ => Err(ShellError::FileNotFoundCustom {
msg: format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
msg: format!(
"{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json, jsonl, avro"
),
span: blamed,
}),
},
@ -149,7 +151,7 @@ fn from_parquet(
low_memory: false,
cloud_options: None,
use_statistics: false,
hive_partitioning: false,
hive_options: HiveOptions::default(),
};
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
@ -244,7 +246,8 @@ fn from_ipc(
cache: true,
rechunk: false,
row_index: None,
memmap: true,
memory_map: true,
cloud_options: None,
};
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)

View File

@ -24,7 +24,7 @@ impl Command for ToNu {
.switch("tail", "shows tail rows", Some('t'))
.input_output_types(vec![
(Type::Custom("expression".into()), Type::Any),
(Type::Custom("dataframe".into()), Type::Table(vec![])),
(Type::Custom("dataframe".into()), Type::table()),
])
//.input_output_type(Type::Any, Type::Any)
.category(Category::Custom("dataframe".into()))

View File

@ -1,5 +1,6 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
use nu_engine::command_prelude::*;
use polars::chunked_array::ops::SortMultipleOptions;
#[derive(Clone)]
pub struct LazySortBy;
@ -126,11 +127,17 @@ impl Command for LazySortBy {
None => expressions.iter().map(|_| false).collect::<Vec<bool>>(),
};
let sort_options = SortMultipleOptions {
descending: reverse,
nulls_last,
multithreaded: true,
maintain_order,
};
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
let lazy = NuLazyFrame::new(
lazy.from_eager,
lazy.into_polars()
.sort_by_exprs(&expressions, reverse, nulls_last, maintain_order),
lazy.into_polars().sort_by_exprs(&expressions, sort_options),
);
Ok(PipelineData::Value(

View File

@ -48,7 +48,6 @@ impl Command for ToLazyFrame {
let df = NuDataFrame::try_from_iter(input.into_iter(), maybe_schema)?;
let lazy = NuLazyFrame::from_dataframe(df);
let value = Value::custom(Box::new(lazy), call.head);
Ok(PipelineData::Value(value, None))
}
}

View File

@ -9,11 +9,19 @@ pub use operations::Axis;
use super::{nu_schema::NuSchema, utils::DEFAULT_ROWS, NuLazyFrame};
use indexmap::IndexMap;
use nu_protocol::{did_you_mean, PipelineData, Record, ShellError, Span, Value};
use polars::prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series};
use polars::{
chunked_array::ops::SortMultipleOptions,
prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series},
};
use polars_plan::prelude::{lit, Expr, Null};
use polars_utils::total_ord::TotalEq;
use polars_utils::total_ord::{TotalEq, TotalHash};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, collections::HashSet, fmt::Display, hash::Hasher};
use std::{
cmp::Ordering,
collections::HashSet,
fmt::Display,
hash::{Hash, Hasher},
};
// DataFrameValue is an encapsulation of Nushell Value that can be used
// to define the PolarsObject Trait. The polars object trait allows to
@ -31,6 +39,15 @@ impl DataFrameValue {
}
}
impl TotalHash for DataFrameValue {
fn tot_hash<H>(&self, state: &mut H)
where
H: Hasher,
{
(*self).hash(state)
}
}
impl Display for DataFrameValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.get_type())
@ -50,7 +67,7 @@ impl PartialEq for DataFrameValue {
}
impl Eq for DataFrameValue {}
impl std::hash::Hash for DataFrameValue {
impl Hash for DataFrameValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match &self.0 {
Value::Nothing { .. } => 0.hash(state),
@ -162,9 +179,11 @@ impl NuDataFrame {
conversion::insert_record(&mut column_values, record, &maybe_schema)?
}
Value::Record { val: record, .. } => {
conversion::insert_record(&mut column_values, *record, &maybe_schema)?
}
Value::Record { val: record, .. } => conversion::insert_record(
&mut column_values,
record.into_owned(),
&maybe_schema,
)?,
_ => {
let key = "0".to_string();
conversion::insert_value(value, key, &mut column_values, &maybe_schema)?
@ -472,12 +491,18 @@ impl NuDataFrame {
.expect("already checked that dataframe is different than 0");
// if unable to sort, then unable to compare
let lhs = match self.as_ref().sort(vec![*first_col], false, false) {
let lhs = match self
.as_ref()
.sort(vec![*first_col], SortMultipleOptions::default())
{
Ok(df) => df,
Err(_) => return None,
};
let rhs = match other.as_ref().sort(vec![*first_col], false, false) {
let rhs = match other
.as_ref()
.sort(vec![*first_col], SortMultipleOptions::default())
{
Ok(df) => df,
Err(_) => return None,
};

View File

@ -313,11 +313,15 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
Expr::SortBy {
expr,
by,
descending,
sort_options,
} => {
let by: Result<Vec<Value>, ShellError> =
by.iter().map(|b| expr_to_value(b, span)).collect();
let descending: Vec<Value> = descending.iter().map(|r| Value::bool(*r, span)).collect();
let descending: Vec<Value> = sort_options
.descending
.iter()
.map(|r| Value::bool(*r, span))
.collect();
Ok(Value::record(
record! {

View File

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

View File

@ -30,8 +30,8 @@ impl Command for BitsInto {
(Type::Duration, Type::String),
(Type::String, Type::String),
(Type::Bool, Type::String),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
.rest(
@ -64,7 +64,7 @@ impl Command for BitsInto {
vec![
Example {
description: "convert a binary value into a string, padded to 8 places with 0s",
example: "01b | into bits",
example: "0x[1] | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),

View File

@ -15,7 +15,7 @@ impl Command for Fmt {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("fmt")
.input_output_types(vec![(Type::Number, Type::Record(vec![]))])
.input_output_types(vec![(Type::Number, Type::record())])
.category(Category::Conversions)
}

View File

@ -1,4 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::engine::Closure;
#[derive(Clone)]
@ -67,116 +67,56 @@ impl Command for EachWhile {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let block = engine_state.get_block(capture_block.block_id).clone();
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 span = call.head;
let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state);
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream { .. } => Ok(input
// TODO: Could this be changed to .into_interruptible_iter(ctrlc) ?
| PipelineData::ListStream(..) => {
let mut closure = ClosureEval::new(engine_state, stack, closure);
Ok(input
.into_iter()
.map_while(move |x| {
// 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) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
match eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
) {
Ok(v) => {
let value = v.into_value(span);
if value.is_nothing() {
None
} else {
Some(value)
}
.map_while(move |value| match closure.run_with_value(value) {
Ok(data) => {
let value = data.into_value(head);
(!value.is_nothing()).then_some(value)
}
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
.into_pipeline_data(engine_state.ctrlc.clone()))
}
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => Ok(stream
} => {
let mut closure = ClosureEval::new(engine_state, stack, closure);
Ok(stream
.into_iter()
.map_while(move |x| {
// 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);
let x = match x {
Ok(x) => x,
Err(_) => return None,
};
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
match eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
) {
Ok(v) => {
let value = v.into_value(span);
if value.is_nothing() {
None
} else {
Some(value)
}
.map_while(move |value| {
let value = value.ok()?;
match closure.run_with_value(value) {
Ok(data) => {
let value = data.into_value(head);
(!value.is_nothing()).then_some(value)
}
Err(_) => None,
}
})
.fuse()
.into_pipeline_data(ctrlc)),
.into_pipeline_data(engine_state.ctrlc.clone()))
}
// This match allows non-iterables to be accepted,
// which is currently considered undesirable (Nov 2022).
PipelineData::Value(x, ..) => {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
PipelineData::Value(value, ..) => {
ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value)
}
}
eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
x.into_pipeline_data(),
)
}
}
.map(|x| x.set_metadata(metadata))
.map(|data| data.set_metadata(metadata))
}
}

View File

@ -58,7 +58,7 @@ fn horizontal_rotate_value(
Value::Record { val: record, .. } => {
let rotations = by.map(|n| n % record.len()).unwrap_or(1);
let (mut cols, mut vals): (Vec<_>, Vec<_>) = record.into_iter().unzip();
let (mut cols, mut vals): (Vec<_>, Vec<_>) = record.into_owned().into_iter().unzip();
if !cells_only {
match direction {
HorizontalDirection::Right => cols.rotate_right(rotations),

View File

@ -16,7 +16,7 @@ impl Command for RollDown {
fn signature(&self) -> Signature {
Signature::build(self.name())
// TODO: It also operates on List
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
.input_output_types(vec![(Type::table(), Type::table())])
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
.category(Category::Filters)
}

View File

@ -16,8 +16,8 @@ impl Command for RollLeft {
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![
(Type::Record(vec![]), Type::Record(vec![])),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::record(), Type::record()),
(Type::table(), Type::table()),
])
.named(
"by",

View File

@ -16,8 +16,8 @@ impl Command for RollRight {
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![
(Type::Record(vec![]), Type::Record(vec![])),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::record(), Type::record()),
(Type::table(), Type::table()),
])
.named(
"by",

View File

@ -16,7 +16,7 @@ impl Command for RollUp {
fn signature(&self) -> Signature {
Signature::build(self.name())
// TODO: It also operates on List
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
.input_output_types(vec![(Type::table(), Type::table())])
.named("by", SyntaxShape::Int, "Number of rows to roll", Some('b'))
.category(Category::Filters)
}

View File

@ -11,8 +11,8 @@ impl Command for Rotate {
fn signature(&self) -> Signature {
Signature::build("rotate")
.input_output_types(vec![
(Type::Record(vec![]), Type::Table(vec![])),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::record(), Type::table()),
(Type::table(), Type::table()),
])
.switch("ccw", "rotate counter clockwise", None)
.rest(
@ -171,7 +171,7 @@ pub fn rotate(
let span = val.span();
match val {
Value::Record { val: record, .. } => {
let (cols, vals): (Vec<_>, Vec<_>) = record.into_iter().unzip();
let (cols, vals): (Vec<_>, Vec<_>) = record.into_owned().into_iter().unzip();
old_column_names = cols;
new_values.extend_from_slice(&vals);
}

View File

@ -1,6 +1,6 @@
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
use nu_protocol::{ast::Block, engine::Closure, PipelineIterator};
use std::{collections::HashSet, sync::Arc};
use nu_engine::{command_prelude::*, ClosureEval};
use nu_protocol::{engine::Closure, PipelineIterator};
use std::collections::HashSet;
#[derive(Clone)]
pub struct UpdateCells;
@ -12,7 +12,7 @@ impl Command for UpdateCells {
fn signature(&self) -> Signature {
Signature::build("update cells")
.input_output_types(vec![(Type::Table(vec![]), Type::Table(vec![]))])
.input_output_types(vec![(Type::table(), Type::table())])
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
@ -87,24 +87,9 @@ impl Command for UpdateCells {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// the block to run on each cell
let engine_state = engine_state.clone();
let block: Closure = call.req(&engine_state, stack, 0)?;
let mut stack = stack.captures_to_stack(block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let block: Arc<Block> = engine_state.get_block(block.block_id).clone();
let eval_block_fn = get_eval_block(&engine_state);
let span = call.head;
stack.with_env(&orig_env_vars, &orig_env_hidden);
// the columns to update
let columns: Option<Value> = call.get_flag(&engine_state, &mut stack, "columns")?;
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
let columns: Option<Value> = call.get_flag(engine_state, stack, "columns")?;
let columns: Option<HashSet<String>> = match columns {
Some(val) => Some(
val.into_list()?
@ -115,27 +100,23 @@ impl Command for UpdateCells {
None => None,
};
let metadata = input.metadata();
Ok(UpdateCellIterator {
input: input.into_iter(),
engine_state,
stack,
block,
iter: input.into_iter(),
closure: ClosureEval::new(engine_state, stack, closure),
columns,
span,
eval_block_fn,
span: head,
}
.into_pipeline_data(ctrlc)
.into_pipeline_data(engine_state.ctrlc.clone())
.set_metadata(metadata))
}
}
struct UpdateCellIterator {
input: PipelineIterator,
iter: PipelineIterator,
closure: ClosureEval,
columns: Option<HashSet<String>>,
engine_state: EngineState,
stack: Stack,
block: Arc<Block>,
eval_block_fn: EvalBlockFn,
span: Span,
}
@ -143,69 +124,36 @@ impl Iterator for UpdateCellIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
match self.input.next() {
Some(val) => {
if let Some(ref cols) = self.columns {
if !val.columns().any(|c| cols.contains(c)) {
return Some(val);
let mut value = self.iter.next()?;
let value = if let Value::Record { val, .. } = &mut value {
let val = val.to_mut();
if let Some(columns) = &self.columns {
for (col, val) in val.iter_mut() {
if columns.contains(col) {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val));
}
}
} else {
for (_, val) in val.iter_mut() {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val))
}
}
let span = val.span();
match val {
Value::Record { val, .. } => Some(Value::record(
val.into_iter()
.map(|(col, val)| match &self.columns {
Some(cols) if !cols.contains(&col) => (col, val),
_ => (
col,
process_cell(
val,
&self.engine_state,
&mut self.stack,
&self.block,
span,
self.eval_block_fn,
),
),
})
.collect(),
span,
)),
val => Some(process_cell(
val,
&self.engine_state,
&mut self.stack,
&self.block,
self.span,
self.eval_block_fn,
)),
}
}
None => None,
}
value
} else {
eval_value(&mut self.closure, self.span, value)
};
Some(value)
}
}
#[allow(clippy::too_many_arguments)]
fn process_cell(
val: Value,
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
span: Span,
eval_block_fn: EvalBlockFn,
) -> Value {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, val.clone());
}
}
match eval_block_fn(engine_state, stack, block, val.into_pipeline_data()) {
Ok(pd) => pd.into_value(span),
Err(e) => Value::error(e, span),
}
fn eval_value(closure: &mut ClosureEval, span: Span, value: Value) -> Value {
closure
.run_with_value(value)
.map(|data| data.into_value(span))
.unwrap_or_else(|err| Value::error(err, span))
}
#[cfg(test)]

View File

@ -10,7 +10,7 @@ impl Command for FromUrl {
fn signature(&self) -> Signature {
Signature::build("from url")
.input_output_types(vec![(Type::String, Type::Record(vec![]))])
.input_output_types(vec![(Type::String, Type::record())])
.category(Category::Formats)
}

View File

@ -46,8 +46,8 @@ impl Command for SubCommand {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.category(Category::Platform)

View File

@ -17,8 +17,8 @@ impl Command for DecodeHex {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::Binary)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -17,8 +17,8 @@ impl Command for EncodeHex {
Type::List(Box::new(Type::Binary)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -13,8 +13,8 @@ impl Command for FormatPattern {
fn signature(&self) -> Signature {
Signature::build("format pattern")
.input_output_types(vec![
(Type::Table(vec![]), Type::List(Box::new(Type::String))),
(Type::Record(vec![]), Type::Any),
(Type::table(), Type::List(Box::new(Type::String))),
(Type::record(), Type::Any),
])
.required(
"pattern",

View File

@ -18,8 +18,8 @@ impl Command for SubCommand {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -14,8 +14,8 @@ impl Command for SubCommand {
Signature::build("str kebab-case")
.input_output_types(vec![
(Type::String, Type::String),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),

View File

@ -14,8 +14,8 @@ impl Command for SubCommand {
Signature::build("str pascal-case")
.input_output_types(vec![
(Type::String, Type::String),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),

View File

@ -18,8 +18,8 @@ impl Command for SubCommand {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -18,8 +18,8 @@ impl Command for SubCommand {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -18,8 +18,8 @@ impl Command for SubCommand {
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.rest(

View File

@ -6,22 +6,22 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021"
license = "MIT"
name = "nu-cmd-lang"
version = "0.92.1"
version = "0.93.0"
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-utils = { path = "../nu-utils", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.93.0" }
nu-parser = { path = "../nu-parser", version = "0.93.0" }
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
nu-utils = { path = "../nu-utils", version = "0.93.0" }
itertools = { workspace = true }
shadow-rs = { version = "0.26", default-features = false }
shadow-rs = { version = "0.27", default-features = false }
[build-dependencies]
shadow-rs = { version = "0.26", default-features = false }
shadow-rs = { version = "0.27", default-features = false }
[features]
mimalloc = []
@ -30,4 +30,4 @@ trash-support = []
sqlite = []
dataframe = []
static-link-openssl = []
wasi = []
system-clipboard = []

View File

@ -19,14 +19,14 @@ impl Command for Collect {
)
.switch(
"keep-env",
"let the block affect environment variables",
"let the closure affect environment variables",
None,
)
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Collect the stream and pass it to a block."
"Collect a stream into a value and then run a closure with the collected value as input."
}
fn run(
@ -36,14 +36,14 @@ impl Command for Collect {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let closure: Closure = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id).clone();
let block = engine_state.get_block(closure.block_id);
let mut stack_captures =
stack.captures_to_stack_preserve_stdio(capture_block.captures.clone());
stack.captures_to_stack_preserve_out_dest(closure.captures.clone());
let metadata = input.metadata();
let input: Value = input.into_value(call.head);
let input = input.into_value(call.head);
let mut saved_positional = None;
if let Some(var) = block.signature.get_positional(0) {
@ -58,7 +58,7 @@ impl Command for Collect {
let result = eval_block(
engine_state,
&mut stack_captures,
&block,
block,
input.into_pipeline_data(),
)
.map(|x| x.set_metadata(metadata));
@ -67,7 +67,7 @@ impl Command for Collect {
redirect_env(engine_state, stack, &stack_captures);
// for when we support `data | let x = $in;`
// remove the variables added earlier
for (var_id, _) in capture_block.captures {
for (var_id, _) in closure.captures {
stack_captures.remove_var(var_id);
}
if let Some(u) = saved_positional {
@ -80,11 +80,18 @@ impl Command for Collect {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
vec![
Example {
description: "Use the second value in the stream",
example: "[1 2 3] | collect { |x| $x.1 }",
result: Some(Value::test_int(2)),
}]
},
Example {
description: "Read and write to the same file",
example: "open file.txt | collect { save -f file.txt }",
result: None,
},
]
}
}

View File

@ -1,8 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::{
engine::{Closure, StateWorkingSet},
PipelineMetadata,
};
use nu_protocol::{engine::StateWorkingSet, PipelineMetadata};
#[derive(Clone)]
pub struct Describe;
@ -49,6 +46,19 @@ impl Command for Describe {
detailed: call.has_flag(engine_state, stack, "detailed")?,
collect_lazyrecords: call.has_flag(engine_state, stack, "collect-lazyrecords")?,
};
if options.collect_lazyrecords {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "Deprecated flag".into(),
msg: "the `--collect-lazyrecords` flag is deprecated, since lazy records will be removed in 0.94.0"
.into(),
span: Some(call.head),
help: None,
inner: vec![],
},
);
}
run(Some(engine_state), call, input, options)
}
@ -267,7 +277,7 @@ fn compact_primitive_description(mut value: Value) -> Value {
if val.len() != 1 {
return value;
}
if let Some(type_name) = val.get_mut("type") {
if let Some(type_name) = val.to_mut().get_mut("type") {
return std::mem::take(type_name);
}
}
@ -303,7 +313,8 @@ fn describe_value(
),
head,
),
Value::Record { mut val, .. } => {
Value::Record { val, .. } => {
let mut val = val.into_owned();
for (_k, v) in val.iter_mut() {
*v = compact_primitive_description(describe_value(
std::mem::take(v),
@ -317,7 +328,7 @@ fn describe_value(
record!(
"type" => Value::string("record", head),
"lazy" => Value::bool(false, head),
"columns" => Value::record(*val, head),
"columns" => Value::record(val, head),
),
head,
)
@ -335,16 +346,12 @@ fn describe_value(
),
head,
),
Value::Block { val, .. }
| Value::Closure {
val: Closure { block_id: val, .. },
..
} => {
let block = engine_state.map(|engine_state| engine_state.get_block(val));
Value::Closure { val, .. } => {
let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id));
if let Some(block) = block {
let mut record = Record::new();
record.push("type", Value::string(value.get_type().to_string(), head));
record.push("type", Value::string("closure", head));
record.push(
"signature",
Value::record(
@ -395,10 +402,11 @@ fn describe_value(
if options.collect_lazyrecords {
let collected = val.collect()?;
if let Value::Record { mut val, .. } =
if let Value::Record { val, .. } =
describe_value(collected, head, engine_state, options)?
{
record.push("length", Value::int(val.len() as i64, head));
let mut val = Record::clone(&val);
for (_k, v) in val.iter_mut() {
*v = compact_primitive_description(describe_value(
std::mem::take(v),
@ -408,7 +416,8 @@ fn describe_value(
)?);
}
record.push("columns", Value::record(*val, head));
record.push("length", Value::int(val.len() as i64, head));
record.push("columns", Value::record(val, head));
} else {
let cols = val.column_names();
record.push("length", Value::int(cols.len() as i64, head));

View File

@ -1,5 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env};
use nu_protocol::{engine::Closure, IoStream, ListStream, RawStream};
use nu_protocol::{engine::Closure, ListStream, OutDest, RawStream};
use std::thread;
#[derive(Clone)]
@ -72,7 +72,7 @@ impl Command for Do {
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
let has_env = call.has_flag(engine_state, caller_stack, "env")?;
let mut callee_stack = caller_stack.captures_to_stack_preserve_stdio(block.captures);
let mut callee_stack = caller_stack.captures_to_stack_preserve_out_dest(block.captures);
let block = engine_state.get_block(block.block_id);
bind_args_to(&mut callee_stack, &block.signature, rest, call.head)?;
@ -117,7 +117,7 @@ impl Command for Do {
None,
)
})
.map_err(|e| e.into_spanned(call.head))
.err_span(call.head)
})
.transpose()?;
@ -191,7 +191,7 @@ impl Command for Do {
metadata,
trim_end_newline,
}) if ignore_program_errors
&& !matches!(caller_stack.stdout(), IoStream::Pipe | IoStream::Capture) =>
&& !matches!(caller_stack.stdout(), OutDest::Pipe | OutDest::Capture) =>
{
Ok(PipelineData::ExternalStream {
stdout,

View File

@ -20,8 +20,10 @@ impl Command for Echo {
}
fn extra_usage(&self) -> &str {
r#"When given no arguments, it returns an empty string. When given one argument,
it returns it. Otherwise, it returns a list of the arguments. There is usually
r#"Unlike `print`, which prints unstructured text to stdout, `echo` is like an
identity function and simply returns its arguments. When given no arguments,
it returns an empty string. When given one argument, it returns it as a
nushell value. Otherwise, it returns a list of the arguments. There is usually
little reason to use this over just writing the values as-is."#
}

View File

@ -1,5 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
use nu_protocol::{engine::Block, ListStream};
use nu_protocol::ListStream;
#[derive(Clone)]
pub struct For;
@ -66,22 +66,27 @@ impl Command for For {
.as_keyword()
.expect("internal error: missing keyword");
let block_id = call
.positional_nth(2)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let eval_expression = get_eval_expression(engine_state);
let eval_block = get_eval_block(engine_state);
let values = eval_expression(engine_state, stack, keyword_expr)?;
let block: Block = call.req(engine_state, stack, 2)?;
let value = eval_expression(engine_state, stack, keyword_expr)?;
let numbered = call.has_flag(engine_state, stack, "numbered")?;
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let block = engine_state.get_block(block.block_id).clone();
let block = engine_state.get_block(block_id);
let stack = &mut stack.push_redirection(None, None);
match values {
let span = value.span();
match value {
Value::List { vals, .. } => {
for (idx, x) in ListStream::from_stream(vals.into_iter(), ctrlc).enumerate() {
// with_env() is used here to ensure that each iteration uses
@ -103,7 +108,7 @@ impl Command for For {
},
);
match eval_block(&engine_state, stack, &block, PipelineData::empty()) {
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
Err(ShellError::Break { .. }) => {
break;
}
@ -125,7 +130,7 @@ impl Command for For {
}
}
Value::Range { val, .. } => {
for (idx, x) in val.into_range_iter(ctrlc)?.enumerate() {
for (idx, x) in val.into_range_iter(span, ctrlc).enumerate() {
stack.add_var(
var_id,
if numbered {
@ -141,7 +146,7 @@ impl Command for For {
},
);
match eval_block(&engine_state, stack, &block, PipelineData::empty()) {
match eval_block(&engine_state, stack, block, PipelineData::empty()) {
Err(ShellError::Break { .. }) => {
break;
}
@ -165,7 +170,7 @@ impl Command for For {
x => {
stack.add_var(var_id, x);
eval_block(&engine_state, stack, &block, PipelineData::empty())?.into_value(head);
eval_block(&engine_state, stack, block, PipelineData::empty())?.into_value(head);
}
}
Ok(PipelineData::empty())

View File

@ -41,11 +41,7 @@ impl Command for HideEnv {
for name in env_var_names {
if !stack.remove_env_var(engine_state, &name.item) && !ignore_errors {
let all_names: Vec<String> = stack
.get_env_var_names(engine_state)
.iter()
.cloned()
.collect();
let all_names = stack.get_env_var_names(engine_state);
if let Some(closest_match) = did_you_mean(&all_names, &name.item) {
return Err(ShellError::DidYouMeanCustom {
msg: format!("Environment variable '{}' not found", name.item),

View File

@ -2,7 +2,7 @@ use nu_engine::{
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
};
use nu_protocol::{
engine::{Block, StateWorkingSet},
engine::StateWorkingSet,
eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
};
@ -52,20 +52,16 @@ impl Command for If {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cond = call.positional_nth(0).expect("checked through parser");
let then_block: Block = call.req_const(working_set, 1)?;
let then_block = call
.positional_nth(1)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let else_case = call.positional_nth(2);
let result = eval_constant(working_set, cond)?;
match &result {
Value::Bool { val, .. } => {
if *val {
let block = working_set.get_block(then_block.block_id);
eval_const_subexpression(
working_set,
block,
input,
block.span.unwrap_or(call.head),
)
if eval_constant(working_set, cond)?.as_bool()? {
let block = working_set.get_block(then_block);
eval_const_subexpression(working_set, block, input, block.span.unwrap_or(call.head))
} else if let Some(else_case) = else_case {
if let Some(else_expr) = else_case.as_keyword() {
if let Some(block_id) = else_expr.as_block() {
@ -86,14 +82,6 @@ impl Command for If {
Ok(PipelineData::empty())
}
}
x => Err(ShellError::CantConvert {
to_type: "bool".into(),
from_type: x.get_type().to_string(),
span: result.span(),
help: None,
}),
}
}
fn run(
&self,
@ -103,17 +91,19 @@ impl Command for If {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cond = call.positional_nth(0).expect("checked through parser");
let then_block: Block = call.req(engine_state, stack, 1)?;
let then_block = call
.positional_nth(1)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let else_case = call.positional_nth(2);
let eval_expression = get_eval_expression(engine_state);
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
let eval_block = get_eval_block(engine_state);
let result = eval_expression(engine_state, stack, cond)?;
match &result {
Value::Bool { val, .. } => {
if *val {
let block = engine_state.get_block(then_block.block_id);
if eval_expression(engine_state, stack, cond)?.as_bool()? {
let block = engine_state.get_block(then_block);
eval_block(engine_state, stack, block, input)
} else if let Some(else_case) = else_case {
if let Some(else_expr) = else_case.as_keyword() {
@ -125,21 +115,12 @@ impl Command for If {
.map(|res| res.0)
}
} else {
eval_expression_with_input(engine_state, stack, else_case, input)
.map(|res| res.0)
eval_expression_with_input(engine_state, stack, else_case, input).map(|res| res.0)
}
} else {
Ok(PipelineData::empty())
}
}
x => Err(ShellError::CantConvert {
to_type: "bool".into(),
from_type: x.get_type().to_string(),
span: result.span(),
help: None,
}),
}
}
fn examples(&self) -> Vec<Example> {
vec![

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::{engine::StateWorkingSet, IoStream};
use nu_protocol::{engine::StateWorkingSet, OutDest};
#[derive(Clone)]
pub struct Ignore;
@ -56,8 +56,8 @@ impl Command for Ignore {
}]
}
fn stdio_redirect(&self) -> (Option<IoStream>, Option<IoStream>) {
(Some(IoStream::Null), None)
fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
(Some(OutDest::Null), None)
}
}

View File

@ -15,7 +15,7 @@ impl Command for LazyMake {
fn signature(&self) -> Signature {
Signature::build("lazy make")
.input_output_types(vec![(Type::Nothing, Type::Record(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::record())])
.required_named(
"columns",
SyntaxShape::List(Box::new(SyntaxShape::String)),
@ -54,6 +54,18 @@ impl Command for LazyMake {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "Deprecated command".into(),
msg: "warning: lazy records and the `lazy make` command will be removed in 0.94.0"
.into(),
span: Some(call.head),
help: None,
inner: vec![],
},
);
let span = call.head;
let columns: Vec<Spanned<String>> = call
.get_flag(engine_state, stack, "columns")?
@ -80,7 +92,7 @@ impl Command for LazyMake {
}
}
let stack = stack.clone().reset_stdio().capture();
let stack = stack.clone().reset_out_dest().capture();
Ok(Value::lazy_record(
Box::new(NuLazyRecord {

View File

@ -61,7 +61,7 @@ impl Command for Let {
let eval_block = get_eval_block(engine_state);
let stack = &mut stack.start_capture();
let pipeline_data = eval_block(engine_state, stack, block, input)?;
let mut value = pipeline_data.into_value(call.head);
let value = pipeline_data.into_value(call.head);
// if given variable type is Glob, and our result is string
// then nushell need to convert from Value::String to Value::Glob
@ -69,12 +69,12 @@ impl Command for Let {
// if we pass it to other commands.
let var_type = &engine_state.get_var(var_id).ty;
let val_span = value.span();
match value {
let value = match value {
Value::String { val, .. } if var_type == &Type::Glob => {
value = Value::glob(val, false, val_span);
}
_ => {}
Value::glob(val, false, val_span)
}
value => value,
};
stack.add_var(var_id, value);
Ok(PipelineData::empty())

View File

@ -1,5 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_block};
use nu_protocol::engine::Block;
#[derive(Clone)]
pub struct Loop;
@ -28,7 +27,13 @@ impl Command for Loop {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let block: Block = call.req(engine_state, stack, 0)?;
let block_id = call
.positional_nth(0)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let block = engine_state.get_block(block_id);
let eval_block = get_eval_block(engine_state);
let stack = &mut stack.push_redirection(None, None);
@ -38,8 +43,6 @@ impl Command for Loop {
break;
}
let block = engine_state.get_block(block.block_id);
match eval_block(engine_state, stack, block, PipelineData::empty()) {
Err(ShellError::Break { .. }) => {
break;

View File

@ -1,10 +1,7 @@
use nu_engine::{
command_prelude::*, get_eval_block, get_eval_expression, get_eval_expression_with_input,
};
use nu_protocol::{
ast::{Expr, Expression},
engine::Matcher,
};
use nu_protocol::engine::Matcher;
#[derive(Clone)]
pub struct Match;
@ -38,25 +35,25 @@ impl Command for Match {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let value: Value = call.req(engine_state, stack, 0)?;
let block = call.positional_nth(1);
let matches = call
.positional_nth(1)
.expect("checked through parser")
.as_match_block()
.expect("missing match block");
let eval_expression = get_eval_expression(engine_state);
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
let eval_block = get_eval_block(engine_state);
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) {
for (pattern, expr) in matches {
if pattern.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);
for (id, value) in match_variables.drain(..) {
stack.add_var(id, value);
}
let guard_matches = if let Some(guard) = &match_.0.guard {
let guard_matches = if let Some(guard) = &pattern.guard {
let Value::Bool { val, .. } = eval_expression(engine_state, stack, guard)?
else {
return Err(ShellError::MatchGuardNotBool { span: guard.span });
@ -68,15 +65,15 @@ impl Command for Match {
};
if guard_matches {
return if let Some(block_id) = match_.1.as_block() {
return if let Some(block_id) = expr.as_block() {
let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, input)
} else {
eval_expression_with_input(engine_state, stack, &match_.1, input)
.map(|x| x.0)
eval_expression_with_input(engine_state, stack, expr, input).map(|x| x.0)
};
}
}
} else {
match_variables.clear();
}
}

View File

@ -71,13 +71,3 @@ pub use try_::Try;
pub use use_::Use;
pub use version::Version;
pub use while_::While;
mod plugin;
mod plugin_list;
mod plugin_stop;
mod register;
pub use plugin::PluginCommand;
pub use plugin_list::PluginList;
pub use plugin_stop::PluginStop;
pub use register::Register;

View File

@ -61,7 +61,7 @@ impl Command for Mut {
let eval_block = get_eval_block(engine_state);
let stack = &mut stack.start_capture();
let pipeline_data = eval_block(engine_state, stack, block, input)?;
let mut value = pipeline_data.into_value(call.head);
let value = pipeline_data.into_value(call.head);
// if given variable type is Glob, and our result is string
// then nushell need to convert from Value::String to Value::Glob
@ -69,12 +69,12 @@ impl Command for Mut {
// if we pass it to other commands.
let var_type = &engine_state.get_var(var_id).ty;
let val_span = value.span();
match value {
let value = match value {
Value::String { val, .. } if var_type == &Type::Glob => {
value = Value::glob(val, false, val_span);
}
_ => {}
Value::glob(val, false, val_span)
}
value => value,
};
stack.add_var(var_id, value);
Ok(PipelineData::empty())

View File

@ -40,17 +40,11 @@ impl Command for Return {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let return_value: Option<Value> = call.opt(engine_state, stack, 0)?;
if let Some(value) = return_value {
let value = return_value.unwrap_or(Value::nothing(call.head));
Err(ShellError::Return {
span: call.head,
value: Box::new(value),
})
} else {
Err(ShellError::Return {
span: call.head,
value: Box::new(Value::nothing(call.head)),
})
}
}
fn examples(&self) -> Vec<Example> {

View File

@ -1,5 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, EvalBlockFn};
use nu_protocol::engine::{Block, Closure};
use nu_protocol::engine::Closure;
#[derive(Clone)]
pub struct Try;
@ -38,15 +38,18 @@ impl Command for Try {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let try_block: Block = call.req(engine_state, stack, 0)?;
let try_block = call
.positional_nth(0)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let catch_block: Option<Closure> = call.opt(engine_state, stack, 1)?;
let try_block = engine_state.get_block(try_block.block_id);
let try_block = engine_state.get_block(try_block);
let eval_block = get_eval_block(engine_state);
let result = eval_block(engine_state, stack, try_block, input);
match result {
match eval_block(engine_state, stack, try_block, input) {
Err(error) => {
let error = intercept_block_control(error)?;
let err_record = err_to_record(error, call.head);
@ -61,9 +64,8 @@ impl Command for Try {
Ok(pipeline) => {
let (pipeline, external_failed) = pipeline.check_external_failed();
if external_failed {
// Because external command errors aren't "real" errors,
// (unless do -c is in effect)
// they can't be passed in as Nushell values.
let exit_code = pipeline.drain_with_exit_code()?;
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(exit_code, call.head));
let err_value = Value::nothing(call.head);
handle_catch(err_value, catch_block, engine_state, stack, eval_block)
} else {

View File

@ -1,3 +1,5 @@
use std::sync::OnceLock;
use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use shadow_rs::shadow;
@ -14,7 +16,7 @@ impl Command for Version {
fn signature(&self) -> Signature {
Signature::build("version")
.input_output_types(vec![(Type::Nothing, Type::Record(vec![]))])
.input_output_types(vec![(Type::Nothing, Type::record())])
.allow_variants_without_examples(true)
.category(Category::Core)
}
@ -34,7 +36,7 @@ impl Command for Version {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
version(engine_state, call)
version(engine_state, call.head)
}
fn run_const(
@ -43,7 +45,7 @@ impl Command for Version {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
version(working_set.permanent(), call)
version(working_set.permanent(), call.head)
}
fn examples(&self) -> Vec<Example> {
@ -55,76 +57,63 @@ impl Command for Version {
}
}
pub fn version(engine_state: &EngineState, call: &Call) -> Result<PipelineData, ShellError> {
// Pre-allocate the arrays in the worst case (12 items):
fn push_non_empty(record: &mut Record, name: &str, value: &str, span: Span) {
if !value.is_empty() {
record.push(name, Value::string(value, span))
}
}
pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, ShellError> {
// Pre-allocate the arrays in the worst case (17 items):
// - version
// - major
// - minor
// - patch
// - pre
// - branch
// - commit_hash
// - build_os
// - build_target
// - rust_version
// - rust_channel
// - cargo_version
// - build_time
// - build_rust_channel
// - allocator
// - features
// - installed_plugins
let mut record = Record::with_capacity(12);
let mut record = Record::with_capacity(17);
record.push(
"version",
Value::string(env!("CARGO_PKG_VERSION"), call.head),
);
record.push("version", Value::string(env!("CARGO_PKG_VERSION"), span));
record.push("branch", Value::string(build::BRANCH, call.head));
push_version_numbers(&mut record, span);
let commit_hash = option_env!("NU_COMMIT_HASH");
if let Some(commit_hash) = commit_hash {
record.push("commit_hash", Value::string(commit_hash, call.head));
push_non_empty(&mut record, "pre", build::PKG_VERSION_PRE, span);
record.push("branch", Value::string(build::BRANCH, span));
if let Some(commit_hash) = option_env!("NU_COMMIT_HASH") {
record.push("commit_hash", Value::string(commit_hash, span));
}
let build_os = Some(build::BUILD_OS).filter(|x| !x.is_empty());
if let Some(build_os) = build_os {
record.push("build_os", Value::string(build_os, call.head));
}
let build_target = Some(build::BUILD_TARGET).filter(|x| !x.is_empty());
if let Some(build_target) = build_target {
record.push("build_target", Value::string(build_target, call.head));
}
let rust_version = Some(build::RUST_VERSION).filter(|x| !x.is_empty());
if let Some(rust_version) = rust_version {
record.push("rust_version", Value::string(rust_version, call.head));
}
let rust_channel = Some(build::RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(rust_channel) = rust_channel {
record.push("rust_channel", Value::string(rust_channel, call.head));
}
let cargo_version = Some(build::CARGO_VERSION).filter(|x| !x.is_empty());
if let Some(cargo_version) = cargo_version {
record.push("cargo_version", Value::string(cargo_version, call.head));
}
let build_time = Some(build::BUILD_TIME).filter(|x| !x.is_empty());
if let Some(build_time) = build_time {
record.push("build_time", Value::string(build_time, call.head));
}
let build_rust_channel = Some(build::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(build_rust_channel) = build_rust_channel {
record.push(
push_non_empty(&mut record, "build_os", build::BUILD_OS, span);
push_non_empty(&mut record, "build_target", build::BUILD_TARGET, span);
push_non_empty(&mut record, "rust_version", build::RUST_VERSION, span);
push_non_empty(&mut record, "rust_channel", build::RUST_CHANNEL, span);
push_non_empty(&mut record, "cargo_version", build::CARGO_VERSION, span);
push_non_empty(&mut record, "build_time", build::BUILD_TIME, span);
push_non_empty(
&mut record,
"build_rust_channel",
Value::string(build_rust_channel, call.head),
build::BUILD_RUST_CHANNEL,
span,
);
}
record.push("allocator", Value::string(global_allocator(), call.head));
record.push("allocator", Value::string(global_allocator(), span));
record.push(
"features",
Value::string(features_enabled().join(", "), call.head),
Value::string(features_enabled().join(", "), span),
);
// Get a list of plugin names
@ -136,10 +125,26 @@ pub fn version(engine_state: &EngineState, call: &Call) -> Result<PipelineData,
record.push(
"installed_plugins",
Value::string(installed_plugins.join(", "), call.head),
Value::string(installed_plugins.join(", "), span),
);
Ok(Value::record(record, call.head).into_pipeline_data())
Ok(Value::record(record, span).into_pipeline_data())
}
/// Add version numbers as integers to the given record
fn push_version_numbers(record: &mut Record, head: Span) {
static VERSION_NUMBERS: OnceLock<(u8, u8, u8)> = OnceLock::new();
let &(major, minor, patch) = VERSION_NUMBERS.get_or_init(|| {
(
build::PKG_VERSION_MAJOR.parse().expect("Always set"),
build::PKG_VERSION_MINOR.parse().expect("Always set"),
build::PKG_VERSION_PATCH.parse().expect("Always set"),
)
});
record.push("major", Value::int(major.into(), head));
record.push("minor", Value::int(minor.into(), head));
record.push("patch", Value::int(patch.into(), head));
}
fn global_allocator() -> &'static str {
@ -180,9 +185,9 @@ fn features_enabled() -> Vec<String> {
names.push("static-link-openssl".to_string());
}
#[cfg(feature = "wasi")]
#[cfg(feature = "system-clipboard")]
{
names.push("wasi".to_string());
names.push("system-clipboard".to_string());
}
names.sort();

View File

@ -1,5 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
use nu_protocol::engine::Block;
#[derive(Clone)]
pub struct While;
@ -38,7 +37,11 @@ impl Command for While {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cond = call.positional_nth(0).expect("checked through parser");
let block: Block = call.req(engine_state, stack, 1)?;
let block_id = call
.positional_nth(1)
.expect("checked through parser")
.as_block()
.expect("internal error: missing block");
let eval_expression = get_eval_expression(engine_state);
let eval_block = get_eval_block(engine_state);
@ -55,7 +58,7 @@ impl Command for While {
match &result {
Value::Bool { val, .. } => {
if *val {
let block = engine_state.get_block(block.block_id);
let block = engine_state.get_block(block_id);
match eval_block(engine_state, stack, block, PipelineData::empty()) {
Err(ShellError::Break { .. }) => {

View File

@ -63,9 +63,6 @@ pub fn create_default_context() -> EngineState {
While,
};
//#[cfg(feature = "plugin")]
bind_command!(PluginCommand, PluginList, PluginStop, Register,);
working_set.render()
};

View File

@ -1,11 +1,15 @@
use itertools::Itertools;
use nu_engine::command_prelude::*;
use nu_protocol::{
ast::{Block, RangeInclusion},
ast::Block,
debugger::WithoutDebug,
engine::{StateDelta, StateWorkingSet},
Range,
};
use std::{
sync::Arc,
{collections::HashSet, ops::Bound},
};
use std::{collections::HashSet, sync::Arc};
pub fn check_example_input_and_output_types_match_command_signature(
example: &Example,
@ -219,17 +223,45 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
Value::Date { val, .. } => {
write!(f, "Date({:?})", val)
}
Value::Range { val, .. } => match val.inclusion {
RangeInclusion::Inclusive => write!(
Value::Range { val, .. } => match val {
Range::IntRange(range) => match range.end() {
Bound::Included(end) => write!(
f,
"Range({:?}..{:?}, step: {:?})",
val.from, val.to, val.incr
range.start(),
end,
range.step(),
),
RangeInclusion::RightExclusive => write!(
Bound::Excluded(end) => write!(
f,
"Range({:?}..<{:?}, step: {:?})",
val.from, val.to, val.incr
range.start(),
end,
range.step(),
),
Bound::Unbounded => {
write!(f, "Range({:?}.., step: {:?})", range.start(), range.step())
}
},
Range::FloatRange(range) => match range.end() {
Bound::Included(end) => write!(
f,
"Range({:?}..{:?}, step: {:?})",
range.start(),
end,
range.step(),
),
Bound::Excluded(end) => write!(
f,
"Range({:?}..<{:?}, step: {:?})",
range.start(),
end,
range.step(),
),
Bound::Unbounded => {
write!(f, "Range({:?}.., step: {:?})", range.start(), range.step())
}
},
},
Value::String { val, .. } | Value::Glob { val, .. } => {
write!(f, "{:?}", val)
@ -256,9 +288,6 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
}
write!(f, "]")
}
Value::Block { val, .. } => {
write!(f, "Block({:?})", val)
}
Value::Closure { val, .. } => {
write!(f, "Closure({:?})", val)
}

View File

@ -0,0 +1,20 @@
[package]
authors = ["The Nushell Project Developers"]
description = "Commands for managing Nushell plugins."
edition = "2021"
license = "MIT"
name = "nu-cmd-plugin"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
version = "0.93.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.93.0" }
nu-path = { path = "../nu-path", version = "0.93.0" }
nu-protocol = { path = "../nu-protocol", version = "0.93.0", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.0" }
itertools = { workspace = true }
[dev-dependencies]

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 - 2023 The Nushell Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,3 @@
# nu-cmd-plugin
This crate implements Nushell commands related to plugin management.

View File

@ -0,0 +1,5 @@
mod plugin;
mod register;
pub use plugin::*;
pub use register::Register;

View File

@ -0,0 +1,132 @@
use std::sync::Arc;
use nu_engine::{command_prelude::*, current_dir};
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
use nu_protocol::{PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin};
use crate::util::{get_plugin_dirs, modify_plugin_file};
#[derive(Clone)]
pub struct PluginAdd;
impl Command for PluginAdd {
fn name(&self) -> &str {
"plugin add"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::Nothing, Type::Nothing)
// This matches the option to `nu`
.named(
"plugin-config",
SyntaxShape::Filepath,
"Use a plugin registry file other than the one set in `$nu.plugin-path`",
None,
)
.named(
"shell",
SyntaxShape::Filepath,
"Use an additional shell program (cmd, sh, python, etc.) to run the plugin",
Some('s'),
)
.required(
"filename",
SyntaxShape::Filepath,
"Path to the executable for the plugin",
)
.category(Category::Plugin)
}
fn usage(&self) -> &str {
"Add a plugin to the plugin registry file."
}
fn extra_usage(&self) -> &str {
r#"
This does not load the plugin commands into the scope - see `register` for that.
Instead, it runs the plugin to get its command signatures, and then edits the
plugin registry file (by default, `$nu.plugin-path`). The changes will be
apparent the next time `nu` is next launched with that plugin registry file.
"#
.trim()
}
fn search_terms(&self) -> Vec<&str> {
vec!["load", "register", "signature"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "plugin add nu_plugin_inc",
description: "Run the `nu_plugin_inc` plugin from the current directory or $env.NU_PLUGIN_DIRS and install its signatures.",
result: None,
},
Example {
example: "plugin add --plugin-config polars.msgpackz nu_plugin_polars",
description: "Run the `nu_plugin_polars` plugin from the current directory or $env.NU_PLUGIN_DIRS, and install its signatures to the \"polars.msgpackz\" plugin registry file.",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let filename: Spanned<String> = call.req(engine_state, stack, 0)?;
let shell: Option<Spanned<String>> = call.get_flag(engine_state, stack, "shell")?;
let cwd = current_dir(engine_state, stack)?;
// Check the current directory, or fall back to NU_PLUGIN_DIRS
let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
get_plugin_dirs(engine_state, stack)
})
.err_span(filename.span)?;
let shell_expanded = shell
.as_ref()
.map(|s| nu_path::canonicalize_with(&s.item, &cwd).err_span(s.span))
.transpose()?;
// Parse the plugin filename so it can be used to spawn the plugin
let identity = PluginIdentity::new(filename_expanded, shell_expanded).map_err(|_| {
ShellError::GenericError {
error: "Plugin filename is invalid".into(),
msg: "plugin executable files must start with `nu_plugin_`".into(),
span: Some(filename.span),
help: None,
inner: vec![],
}
})?;
let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
// Start the plugin manually, to get the freshest signatures and to not affect engine
// state. Provide a GC config that will stop it ASAP
let plugin = Arc::new(PersistentPlugin::new(
identity,
PluginGcConfig {
enabled: true,
stop_after: 0,
},
));
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
let commands = interface.get_signature()?;
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
// Update the file with the received signatures
let item = PluginRegistryItem::new(plugin.identity(), commands);
contents.upsert_plugin(item);
Ok(())
})?;
Ok(Value::nothing(call.head).into_pipeline_data())
}
}

View File

@ -13,22 +13,29 @@ impl Command for PluginList {
Signature::build("plugin list")
.input_output_type(
Type::Nothing,
Type::Table(vec![
Type::Table(
[
("name".into(), Type::String),
("is_running".into(), Type::Bool),
("pid".into(), Type::Int),
("filename".into(), Type::String),
("shell".into(), Type::String),
("commands".into(), Type::List(Type::String.into())),
]),
]
.into(),
),
)
.category(Category::Core)
.category(Category::Plugin)
}
fn usage(&self) -> &str {
"List installed plugins."
}
fn search_terms(&self) -> Vec<&str> {
vec!["scope"]
}
fn examples(&self) -> Vec<nu_protocol::Example> {
vec![
Example {

View File

@ -1,5 +1,17 @@
use nu_engine::{command_prelude::*, get_full_help};
mod add;
mod list;
mod rm;
mod stop;
mod use_;
pub use add::PluginAdd;
pub use list::PluginList;
pub use rm::PluginRm;
pub use stop::PluginStop;
pub use use_::PluginUse;
#[derive(Clone)]
pub struct PluginCommand;
@ -11,17 +23,13 @@ impl Command for PluginCommand {
fn signature(&self) -> Signature {
Signature::build("plugin")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.category(Category::Core)
.category(Category::Plugin)
}
fn usage(&self) -> &str {
"Commands for managing plugins."
}
fn extra_usage(&self) -> &str {
"To load a plugin, see `register`."
}
fn run(
&self,
engine_state: &EngineState,
@ -44,6 +52,21 @@ impl Command for PluginCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "plugin add nu_plugin_inc",
description: "Run the `nu_plugin_inc` plugin from the current directory and install its signatures.",
result: None,
},
Example {
example: "plugin use inc",
description: "
Load (or reload) the `inc` plugin from the plugin registry file and put its
commands in scope. The plugin must already be in the registry file at parse
time.
"
.trim(),
result: None,
},
Example {
example: "plugin list",
description: "List installed plugins",
@ -54,6 +77,11 @@ impl Command for PluginCommand {
description: "Stop the plugin named `inc`.",
result: None,
},
Example {
example: "plugin rm inc",
description: "Remove the installed signatures for the `inc` plugin.",
result: None,
},
]
}
}

View File

@ -0,0 +1,113 @@
use nu_engine::command_prelude::*;
use crate::util::{canonicalize_possible_filename_arg, modify_plugin_file};
#[derive(Clone)]
pub struct PluginRm;
impl Command for PluginRm {
fn name(&self) -> &str {
"plugin rm"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::Nothing, Type::Nothing)
// This matches the option to `nu`
.named(
"plugin-config",
SyntaxShape::Filepath,
"Use a plugin registry file other than the one set in `$nu.plugin-path`",
None,
)
.switch(
"force",
"Don't cause an error if the plugin name wasn't found in the file",
Some('f'),
)
.required(
"name",
SyntaxShape::String,
"The name, or filename, of the plugin to remove",
)
.category(Category::Plugin)
}
fn usage(&self) -> &str {
"Remove a plugin from the plugin registry file."
}
fn extra_usage(&self) -> &str {
r#"
This does not remove the plugin commands from the current scope or from `plugin
list` in the current shell. It instead removes the plugin from the plugin
registry file (by default, `$nu.plugin-path`). The changes will be apparent the
next time `nu` is launched with that plugin registry file.
This can be useful for removing an invalid plugin signature, if it can't be
fixed with `plugin add`.
"#
.trim()
}
fn search_terms(&self) -> Vec<&str> {
vec!["remove", "delete", "signature"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "plugin rm inc",
description: "Remove the installed signatures for the `inc` plugin.",
result: None,
},
Example {
example: "plugin rm ~/.cargo/bin/nu_plugin_inc",
description: "Remove the installed signatures for the plugin with the filename `~/.cargo/bin/nu_plugin_inc`.",
result: None,
},
Example {
example: "plugin rm --plugin-config polars.msgpackz polars",
description: "Remove the installed signatures for the `polars` plugin from the \"polars.msgpackz\" plugin registry file.",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
let force = call.has_flag(engine_state, stack, "force")?;
let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item);
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
if let Some(index) = contents
.plugins
.iter()
.position(|p| p.name == name.item || p.filename == filename)
{
contents.plugins.remove(index);
Ok(())
} else if force {
Ok(())
} else {
Err(ShellError::GenericError {
error: format!("Failed to remove the `{}` plugin", name.item),
msg: "couldn't find a plugin with this name in the registry file".into(),
span: Some(name.span),
help: None,
inner: vec![],
})
}
})?;
Ok(Value::nothing(call.head).into_pipeline_data())
}
}

View File

@ -1,5 +1,7 @@
use nu_engine::command_prelude::*;
use crate::util::canonicalize_possible_filename_arg;
#[derive(Clone)]
pub struct PluginStop;
@ -14,9 +16,9 @@ impl Command for PluginStop {
.required(
"name",
SyntaxShape::String,
"The name of the plugin to stop.",
"The name, or filename, of the plugin to stop",
)
.category(Category::Core)
.category(Category::Plugin)
}
fn usage(&self) -> &str {
@ -30,6 +32,11 @@ impl Command for PluginStop {
description: "Stop the plugin named `inc`.",
result: None,
},
Example {
example: "plugin stop ~/.cargo/bin/nu_plugin_inc",
description: "Stop the plugin with the filename `~/.cargo/bin/nu_plugin_inc`.",
result: None,
},
Example {
example: "plugin list | each { |p| plugin stop $p.name }",
description: "Stop all plugins.",
@ -47,9 +54,12 @@ impl Command for PluginStop {
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item);
let mut found = false;
for plugin in engine_state.plugins() {
if plugin.identity().name() == name.item {
let id = &plugin.identity();
if id.name() == name.item || id.filename() == filename {
plugin.stop()?;
found = true;
}

View File

@ -0,0 +1,89 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct PluginUse;
impl Command for PluginUse {
fn name(&self) -> &str {
"plugin use"
}
fn usage(&self) -> &str {
"Load a plugin from the plugin registry file into scope."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.named(
"plugin-config",
SyntaxShape::Filepath,
"Use a plugin registry file other than the one set in `$nu.plugin-path`",
None,
)
.required(
"name",
SyntaxShape::String,
"The name, or filename, of the plugin to load",
)
.category(Category::Plugin)
}
fn extra_usage(&self) -> &str {
r#"
This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html
The plugin definition must be available in the plugin registry file at parse
time. Run `plugin add` first in the REPL to do this, or from a script consider
preparing a plugin registry file and passing `--plugin-config`, or using the
`--plugin` option to `nu` instead.
If the plugin was already loaded, this will reload the latest definition from
the registry file into scope.
Note that even if the plugin filename is specified, it will only be loaded if
it was already previously registered with `plugin add`.
"#
.trim()
}
fn search_terms(&self) -> Vec<&str> {
vec!["add", "register", "scope"]
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Load the commands for the `query` plugin from $nu.plugin-path",
example: r#"plugin use query"#,
result: None,
},
Example {
description: "Load the commands for the plugin with the filename `~/.cargo/bin/nu_plugin_query` from $nu.plugin-path",
example: r#"plugin use ~/.cargo/bin/nu_plugin_query"#,
result: None,
},
Example {
description:
"Load the commands for the `query` plugin from a custom plugin registry file",
example: r#"plugin use --plugin-config local-plugins.msgpackz query"#,
result: None,
},
]
}
}

View File

@ -31,12 +31,21 @@ impl Command for Register {
"path of shell used to run plugin (cmd, sh, python, etc)",
Some('s'),
)
.category(Category::Core)
.category(Category::Plugin)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
r#"
Deprecated in favor of `plugin add` and `plugin use`.
This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html
"#
.trim()
}
fn search_terms(&self) -> Vec<&str> {
vec!["add"]
}
fn is_parser_keyword(&self) -> bool {

View File

@ -0,0 +1,32 @@
use crate::*;
use nu_protocol::engine::{EngineState, StateWorkingSet};
pub fn add_plugin_command_context(mut engine_state: EngineState) -> EngineState {
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
macro_rules! bind_command {
( $( $command:expr ),* $(,)? ) => {
$( working_set.add_decl(Box::new($command)); )*
};
}
bind_command!(
PluginAdd,
PluginCommand,
PluginList,
PluginRm,
PluginStop,
PluginUse,
Register,
);
working_set.render()
};
if let Err(err) = engine_state.merge_delta(delta) {
eprintln!("Error creating default context: {err:?}");
}
engine_state
}

View File

@ -0,0 +1,8 @@
//! Nushell commands for managing plugins.
mod commands;
mod default_context;
mod util;
pub use commands::*;
pub use default_context::*;

View File

@ -0,0 +1,89 @@
use std::{
fs::{self, File},
path::PathBuf,
};
use nu_engine::{command_prelude::*, current_dir};
use nu_protocol::{engine::StateWorkingSet, PluginRegistryFile};
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 cwd = current_dir(engine_state, stack)?;
let plugin_registry_file_path = if let Some(ref custom_path) = custom_path {
nu_path::expand_path_with(&custom_path.item, cwd, true)
} else {
engine_state
.plugin_path
.clone()
.ok_or_else(|| ShellError::GenericError {
error: "Plugin registry file not set".into(),
msg: "pass --plugin-config explicitly here".into(),
span: Some(span),
help: Some("you may be running `nu` with --no-config-file".into()),
inner: vec![],
})?
};
// Try to read the plugin file if it exists
let mut contents = if fs::metadata(&plugin_registry_file_path).is_ok_and(|m| m.len() > 0) {
PluginRegistryFile::read_from(
File::open(&plugin_registry_file_path).err_span(span)?,
Some(span),
)?
} else {
PluginRegistryFile::default()
};
// Do the operation
operate(&mut contents)?;
// Save the modified file on success
contents.write_to(
File::create(&plugin_registry_file_path).err_span(span)?,
Some(span),
)?;
Ok(())
}
pub(crate) fn canonicalize_possible_filename_arg(
engine_state: &EngineState,
stack: &Stack,
arg: &str,
) -> PathBuf {
// This results in the best possible chance of a match with the plugin item
if let Ok(cwd) = nu_engine::current_dir(engine_state, stack) {
let path = nu_path::expand_path_with(arg, &cwd, true);
// Try to canonicalize
nu_path::locate_in_dirs(&path, &cwd, || get_plugin_dirs(engine_state, stack))
// If we couldn't locate it, return the expanded path alone
.unwrap_or(path)
} else {
arg.into()
}
}
pub(crate) fn get_plugin_dirs(
engine_state: &EngineState,
stack: &Stack,
) -> impl Iterator<Item = String> {
// Get the NU_PLUGIN_DIRS constant or env var
let working_set = StateWorkingSet::new(engine_state);
let value = working_set
.find_variable(b"$NU_PLUGIN_DIRS")
.and_then(|var_id| working_set.get_constant(var_id).ok().cloned())
.or_else(|| stack.get_env_var(engine_state, "NU_PLUGIN_DIRS"));
// Get all of the strings in the list, if possible
value
.into_iter()
.flat_map(|value| value.into_list().ok())
.flatten()
.flat_map(|list_item| list_item.coerce_into_string().ok())
}

View File

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

View File

@ -1,11 +1,10 @@
use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignment, TextStyle};
use nu_ansi_term::{Color, Style};
use nu_engine::{env::get_config, eval_block};
use nu_engine::{env::get_config, ClosureEvalOnce};
use nu_protocol::{
cli_error::CliError,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
IntoPipelineData, Value,
engine::{Closure, EngineState, Stack, StateWorkingSet},
Span, Value,
};
use std::{
collections::HashMap,
@ -18,7 +17,7 @@ use std::{
#[derive(Debug, Clone)]
pub enum ComputableStyle {
Static(Style),
Closure(Value),
Closure(Closure, Span),
}
// An alias for the mapping used internally by StyleComputer.
@ -56,31 +55,14 @@ impl<'a> StyleComputer<'a> {
// Static values require no computation.
Some(ComputableStyle::Static(s)) => *s,
// Closures are run here.
Some(ComputableStyle::Closure(v)) => {
let span = v.span();
match v {
Value::Closure { val, .. } => {
let block = self.engine_state.get_block(val.block_id).clone();
// Because captures_to_stack() clones, we don't need to use with_env() here
// (contrast with_env() usage in `each` or `do`).
let mut stack = self.stack.captures_to_stack(val.captures.clone());
Some(ComputableStyle::Closure(closure, span)) => {
let result = ClosureEvalOnce::new(self.engine_state, self.stack, closure.clone())
.debug(false)
.run_with_value(value.clone());
// Support 1-argument blocks as well as 0-argument blocks.
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
}
// Run the block.
match eval_block::<WithoutDebug>(
self.engine_state,
&mut stack,
&block,
value.clone().into_pipeline_data(),
) {
match result {
Ok(v) => {
let value = v.into_value(span);
let value = v.into_value(*span);
// These should be the same color data forms supported by color_config.
match value {
Value::Record { .. } => color_record_to_nustyle(&value),
@ -100,9 +82,6 @@ impl<'a> StyleComputer<'a> {
}
}
}
_ => Style::default(),
}
}
// There should be no other kinds of values (due to create_map() in config.rs filtering them out)
// so this is just a fallback.
_ => Style::default(),
@ -126,9 +105,7 @@ impl<'a> StyleComputer<'a> {
Value::Nothing { .. } => TextStyle::with_style(Left, s),
Value::Binary { .. } => TextStyle::with_style(Left, s),
Value::CellPath { .. } => TextStyle::with_style(Left, s),
Value::Record { .. } | Value::List { .. } | Value::Block { .. } => {
TextStyle::with_style(Left, s)
}
Value::Record { .. } | Value::List { .. } => TextStyle::with_style(Left, s),
Value::Closure { .. }
| Value::Custom { .. }
| Value::Error { .. }
@ -167,9 +144,10 @@ impl<'a> StyleComputer<'a> {
].into_iter().collect();
for (key, value) in &config.color_config {
let span = value.span();
match value {
Value::Closure { .. } => {
map.insert(key.to_string(), ComputableStyle::Closure(value.clone()));
Value::Closure { val, .. } => {
map.insert(key.to_string(), ComputableStyle::Closure(val.clone(), span));
}
Value::Record { .. } => {
map.insert(

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.92.1"
version = "0.93.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,24 +13,26 @@ version = "0.92.1"
bench = false
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.92.1" }
nu-color-config = { path = "../nu-color-config", version = "0.92.1" }
nu-engine = { path = "../nu-engine", version = "0.92.1" }
nu-glob = { path = "../nu-glob", version = "0.92.1" }
nu-json = { path = "../nu-json", version = "0.92.1" }
nu-parser = { path = "../nu-parser", version = "0.92.1" }
nu-path = { path = "../nu-path", version = "0.92.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.92.1" }
nu-protocol = { path = "../nu-protocol", version = "0.92.1" }
nu-system = { path = "../nu-system", version = "0.92.1" }
nu-table = { path = "../nu-table", version = "0.92.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.92.1" }
nu-utils = { path = "../nu-utils", version = "0.92.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.0" }
nu-color-config = { path = "../nu-color-config", version = "0.93.0" }
nu-engine = { path = "../nu-engine", version = "0.93.0" }
nu-glob = { path = "../nu-glob", version = "0.93.0" }
nu-json = { path = "../nu-json", version = "0.93.0" }
nu-parser = { path = "../nu-parser", version = "0.93.0" }
nu-path = { path = "../nu-path", version = "0.93.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.0" }
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
nu-system = { path = "../nu-system", version = "0.93.0" }
nu-table = { path = "../nu-table", version = "0.93.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.93.0" }
nu-utils = { path = "../nu-utils", version = "0.93.0" }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.93.0" }
alphanumeric-sort = { workspace = true }
base64 = { workspace = true }
bracoxide = { workspace = true }
brotli = { workspace = true }
byteorder = { workspace = true }
bytesize = { workspace = true }
calamine = { workspace = true, features = ["dates"] }
@ -73,6 +75,7 @@ rayon = { workspace = true }
regex = { workspace = true }
roxmltree = { workspace = true }
rusqlite = { workspace = true, features = ["bundled", "backup", "chrono"], optional = true }
rmp = { workspace = true }
same-file = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
@ -134,11 +137,12 @@ trash-support = ["trash"]
which-support = ["which"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.92.1" }
nu-test-support = { path = "../nu-test-support", version = "0.92.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.0" }
nu-test-support = { path = "../nu-test-support", version = "0.93.0" }
dirs-next = { workspace = true }
mockito = { workspace = true, default-features = false }
quickcheck = { workspace = true }
quickcheck_macros = { workspace = true }
rstest = { workspace = true, default-features = false }
pretty_assertions = { workspace = true }

View File

@ -31,8 +31,8 @@ impl Command for BytesAdd {
Type::List(Box::new(Type::Binary)),
Type::List(Box::new(Type::Binary)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.required("data", SyntaxShape::Binary, "The binary to add.")

View File

@ -41,8 +41,8 @@ impl Command for BytesAt {
Type::List(Box::new(Type::Binary)),
Type::List(Box::new(Type::Binary)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.required("range", SyntaxShape::Range, "The range to get bytes.")
.rest(

View File

@ -24,8 +24,8 @@ impl Command for BytesEndsWith {
fn signature(&self) -> Signature {
Signature::build("bytes ends-with")
.input_output_types(vec![(Type::Binary, Type::Bool),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.required("pattern", SyntaxShape::Binary, "The pattern to match.")

View File

@ -28,8 +28,8 @@ impl Command for BytesIndexOf {
(Type::Binary, Type::Any),
// FIXME: this shouldn't be needed, cell paths should work with the two
// above
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.required(

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