Compare commits

...

169 Commits

Author SHA1 Message Date
Cole Cecil
520f11fb8f
docs: Add vfox to list of tools supporting Nushell (#15687)
This change adds [vfox](https://github.com/version-fox/vfox) to the list
of tools that support Nushell in the readme.

This is a tool for managing multiple versions of SDKs (similar to
[asdf](https://asdf-vm.com/), but cross-platform). After some work by me
and another contributor (see
https://github.com/version-fox/vfox/issues/207), vfox now works in
Nushell!
2025-05-04 20:56:10 -05:00
Bruce Weirdan
39b95fc59e
Environment-aware help for open and save (#15651)
# Description

This extends the documentation on the commands `open` and `save` can run
under the hood, and explicitly lists those, based on the current user
environment.

Also see [this discord
thread](https://discord.com/channels/601130461678272522/988303282931912704/1364930487092777020)

# User-Facing Changes

Users will be able to see the list of commands that `open` and `save`
can run, and the extensions that each command is run for, in `help open`
and `help save` respectively:

## `help open`

![image](https://github.com/user-attachments/assets/b245d12c-c6ef-4c6d-a9f1-6c5111cb0684)

## `help save`

![image](https://github.com/user-attachments/assets/e92ddb6b-6a1e-40cc-9139-78db8a921d4a)


# Tests + Formatting

All pass except for the ones that don't (and never did pass for me
before).

# After Submitting

No updates needed.
2025-05-03 17:07:39 -05:00
A. Taha Baki
63e68934f6
Numbers proceeded with the escape character ignored fix (#15684)
Fixes #15675

I've added relevant test cases to ensure coverage of the identified bug.
The issue originated from my crate and pertains to the bracoxide
dependency—a bug I’ve internally referred to as IgnorantNumbers. I’ve
submitted a fix and updated the bracoxide dependency accordingly.
2025-05-03 08:10:51 -05:00
Tim Nieradzik
acc152564c
docs: fix available fields in history import command (#15686)
- The ID field cannot be set (see `item_from_record`)
- Fix command line's field name
2025-05-03 08:09:58 -05:00
German David
8f63db4c95
Add 'single' to supported table modes (#15681)
<!--
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.
-->

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-05-02 16:21:11 -05:00
German David
cb133ed387
feat(table): Add new 'single' table mode (#15672)
<!--
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 #15381

# 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.
-->
Adds a new table mode called `single`, it looks like the `heavy` mode,
but the key difference is that it uses thinner lines. I decided on the
name `single` because it's one of the border styles Neovim uses, and
they look practically the same.

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

New config option:

```nushell
$env.config.table.mode = 'single'
```

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Added new tests in `crates/nu-table/tests/style.rs` to cover the single
table mode.

# 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.
-->
2025-05-01 15:30:57 -05:00
zc he
a7547a54bc
fix(parser): namespace pollution of constants by use module.nu (#15518)
A bug introduced by #14920 

When `use module.nu` is called, all exported constants defined in it are
added to the scope.

# Description

On the branch of empty arguments, the constant var_id vector should be
empty, only constant_values (for `$module.foo` access) are injected.

# User-Facing Changes

# Tests + Formatting

~todo!~

adjusted

# After Submitting
2025-05-01 09:47:16 -05:00
Luong Vo
d1969a3c9a
docs: update ubuntu version in PLATFORM_SUPPORT.md (#15662)
<!--
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 interested in how nu-shell handles glibc, especially older
versions of it. I figured out from the docs that ubuntu 20.04 is
utilized. However, in reality, github has deprecated ubuntu 20.04, and
the code for ci.yaml in github workflow clearly states that it is 22.04.

This is just a minor doc update to clarify forgotten information
2025-05-01 09:44:49 -05:00
pyz4
ce582cdafb
feat(polars): add polars horizontal aggregation command (#15656)
<!--
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.
-->
This PR seeks to port over the `*_horizontal` commands in polars
rust/python (e.g.,
https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.sum_horizontal.html),
which aggregate across multiple columns (as opposed to rows). See below
for several examples.

```nushell
#  Horizontal sum across two columns (ignore nulls by default)
  > [[a b]; [1 2] [2 3] [3 4] [4 5] [5 null]]
                    | polars into-df
                    | polars select (polars horizontal sum a b)
                    | polars collect
  ╭───┬─────╮
  │ # │ sum │
  ├───┼─────┤
  │ 0 │   3 │
  │ 1 │   5 │
  │ 2 │   7 │
  │ 3 │   9 │
  │ 4 │   5 │
  ╰───┴─────╯

#  Horizontal sum across two columns while accounting for nulls
  > [[a b]; [1 2] [2 3] [3 4] [4 5] [5 null]]
                    | polars into-df
                    | polars select (polars horizontal sum a b --nulls)
                    | polars collect
  ╭───┬─────╮
  │ # │ sum │
  ├───┼─────┤
  │ 0 │   3 │
  │ 1 │   5 │
  │ 2 │   7 │
  │ 3 │   9 │
  │ 4 │     │
  ╰───┴─────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users have access to a new command, `polars
horizontal`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-05-01 09:44:15 -05:00
Bahex
55de232a1c
refactor Value::follow_cell_path to reduce clones and return Cow (#15640)
# Description
While working on something else, I noticed that
`Value::follow_cell_path` receives `self`.

While it would be ideal for the signature to be `(&'a self, cell_path)
-> &'a Value`, that's not possible because:
1. Selecting a row from a list and field from a record can be done with
a reference but selecting a column from a table requires creating a new
list.
2. `Value::Custom` returns new `Value`s when indexed.

So the signature becomes `(&'a self, cell_path) -> Cow<'a, Value>`.

Another complication that arises is, once a new `Value` is created, and
it is further indexed, the `current` variable
1. can't be `&'a Value`, as the lifetime requirement means it can't
refer to local variables
2. _shouldn't_ be `Cow<'a, Value>`, as once it becomes an owned value,
it can't be borrowed ever again, as `current` is derived from its
previous value in further iterations. So once it's owned, it can't be
indexed by reference, leading to more clones

We need `current` to have _two_ possible lifetimes
1. `'out`: references derived from `&self`
2. `'local`: references derived from an owned value stored in a local
variable

```rust
enum MultiLife<'out, 'local, T>
where
    'out: 'local,
    T: ?Sized,
{
    Out(&'out T),
    Local(&'local T),
}
```
With `current: MultiLife<'out, '_, Value>`, we can traverse values with
minimal clones, and we can transform it to `Cow<'out, Value>` easily
(`MultiLife::Out -> Cow::Borrowed, MultiLife::Local -> Cow::Owned`) to
return it

# User-Facing Changes

# Tests + Formatting

# After Submitting

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-05-01 09:43:57 -05:00
Maxim Zhiburt
deca337a56
nu-table/ 1 refactoring + a few optimizations + small fix (#15653)
- A few days back I've got this idea regarding recalculus of width.
Now it calculates step by step.
So 1 loop over all data was removed.
All though there's full recalculation in case of `header_on_border`
😞 (can be fixed..... but I decided to be short)

In perfect world it also shall be refactored ......

- Also have done small refactoring to switch build table from
`Vec<Vec<_>>>` to table itself. To hide internals (kind of still there's
things which I don't like).
It touched the `--expand` algorithm lightly you can see the tests
changes.

- And when doing that noticed one more opportunity, to remove HashMap
usage and directly use `tabled::ColoredConfig`. Which reduces copy
operations and allocations.

- And fixed a small issue where trailing column being using deleted
column styles.


![image](https://github.com/user-attachments/assets/19b09dba-c688-4e91-960a-e11ed11fd275)

To conclude optimizations;
I did small testing and it's not slower.
But I didn't get the faster results either.
But I believe it must be faster well in all cases, I think maybe bigger
tables must be tested.
Maybe someone could have a few runs to compare performance.

cc: @fdncred
2025-05-01 09:43:30 -05:00
Tyarel
60e9f469af
change http get header example to use a record (#15674)
# Description

When first using `http get`, I was confused that all the examples used a
list for headers, leading me to believe this was the only way, and it
seemed a little weird having records in the language. Then, I found out
that you can indeed use record, so I changed the example to show this
behavior in a way users can find. There still is another examples that
uses a list so there should be no problem there.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-05-01 09:42:53 -05:00
Doru
b500ac57c2
Update job_recv.rs (#15673)
remove j

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

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-05-01 06:19:32 -05:00
Yash Thakur
eadb8da9f7
Bump to 0.104.1 dev version (#15669)
Marks development or hotfix
2025-04-29 23:33:10 -04:00
Yash Thakur
cda15d91dd
Bump version for 0.104.0 release (#15664) 2025-04-29 19:31:45 -04:00
Yash Thakur
651a8716fb
Pin reedline to 0.40 for 0.104 release (#15663) 2025-04-29 16:32:18 -04:00
Douglas
a1b7574306
Renamed join_where to join-where (#15660)
Renames the new `polars join_where` to `polars join-where` so that it
conforms to the other Polars commands.
2025-04-29 11:17:28 -04:00
Darren Schroeder
09f12b9c4a
bump reedline to 75f2c50 (#15659)
# Description

This PR bumps reedline in nushell to the latest commit in the repo and
thiserror because it wouldn't compile without it, so that we can do some
quick testing to ensure there are no problems.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-29 09:50:48 -05:00
Gabriel de Perthuis
9ae74e3941
Upgrade calamine dependency to fix zip semver breakage (#15657)
See
-
https://github.com/tafia/calamine/blob/master/Changelog.md#0270-2025-04-22
- https://github.com/tafia/calamine/pull/500

Fixes https://github.com/nushell/nushell/issues/15584
2025-04-28 13:58:06 -05:00
Bahex
d8bec8668f
feat(table): make missing value symbol configurable (#15647)
Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-04-27 22:58:39 +02:00
Justin Ma
12ccaf5e33
Update Nu to 0.103.0 for release workflow and improve Windows OS checks (#15625) 2025-04-27 17:44:16 +02:00
Douglas
5fecf59f54
Revert "Fix kv set with a closure argument" (#15648)
Reverts nushell/nushell#15588 (see comments there)
2025-04-26 23:00:00 -04:00
Anish Bhobe
a3aae2d26c
Fix examples about RFC3339 format in date now and format date. (#15563)
Replace example on `date now | debug` with `date now | format date
"%+"`. Add RFC3339 "%+" format string example on `format date`.

Users can now find how to format date-time to RFC3339.

FIXES: #15168

<!--
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.
-->
Documentation will now provide users examples on how to print RFC3339
strings.

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

Corrects documentation.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-26 19:06:08 -05:00
pyz4
d1d6518ece
feat(polars): enable parsing strings as dates and datetime in polars schema (#15645)
<!--
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.
-->
This PR seeks to add a quality-of-life feature that enables date and
datetime parsing of strings in `polars into-df`, `polars into-lazy`, and
`polars open`, and avoid the more verbose method of casting each column
into date/datetime. Currently, setting the schema to `date` on a `str`
column would silently error as a null column. See a comparison of the
current and proposed implementations.

The proposed implementation assumes a date format "%Y-%m-%d" and a
datetime format of "%Y-%m-%d %H:%M:%S" for naive datetimes and "%Y-%m-%d
%H:%M:%S%:z" for timezone-aware datetimes. Other formats must be
specified via parsing through `polars as-date` and `polars as-datetime`.

```nushell
#  Current Implementations
> [[a]; ["2025-04-01"]] | polars into-df --schema {a: date}
╭───┬───╮
│ # │ a │
├───┼───┤
│ 0 │   │
╰───┴───╯

> [[a]; ["2025-04-01 01:00:00"]] | polars into-df --schema {a: "datetime<ns,*>"}
╭───┬───╮
│ # │ a │
├───┼───┤
│ 0 │   │
╰───┴───╯

#  Proposed Implementation
> [[a]; ["2025-04-01"]] | polars into-df --schema {a: date}
╭───┬─────────────────────╮
│ # │          a          │
├───┼─────────────────────┤
│ 0 │ 04/01/25 12:00:00AM │
╰───┴─────────────────────╯

> [[a]; ["2025-04-01 01:00:00"]] | polars into-df --schema {a: "datetime<ns,*>"}
╭───┬─────────────────────╮
│ # │          a          │
├───┼─────────────────────┤
│ 0 │ 04/01/25 01:00:00AM │
╰───┴─────────────────────╯

> [[a]; ["2025-04-01 01:00:00-04:00"]] | polars into-df --schema {a: "datetime<ns,UTC>"}
╭───┬─────────────────────╮
│ # │          a          │
├───┼─────────────────────┤
│ 0 │ 04/01/25 05:00:00AM │
╰───┴─────────────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users have the added option to parse string columns
into date/datetimes.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-26 11:47:58 -07:00
Renan Ribeiro
2d868323b6
Inter-Job direct messaging (#15253)
# Description

This PR implements an experimental inter-job communication model,
through direct message passing, aka "mail"ing or "dm"ing:



- `job send <id>`: Sends a message the job with the given id, the root
job has id 0. Messages are stored in the recipient's "mailbox"
- `job recv`: Returns a stored message, blocks if the mailbox is empty
- `job flush`: Clear all messages from mailbox

Additionally, messages can be sent with a numeric tag, which can then be
filtered with `mail recv --tag`.
This is useful for spawning jobs and receiving messages specifically
from those jobs.

This PR is mostly a proof of concept for how inter-job communication
could look like, so people can provide feedback and suggestions

Closes  #15199

May close #15220 since now jobs can access their own id.

# User-Facing Changes

Adds, `job id`, `job send`, `job recv` and `job flush`  commands.

# Tests + Formatting

[X] TODO:  Implement tests
[X] Consider rewriting some of the job-related tests to use this, to
make them a bit less fragile.

# After Submitting
2025-04-26 23:24:35 +08:00
Bahex
0389815137
docs(explore): Add ":nu" back to the help text (#15644)
# Description
Looks like `:nu` was forgotten about when the help system was
refactored.

# User-Facing Changes

# Tests + Formatting

# After Submitting

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-04-25 10:24:44 -05:00
Wind
11cdb94699
IR: rasing reasonable error when using subexpression with and operator (#15623)
# Description
Fixes: #15510
I think it's introduced by #14653, which changes `and/or` to `match`
expression.

After looking into `compile_match`, it's important to collect the value
before matching this.
```rust
    // Important to collect it first
    builder.push(Instruction::Collect { src_dst: match_reg }.into_spanned(match_expr.span))?;
```
This pr is going to apply the logic while compiling `and/or` operation.

# User-Facing Changes
The following will raise a reasonable error:
```nushell
> (nu --testbin cococo false) and true
Error: nu:🐚:operator_unsupported_type

  × The 'and' operator does not work on values of type 'string'.
   ╭─[entry #7:1:2]
 1 │ (nu --testbin cococo false) and true
   ·  ─┬                         ─┬─
   ·   │                          ╰── does not support 'string'
   ·   ╰── string
   ╰────
```

# Tests + Formatting
Added 1 test.

# After Submitting
Maybe need to update doc
https://github.com/nushell/nushell.github.io/pull/1876

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-04-25 22:00:20 +08:00
Piepmatz
0ca5c2f135
Add cat and get-content to open's search terms (#15643)
<!--
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.
-->

A friend of mine started using nushell on Windows and wondered why the
`cat` command wasn't available. I answered to him, that he can use `help
-f` or F1 to find the command but then we both realized that neither
`cat` nor `Get-Command` were part of `open`'s search terms. So I added
them.

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

None.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

- 🟢 `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.
-->
2025-04-25 06:56:30 -05:00
pyz4
715b0d90a9
fix(polars): conversion from nanoseconds to time_units in Datetime and Duration parsing (#15637)
<!--
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 current implementation improperly inverts the conversion from
nanoseconds to the specified time units, resulting in nonsensical
Datetime and Duration parsing and integer overflows when the specified
time unit is not nanoseconds. This PR seeks to correct this conversion
by changing the multiplication to an integer division. Below are
examples highlighting the current and proposed implementations.

## Current Implementation
Specifying a different time unit incorrectly changes the returned value.
```nushell
> [[a]; [2024-04-01]] | polars into-df --schema {a: "datetime<ns,UTC>"}
╭───┬───────────────────────╮
│ # │           a           │
├───┼───────────────────────┤
│ 0 │ 04/01/2024 12:00:00AM │

> [[a]; [2024-04-01]] | polars into-df --schema {a: "datetime<ms,UTC>"}
╭───┬───────────────────────╮
│ # │           a           │
├───┼───────────────────────┤
│ 0 │ 06/27/2035 11:22:33PM │ <-- changing the time unit should not change the actual value

> [[a]; [1day]] | polars into-df --schema {a: "duration<ns>"}
╭───┬────────────────╮
│ # │       a        │
├───┼────────────────┤
│ 0 │ 86400000000000 │
╰───┴────────────────╯

> [[a]; [1day]] | polars into-df --schema {a: "duration<ms>"}
╭───┬──────────────────────╮
│ # │          a           │
├───┼──────────────────────┤
│ 0 │ -5833720368547758080 │ <-- i64 overflow
╰───┴──────────────────────╯

```

## Proposed Implementation
```nushell
> [[a]; [2024-04-01]] | polars into-df --schema {a: "datetime<ns,UTC>"}
╭───┬───────────────────────╮
│ # │           a           │
├───┼───────────────────────┤
│ 0 │ 04/01/2024 12:00:00AM │
╰───┴───────────────────────╯

> [[a]; [2024-04-01]] | polars into-df --schema {a: "datetime<ms,UTC>"}
╭───┬───────────────────────╮
│ # │           a           │
├───┼───────────────────────┤
│ 0 │ 04/01/2024 12:00:00AM │
╰───┴───────────────────────╯

> [[a]; [1day]] | polars into-df --schema {a: "duration<ns>"}
╭───┬────────────────╮
│ # │       a        │
├───┼────────────────┤
│ 0 │ 86400000000000 │
╰───┴────────────────╯

> [[a]; [1day]] | polars into-df --schema {a: "duration<ms>"}
╭───┬──────────╮
│ # │    a     │
├───┼──────────┤
│ 0 │ 86400000 │
╰───┴──────────╯
```

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

Developer breaking change: to mitigate the silent overflow in
nanoseconds conversion functions `nanos_from_timeunit` and
`nanos_to_timeunit` (new), the function signatures were changed from
`i64` to `Result<i64, ShellError>`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
No additional examples were added, but I'd be happy to add a few if
needed. The covering tests just didn't fit well into any examples.

# 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.
-->
2025-04-24 14:45:36 -07:00
Matthias Meschede
05c36d1bc7
add polars join_where command (#15635)
# Description

This adds `polars join_where` which allows joining two dataframes based
on a conditions. The command can be used as:

```
➜ let df_a = [[name cash];[Alice 5] [Bob 10]] | polars into-lazy
➜ let df_b = [[item price];[A 3] [B 7] [C 12]] | polars into-lazy
➜ $df_a | polars join_where $df_b ((polars col cash) > (polars col price)) | polars collect
╭───┬───────┬──────┬──────┬───────╮
│ # │ name  │ cash │ item │ price │
├───┼───────┼──────┼──────┼───────┤
│ 0 │ Bob   │   10 │ B    │     7 │
│ 1 │ Bob   │   10 │ A    │     3 │
│ 2 │ Alice │    5 │ A    │     3 │
╰───┴───────┴──────┴──────┴───────╯
```

# User-Facing Changes

- new command `polars join_where`
2025-04-24 14:44:29 -07:00
pyz4
208ebeefab
feat(polars): enable parsing decimals in polars schemas (#15632)
<!--
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.
-->
This PR enables the option to set a column type to `decimal` in the
`--schema` parameter of `polars into-df` and `polars into-lazy`
commands. This option was already available in `polars open`, which used
the underlying polars io commands that already accounted for decimal
types when specified in the schema.

See below for a comparison of the current and proposed implementation.

```nushell
#  Current Implementation
> [[a b]; [1 1.618]]| polars into-df -s {a: u8, b: 'decimal<4,3>'}
Error:   × Error creating dataframe: Unsupported type: Decimal(Some(4), Some(3))

#  Proposed Implementation
> [[a b]; [1 1.618]]| polars into-df -s {a: u8, b: 'decimal<4,3>'} | polars schema
╭───┬──────────────╮
│ a │ u8           │
│ b │ decimal<4,3> │
╰───┴──────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking change. Users has the new option to specify decimal in
`--schema` in `polars into-df` and `polars into-lazy`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
An example in `polars into-df` was modified to showcase the decimal
type.

# 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.
-->
2025-04-24 14:43:28 -07:00
Hayden Frentzel
b33f4b7f55
Run scripts of any file extension in PATHEXT on Windows (#15611)
# Description
On Windows, I would like to be able to call a script directly in nushell
and have that script be found in the PATH and run based on filetype
associations and PATHEXT.

There have been previous discussions related to this feature, see
https://github.com/nushell/nushell/issues/6440 and
https://github.com/nushell/nushell/issues/15476. The latter issue is
only a few weeks old, and after taking a look at it and the resultant PR
I found that currently nushell is hardcoded to support only running
nushell (.nu) scripts in this way.

This PR seeks to make this functionality more generic. Instead of
checking that the file extension is explicitly `NU`, it instead checks
that it **is not** one of `COM`, `EXE`, `BAT`, `CMD`, or `PS1`. The
first four of these are extensions that Windows can figure out how to
run on its own. This is implied by the output of `ftype` for any of
these extensions, which shows that files are just run without a calling
command anyway.
```
>ftype batfile
batfile="%1" %*
```
PS1 files are ignored because they are handled as a special in later
logic.

In implementing this I initially tried to fetch the value of PATHEXT and
confirm that the file extension was indeed in PATHEXT. But I determined
that because `which()` respects PATHEXT, this would be redundant; any
executable that is found by `which` is already going to have an
extension in PATHEXT. It is thus only necessary to check that it isn't
one of the few extensions that should be called directly, without the
use of `cmd.exe`.


There are some small formatting changes to `run_external.rs` in the PR
as a result of running `cargo fmt` that are not entirely related to the
code I modified. I can back out those changes if that is desired.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Behavior for `.nu` scripts will not change. Users will still need to
ensure they have PATHEXT and filetype associations set correctly for
them to work, but this will now also apply to scripts of other types.
2025-04-24 09:10:34 -05:00
Marco Cunha
f41b1460aa
Fix #14660: to md breaks on tables with empty values (#15631)
Fixes #14660

# Description
Fixed an issue where tables with empty values were incorrectly replaced
with [table X row] when converted to Markdown using the ```to md```
command.
Empty values are now replaced with whitespaces to preserve the original
table structure.
Additionally, fixed a missing newline (\n) between tables when using
--per-element in a list.
Removed (\n) from 2 examples for consistency.

Example:

```
For the list
let list = [ {name: bob, age: 21} {name: jim, age: 20} {name: sarah}]

Running "$list | to md --pretty" outputs:

| name  | age |
| ----- | --- |
| bob   | 21  |
| jim   | 20  |
| sarah |     |

------------------------------------------------------------------------------------------------

For the list
let list = [ {name: bob, age: 21} {name: jim, age: 20} {name: sarah} {name: timothy, age: 50} {name: paul} ]

Running "$list | to md --per-element --pretty" outputs:

| name    | age |
| ------- | --- |
| bob     | 21  |
| jim     | 20  |
| timothy | 50  |
| name  |
| ----- |
| sarah |
| paul  |
```

# User-Facing Changes
The ```to md``` behaves as expected when piping a table that contains
empty values showing all rows and the empty items replaced with
whitespace.

# Tests + Formatting
Added 2 test cases to cover both issues.
fmt + clippy OK.

# After Submitting
The command documentation needs to be updated with an example for when
you want to "separate list into markdown tables"
2025-04-24 09:09:48 -05:00
Loïc Riegel
220858d641
history table using sqlite outputs start_timestamp as datetime instead of string (#15630)
Closes #13581

# Description
Before, the table you got from ``history`` had values as strings in the
``startup_timestamp`` column.
Now the values are datetimes.

# User-Facing Changes
```nushell
~\workspace_tns\nushell> history | last 5
╭───┬─────────────────┬─────────────────────┬───────────────────────────────────────────┬─────╮
│ # │ start_timestamp │       command       │                    cwd                    │ ... │
├───┼─────────────────┼─────────────────────┼───────────────────────────────────────────┼─────┤
│ 0 │ a minute ago    │ history             │ C:\Users\RIL1RT\workspace_tns\nushell-bis │ ... │
│ 1 │ 40 seconds ago  │ cd nushell          │ C:\Users\RIL1RT\workspace_tns\nushell-bis │ ... │
│ 2 │ 31 seconds ago  │ target\debug\nu.exe │ C:\Users\RIL1RT\workspace_tns\nushell     │ ... │
│ 3 │ 26 seconds ago  │ history             │ C:\Users\RIL1RT\workspace_tns\nushell     │ ... │
│ 4 │ now             │ history | last 5    │ C:\Users\RIL1RT\workspace_tns\nushell     │ ... │
╰───┴─────────────────┴─────────────────────┴───────────────────────────────────────────┴─────╯
```

# Tests + Formatting


# After Submitting
2025-04-24 08:33:13 -05:00
Loïc Riegel
db261e3ed9
bugfix: str join outputs dates consistently (RFC2822 when possible) (#15629)
Closes #11265

# Description
``str join`` outputs dates just other commands: RFC2822 by default
otherwise RFC3339 for negative dates

# User-Facing Changes

```nushell
~> 2024-01-01
# => Mon, 1 Jan 2024 00:00:00 +0000 (a year ago)
~> '3000 years ago' | date from-human
# => -0975-04-23T20:57:07.217711700+02:00 (3000 years ago)
~> [ 2024-01-01 ] | str join
# => Mon, 1 Jan 2024 00:00:00 +0000
~> [ ('3000 years ago' | date from-human) ] | str join
# => -0975-04-23T20:57:56.221269600+02:00
```

# Tests + Formatting
OK
# After Submitting
Nothing
2025-04-24 08:32:29 -05:00
Darren Schroeder
82eb1c5584
add more details to decribe -d (#15591)
# Description

I was playing around with the `debug` command and wanted to add this
information to it but since most of it already existed in `describe` I
wanted to try and add it here. It adds a few more details that are
hopefully helpful. It mainly tries to add the value type, rust datatype,
and value. I'm not sure all of this is wanted or needed but I thought it
was an interesting introspection idea.

### Before

![image](https://github.com/user-attachments/assets/f1cfc5dd-6c02-4aa1-acb2-8e9931f66dd8)


### After

![image](https://github.com/user-attachments/assets/cfb3c8bd-70dd-4aa1-b03a-375acf6c0e09)


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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-24 08:25:36 -05:00
Stefan Holderbach
6be291b00a
Fix labelling of plugins through correct glob (#15634)
https://github.com/nushell/nushell/pull/15627#issuecomment-2827259125
2025-04-24 14:00:16 +02:00
Wind
7add38fe32
IR: allow subexpression with redirection. (#15617)
# Description
Try to fixes https://github.com/nushell/nushell/issues/15326 in another
way.

The main point of this change is to avoid duplicate `write` and `close`
a redirected file. So during compile, if compiler know current element
is a sub-expression(defined by private `is_subexpression` function), it
will no longer invoke `finish_redirection`.

In this way, we can avoid duplicate `finish_redirection`.

# User-Facing Changes
`(^echo aa) o> /tmp/aaa` will no longer raise an error.

Here is the IR after the pr:
```
# 3 registers, 12 instructions, 11 bytes of data
# 1 file used for redirection
   0: load-literal           %1, string("aaa")
   1: open-file              file(0), %1, append = false
   2: load-literal           %1, glob-pattern("echo", no_expand = false)
   3: load-literal           %2, glob-pattern("true", no_expand = false)
   4: push-positional        %1
   5: push-positional        %2
   6: redirect-out           file(0)
   7: redirect-err           caller
   8: call                   decl 135 "run-external", %0
   9: write-file             file(0), %0
  10: close-file             file(0)
  11: return                 %0
```

# Tests + Formatting
Added 3 tests.

# After Submitting
Maybe need to update doc
https://github.com/nushell/nushell.github.io/pull/1876

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-04-24 13:47:04 +02:00
Auca Coyan
78903724f5
Add labeler bot (#15627)
- fixes #15607 

# Description
Hi! I added a labeler bot workflow and reference to the tags. This
workflow runs whenever is a change in a PR (`pull_request_target`)
[source](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target)

# User-Facing Changes
Nothing here, just the CI

# Tests + Formatting
Not needed

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-04-23 19:55:41 +02:00
Sebastian Nallar
cb57f0a539
Add --follow-symlinks flag to glob command (fixes #15559) (#15626)
Fixes #15559

# Description
The glob command wasn't working correctly with symlinks in the /sys
filesystem. This commit adds a new flag that allows users to explicitly
control whether symlinks should be followed, with special handling for
the /sys directory.

The issue was that the glob command didn't follow symbolic links when
traversing the /sys filesystem, resulting in an empty list even though
paths should be found. This implementation adds a new
`--follow-symlinks` flag that explicitly enables following symlinks. By
default, it now follows symlinks in most paths but has special handling
for /sys paths where the flag is required.

Example:
`
# Before: This would return an empty list on Linux systems
glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Now: This works as expected with the new flag
glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
--follow-symlinks
`

# User-Facing Changes

1. Added the --follow-symlinks (-l) flag to the glob command that allows
users to explicitly control whether symbolic links should be followed
2. Added a new example to the glob command help text demonstrating the
use of this flag

# Tests + Formatting

1. Added a test for the new --follow-symlinks flag
2025-04-23 10:47:48 -05:00
Matthias Meschede
717081bd2f
fix mistake in description of polars pivot command (#15621)
Very small change to fix a typo/mistake in the polars pivot command
description.
2025-04-23 12:22:40 +02:00
suimong
e1ffaf2548
Improve std/log performance (#15614)
<!--
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 #15610 .

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

This PR attempts to improve the performance of `std/log *` by making the
following changes:
1. use explicit piping instead of `reduce` for constructing the log
message
2. constify `log-level`, `log-ansi`, `log-types` etc.
3. use `.` instead of `get` to access `$env` fields


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

Nothing.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Ben Yang <ben@ya.ng>
Co-authored-by: suimong <suimong@users.noreply.github.com>
2025-04-22 13:00:20 -05:00
pyz4
1db4be12d1
fix(polars): remove requirement that pivot columns must be same type in polars pivot (#15608)
<!--
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.
-->
Contrary to the underlying implementation in polars rust/python, `polars
pivot` throws an error if the user tries to pivot on multiple columns of
different types. This PR seeks to remove this type-check. See comparison
below.

```nushell
#  Current implementation: throws error when pivoting on multiple values of different types.
> [[name subject date test_1 test_2 grade_1 grade_2]; [Cady maths 2025-04-01 98 100 A A] [Cady physics 2025-04-01 99 100 A A] [Karen maths 2025-04-02 61 60 D D] [Karen physics 2025-04-02 58 60 D D]] | polars into-df |  polars pivot --on [subject] --index [name] --values [test_1 grade_1]
Error:   × Merge error
   ╭─[entry #291:1:271]
 1 │ [[name subject date test_1 test_2 grade_1 grade_2]; [Cady maths 2025-04-01 98 100 A A] [Cady physics 2025-04-01 99 100 A A] [Karen maths 2025-04-02 61 60 D D] [Karen physics 2025-04-02 58 60 D D]] | polars into-df |  polars pivot --on [subject] --index [name] --values [test_1 grade_1]
   ·                                                                                                                                                                                                                                                                               ───────┬──────
   ·                                                                                                                                                                                                                                                                                      ╰── found different column types in list
   ╰────
  help: datatypes i64 and str are incompatible


#  Proposed implementation
> [[name subject date test_1 test_2 grade_1 grade_2]; [Cady maths 2025-04-01 98 100 A A] [Cady physics 2025-04-01 99 100 A A] [Karen maths 2025-04-02 61 60 D D] [Karen physics 2025-04-02 58 60 D D]] | polars into-df |  polars pivot --on [subject] --index [name] --values [test_1 grade_1]
╭───┬───────┬──────────────┬────────────────┬───────────────┬─────────────────╮
│ # │ name  │ test_1_maths │ test_1_physics │ grade_1_maths │ grade_1_physics │
├───┼───────┼──────────────┼────────────────┼───────────────┼─────────────────┤
│ 0 │ Cady  │           98 │             99 │ A             │ A               │
│ 1 │ Karen │           61 │             58 │ D             │ D               │
╰───┴───────┴──────────────┴────────────────┴───────────────┴─────────────────╯

```

Additionally, this PR ports over the `separator` parameter in `pivot`,
which allows the user to specify how to delimit multiple `values` column
names:

```nushell
> [[name subject date test_1 test_2 grade_1 grade_2]; [Cady maths 2025-04-01 98 100 A A] [Cady physics 2025-04-01 99 100 A A] [Karen maths 2025-04-02 61 60 D D] [Karen physics 2025-04-02 58 60 D D]] | polars into-df |  polars pivot --on [subject] --index [name] --values [test_1 grade_1] --separator /
╭───┬───────┬──────────────┬────────────────┬───────────────┬─────────────────╮
│ # │ name  │ test_1/maths │ test_1/physics │ grade_1/maths │ grade_1/physics │
├───┼───────┼──────────────┼────────────────┼───────────────┼─────────────────┤
│ 0 │ Cady  │           98 │             99 │ A             │ A               │
│ 1 │ Karen │           61 │             58 │ D             │ D               │
╰───┴───────┴──────────────┴────────────────┴───────────────┴─────────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Soft breaking change: where a user may have previously expected an error
(pivoting on multiple columns with different types), no error is thrown.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-22 10:17:11 -07:00
Tyarel
6193679dfc
Fix kv set with a closure argument (#15588)
<!--
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 #15528 
# Description
Fixed `kv set` passing the pipeline input to the closure instead of the
value stored in that key.

# User-Facing Changes
Now `kv set` will pass the value in that key to the closure.

# Tests + Formatting


# After Submitting
2025-04-22 22:30:38 +08:00
Douglas
a9657e17ad
Add env-conversions helpers to std (#15569)
When combined with [the Cookbook
update](https://github.com/nushell/nushell.github.io/pull/1878), this
resolves #15452

# Description

When we removed the startup `ENV_CONVERSION` for path, as noted in the
issue above, we removed the ability for users to access this closure for
other purposes. This PR adds the PATH closures back as a `std` commands
that outputs a record of closures (similar to `ENV_CONVERSIONS`).

# User-Facing Changes

Doc will be updated and users can once again easily access `direnv`

# Tests + Formatting

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

# After Submitting

Doc PR to be merged when released in 0.104
2025-04-22 07:22:46 +08:00
André Lazenga
03d455a688
Fix #13546: Outer joins incorrectly removing unmatched rows (#15472)
Fixes #13546 

# Description

Previously, outer joins would remove rows without join columns, since
the "did not match" logic only executed when the row had the join
column.
To solve this, missing join columns are now treated the same as "exists
but did not match" cases. The logic now executes both when the join
column doesn't exist and when it exists but doesn't match, ensuring rows
without join columns are preserved. If the join column is not defined at
all, the previous behavior remains unchanged.

Example:
```
For the tables:
let left_side = [{a: a1 ref: 1} {a: a2 ref: 2} {a: a3}]
let right_side = [[b ref]; [b1 1] [b2 2] [b3 3]]

Running "$left_side | join -l $right_side ref" now outputs:
╭───┬────┬─────┬────╮
│ # │ a  │ ref │ b  │
├───┼────┼─────┼────┤
│ 0 │ a1 │   1 │ b1 │
│ 1 │ a2 │   2 │ b2 │
│ 2 │ a3 │     │    │
╰───┴────┴─────┴────╯
```

# User-Facing Changes

The ```join``` command will behave more similarly to SQL-style joins. In
this case, rows that lack the join column are preserved.

# Tests + Formatting

Added 2 test cases.
fmt + clippy OK.

# After Submitting

I don't believe anything is necessary.
2025-04-22 07:19:08 +08:00
Wind
bae04352ca
overlay use: keep PWD after activating the overlay thought file. (#15566)
# Description
Fixes: #14048

The issue happened when re-using a ***module file***, and the overlay
already has already saved `PWD`, then nushell restores the `PWD`
variable after activating it.

This pr is going to fix it by restoring `PWD` after re-using a module
file.

# User-Facing Changes
`overlay use spam.nu` will always keep `PWD`, if `spam.nu` itself
doesn't change `PWD` while activating.

# Tests + Formatting
Added 2 tests.

# After Submitting
NaN
2025-04-21 20:09:08 +08:00
Renan Ribeiro
a1497716f1
Add job tags (#15555)
# Description

This PR implements job tagging through the usage of a new `job tag`
command and a `--tag` for `job spawn`

Closes #15354

# User-Facing Changes

- New `job tag` command
- Job list may now have an additional `tag` column for the tag of jobs
(rows representing jobs without tags do not have this column filled)
- New `--tag` flag for `job spawn`

# Tests + Formatting

Integration tests are provided to test the newly implemented features

# After Submitting

Possibly document job tagging in the jobs documentation
2025-04-21 20:08:00 +08:00
scarlet-storm
b5b63d2bf9
Enable socks proxy support in ureq (#15597)
# Description
Enable socks-proxy feature in ureq.
This allows use of socks protocol in proxy env variables when using
nushell http client.
eg. to use a socks5 proxy on localhost
``` 
ALL_PROXY=socks5://localhost:8080 http get ...
```
# User-Facing Changes

None
# Tests + Formatting

# After Submitting
2025-04-21 07:54:47 +08:00
Loïc Riegel
5c59611083
feat: duration from record (#15600)
Closes #15543

# Description

1. Simplify code in ``datetime.rs`` based on a suggestion in my last PR
on "datetime from record"
1. Make ``into duration`` work with durations inside a record, provided
as a cell path
1. Make ``into duration`` work with durations as record

# User-Facing Changes

```nushell
# Happy paths
~> {d: '1hr'} | into duration d
╭───┬─────╮
│ d │ 1hr │
╰───┴─────╯

~> {week: 10, day: 2, sign: '+'} | into duration
10wk 2day

# Error paths and invalid usage
~> {week: 10, day: 2, sign: 'x'} | into duration
Error: nu:🐚:incorrect_value

  × Incorrect value.
   ╭─[entry #4:1:26]
 1 │ {week: 10, day: 2, sign: 'x'} | into duration
   ·                          ─┬─    ──────┬──────
   ·                           │           ╰── encountered here
   ·                           ╰── Invalid sign. Allowed signs are +, -
   ╰────

~> {week: 10, day: -2, sign: '+'} | into duration
Error: nu:🐚:incorrect_value

  × Incorrect value.
   ╭─[entry #5:1:17]
 1 │ {week: 10, day: -2, sign: '+'} | into duration
   ·                 ─┬               ──────┬──────
   ·                  │                     ╰── encountered here
   ·                  ╰── number should be positive
   ╰────

~> {week: 10, day: '2', sign: '+'} | into duration
Error: nu:🐚:only_supports_this_input_type

  × Input type not supported.
   ╭─[entry #6:1:17]
 1 │ {week: 10, day: '2', sign: '+'} | into duration
   ·                 ─┬─               ──────┬──────
   ·                  │                      ╰── only int input data is supported
   ·                  ╰── input type: string
   ╰────

~> {week: 10, unknown: 1} | into duration
Error: nu:🐚:unsupported_input

  × Unsupported input
   ╭─[entry #7:1:1]
 1 │ {week: 10, unknown: 1} | into duration
   · ───────────┬──────────   ──────┬──────
   ·            │                   ╰── Column 'unknown' is not valid for a structured duration. Allowed columns are: week, day, hour, minute, second, millisecond, microsecond, nanosecond, sign
   ·            ╰── value originates from here
   ╰────

~> {week: 10, day: 2, sign: '+'} | into duration --unit sec
Error: nu:🐚:incompatible_parameters

  × Incompatible parameters.
   ╭─[entry #2:1:33]
 1 │ {week: 10, day: 2, sign: '+'} | into duration --unit sec
   ·                                 ──────┬────── ─────┬────
   ·                                       │            ╰── the units should be included in the record
   ·                                       ╰── got a record as input
   ╰────
```

# Tests + Formatting
- Add examples and integration tests for ``into duration``
- Add one test for ``into duration``

# After Submitting
If this is merged in time, I'll update my PR on the "datetime handling
highlights" for the release notes.
2025-04-19 18:29:12 -05:00
Loïc Riegel
1503ee09ba
Bugfix/loss of precision when parsing value with unit (#15606)
Closes #12858

# Description
As explained in the ticket, easy to reproduce. Example: 1.07 minute is
1.07*60=64.2 secondes
```nushell
# before - wrong
> 1.07min
1min 4sec

# now - right
> 1.07min
1min 4sec 200ms
```

# User-Facing Changes
Bug is fixed when using ``into duration``.

# Tests + Formatting
Added a test for ``into duration``
Fixed ``parse_long_duration`` test: we gained precision 😄 

# After Submitting
Release notes? Or blog is enough? Let me know
2025-04-19 17:02:40 -05:00
zc he
24dba9dc53
fix(lsp): regression of semantic tokens of module-prefixed commands (#15603)
# Description

Fixes a regression caused by #15567, where I made the space detection in
command names switched from `get_span_content` to `get_decl().name()`,
which is slightly faster but it won't work in some cases:

e.g.
```nushell
use std/assert
assert equal
```

Reverted in this PR.

# User-Facing Changes

None

# Tests + Formatting

Refined

# After Submitting
2025-04-19 06:02:49 -05:00
pyz4
a2dc3e3b33
feat(polars): enable as_date and as_datetime to handle expressions as inputs (#15590)
<!--
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.
-->
This PR is a follow-up to the previous PR #15557 and part of a wider
campaign to enable certain polars commands that only operated on the
entire dataframe to also operate on expressions. Here, we enable two
commands `polars as-date` and `polars as-datetime` to receive
expressions as inputs so that they may be used on specific columns in a
dataframe with multiple columns of different types. See examples below.

```nushell
> [[a b]; ["2025-04-01" 1] ["2025-04-02" 2] ["2025-04-03" 3]] | polars into-df | polars select (polars col a | polars as-date %Y-%m-%d) b | polars collect
╭───┬───────────────────────┬───╮
│ # │           a           │ b │
├───┼───────────────────────┼───┤
│ 0 │ 04/01/2025 12:00:00AM │ 1 │
│ 1 │ 04/02/2025 12:00:00AM │ 2 │
│ 2 │ 04/03/2025 12:00:00AM │ 3 │
╰───┴───────────────────────┴───╯

> seq date -b 2025-04-01 --periods 4 --increment 25min -o "%Y-%m-%d %H:%M:%S" | polars into-df | polars select (polars col 0 | polars as-datetime "%Y-%m-%d %H:%M:%S") | polars collect
╭───┬───────────────────────╮
│ # │           0           │
├───┼───────────────────────┤
│ 0 │ 04/01/2025 12:00:00AM │
│ 1 │ 04/01/2025 12:25:00AM │
│ 2 │ 04/01/2025 12:50:00AM │
│ 3 │ 04/01/2025 01:15:00AM │
╰───┴───────────────────────╯

``` 

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users have the additional option to use `polars
as-date` and `polars as-datetime` in expressions that operate on
specific columns.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Examples have been added to `polars as-date` and `polars as-datetime`.

# 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.
-->
2025-04-18 13:50:36 -07:00
pyz4
95998bdd53
fix(custom_value) + fix(polars): map // operator to FloorDivide for custom values and in polars (#15599)
<!--
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.
-->
This PR fixes an issue where, for custom values, the `//` operator was
incorrectly mapped to `Math::Divide` instead of `Math::FloorDivide`.
This PR also fixes the same mis-mapping in the `polars` plugin.

```nushell
> [[a b c]; [x 1 1.1] [y 2 2.2] [z 3 3.3]] | polars into-df | polars select {div: ((polars col c) / (polars col b)), floor_div: ((polars col c) // (polars col b))} | polars collect
╭───┬───────┬───────────╮
│ # │  div  │ floor_div │
├───┼───────┼───────────┤
│ 0 │ 1.100 │     1.000 │
│ 1 │ 1.100 │     1.000 │
│ 2 │ 1.100 │     1.000 │
╰───┴───────┴───────────╯
```

**Note:** the number of line changes in this PR is inflated because of
auto-formatting in `nu_plugin_polars/Cargo.toml`. Substantively, I've
only added the `round_series` feature to the polars dependency list.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Breaking change: users who expected the operator `//` to function the
same as `/` for custom values will not get the expected result.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
No tests were yet added, but let me know if we should put something into
one of the polars examples.

# 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.
-->
2025-04-18 13:49:33 -07:00
pyz4
bd5de023a1
feat(polars): add pow (**) operator for polars expressions (#15598)
<!--
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.
-->
This PR adds the exponent operator ("**") to polars expressions.

```nushell
  > [[a b]; [6 2] [4 2] [2 2]] | polars into-df | polars select a b {c: ((polars col a) ** 2)}
  ╭───┬───┬───┬────╮
  │ # │ a │ b │ c  │
  ├───┼───┼───┼────┤
  │ 0 │ 6 │ 2 │ 36 │
  │ 1 │ 4 │ 2 │ 16 │
  │ 2 │ 2 │ 2 │  4 │
  ╰───┴───┴───┴────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users are enabled to use the `**` operator in
polars expressions.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
An example in `polars select` was modified to showcase the `**`
operator.

# 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.
-->
2025-04-18 13:48:59 -07:00
Darren Schroeder
38e761493d
add --raw-value option to debug command (#15581)
# Description

This adds a new option `--raw-value`/`-v` to the `debug` command to
allow you to only get the debug string part of the nushell value.
Because, sometimes you don't need the span or nushell datatype and you
just want the val part.

You can see the difference between `debug -r` and `debug -v` here.

![image](https://github.com/user-attachments/assets/ac16cdf0-2ec8-4f61-a2c4-81341f8d363b)

It should work on all datatypes except Value::Error and Value::Closure.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-17 12:12:07 -05:00
Julian Amarilla
7fcebf37ec
Fix #15440 default --empty fails at empty streams (#15562)
Fixes #15440 

# Description
Wraps ListStream stream type from `impl Iterator` to `Peekable<impl
Iterator>`, this allows checking for empty streams and treating them as
empty values
 
Example:
```
# previously
$ glob ? | default -e void
> # empty list

$ echo '' | default -e void
> void

####################

# now
$ glob ? | default -e void
> void

$ echo '' | default -e void
> void
```

# User-Facing Changes

empty list streams will behave as `nothing` values when testing for
emptiness

# Tests + Formatting

- Add 2 tests
- clippy OK
- fmt OK

# After Submitting
2025-04-17 16:57:25 +02:00
pyz4
0e9927ea4d
polars: expand polars col to handle multiple columns and by types (#15570)
<!--
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.
-->
This PR seeks to expand `polars col` functionality to allow selecting
multiple columns and columns by type, which is particularly useful when
piping to subsequent expressions that should be applied to each column
selected (e.g., `polars col int --type | polars sum` as a shorthand for
`[(polars col a | polars sum), (polars col b | polars sum)]`). See
examples below.

```nushell
#  Select multiple columns (cannot be used with asterisk wildcard)
  > [[a b c]; [x 1 1.1] [y 2 2.2] [z 3 3.3]] | polars into-df 
          | polars select (polars col b c | polars sum) | polars collect
  ╭───┬───┬──────╮
  │ # │ b │  c   │
  ├───┼───┼──────┤
  │ 0 │ 6 │ 6.60 │
  ╰───┴───┴──────╯

#  Select multiple columns by types (cannot be used with asterisk wildcard)
  > [[a b c]; [x o 1.1] [y p 2.2] [z q 3.3]] | polars into-df 
           | polars select (polars col str f64 --type | polars max) | polars collect
  ╭───┬───┬───┬──────╮
  │ # │ a │ b │  c   │
  ├───┼───┼───┼──────┤
  │ 0 │ z │ q │ 3.30 │
  ╰───┴───┴───┴──────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users have the additional capability to select
multiple columns in `polars col`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-16 14:30:49 -07:00
Piepmatz
d273ce89df
Add --plugins flag to nu-std/testing.nu (#15552)
<!--
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.
-->
In this PR I added the flag `--plugins` to the `testing.nu` file inside
of `crates/nu-std`. This allows running tests with active plugins. While
I did not use it here in this repo, it allows testing in
[nushell/plugin-examples](https://github.com/nushell/plugin-examples)
with plugins.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

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

(nothing broke \o/)

# 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.
-->
2025-04-16 23:20:04 +02:00
pyz4
2dc5c19b71
feat(polars): loosen constraints on accepted expressions in polars group-by (#15583)
# 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.
-->
This PR lifts the constraint that expressions in the `polars group-by`
command must be limited only to the type `Expr::Column` rather than most
`Expr` types, which is what the underlying polars crate allows. This
change enables more complex expressions to group by.

In the example below, we group by even or odd days of column `a`. While
we can reach the same result by creating and grouping by a new column in
two separate steps, integrating these steps in a single group-by allows
for better delegation to the polars optimizer.

```nushell
#  Group by an expression and perform an aggregation
  > [[a b]; [2025-04-01 1] [2025-04-02 2] [2025-04-03 3] [2025-04-04 4]]
    | polars into-lazy
    | polars group-by (polars col a | polars get-day | $in mod 2)
    | polars agg [
        (polars col b | polars min | polars as "b_min")
        (polars col b | polars max | polars as "b_max")
        (polars col b | polars sum | polars as "b_sum")
     ]
    | polars collect
    | polars sort-by a
  ╭───┬───┬───────┬───────┬───────╮
  │ # │ a │ b_min │ b_max │ b_sum │
  ├───┼───┼───────┼───────┼───────┤
  │ 0 │ 0 │     2 │     4 │     6 │
  │ 1 │ 1 │     1 │     3 │     4 │
  ╰───┴───┴───────┴───────┴───────╯
```  

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. The user is empowered to use more complex
expressions in `polars group-by`

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
An example is added to `polars group-by`.

# 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.
-->
2025-04-16 14:18:48 -07:00
pyz4
669b44ad7d
feat(polars): add polars truncate for rounding datetimes (#15582)
<!--
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.
-->
This PR directly ports the polars function `polars.Expr.dt.truncate`
(https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.Expr.dt.truncate.html),
which rounds a datetime to an arbitrarily specified period length. This
function is particularly useful when rounding to variable period lengths
such as months or quarters. See below for examples.

```nushell
#  Truncate a series of dates by period length
  > seq date -b 2025-01-01 --periods 4 --increment 6wk -o "%Y-%m-%d %H:%M:%S" | polars into-df | polars as-datetime "%F %H:%M:%S" --naive | polars select datetime (polars col datetime | polars truncate 5d37m | polars as truncated) | polars collect
  ╭───┬───────────────────────┬───────────────────────╮
  │ # │       datetime        │       truncated       │
  ├───┼───────────────────────┼───────────────────────┤
  │ 0 │ 01/01/2025 12:00:00AM │ 12/30/2024 04:49:00PM │
  │ 1 │ 02/12/2025 12:00:00AM │ 02/08/2025 09:45:00PM │
  │ 2 │ 03/26/2025 12:00:00AM │ 03/21/2025 02:41:00AM │
  │ 3 │ 05/07/2025 12:00:00AM │ 05/05/2025 08:14:00AM │
  ╰───┴───────────────────────┴───────────────────────╯

#  Truncate based on period length measured in quarters and months
> seq date -b 2025-01-01 --periods 4 --increment 6wk -o "%Y-%m-%d %H:%M:%S" | polars into-df | polars as-datetime "%F %H:%M:%S" --naive | polars select datetime (polars col datetime | polars truncate 1q5mo | polars as truncated) | polars collect
╭───┬───────────────────────┬───────────────────────╮
│ # │       datetime        │       truncated       │
├───┼───────────────────────┼───────────────────────┤
│ 0 │ 01/01/2025 12:00:00AM │ 09/01/2024 12:00:00AM │
│ 1 │ 02/12/2025 12:00:00AM │ 09/01/2024 12:00:00AM │
│ 2 │ 03/26/2025 12:00:00AM │ 09/01/2024 12:00:00AM │
│ 3 │ 05/07/2025 12:00:00AM │ 05/01/2025 12:00:00AM │
╰───┴───────────────────────┴───────────────────────╯

```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. This PR introduces a new command `polars truncate`

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-16 14:17:49 -07:00
dependabot[bot]
eff063822a
build(deps): bump rust-embed from 8.6.0 to 8.7.0 (#15579)
Bumps [rust-embed](https://github.com/pyros2097/rust-embed) from 8.6.0
to 8.7.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pyrossh/rust-embed/blob/master/changelog.md">rust-embed's
changelog</a>.</em></p>
<blockquote>
<h2>[8.7.0] - 2025-04-10</h2>
<ul>
<li>add deterministic timestamps flag for deterministic builds <a
href="https://redirect.github.com/pyrossh/rust-embed/pull/259">#259</a>.
Thanks to <a href="https://github.com/daywalker90">daywalker90</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/pyros2097/rust-embed/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust-embed&package-manager=cargo&previous-version=8.6.0&new-version=8.7.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>
2025-04-16 22:14:59 +08:00
dependabot[bot]
9a5c4d36be
build(deps): bump data-encoding from 2.8.0 to 2.9.0 (#15580)
Bumps [data-encoding](https://github.com/ia0/data-encoding) from 2.8.0
to 2.9.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4fce77c46b"><code>4fce77c</code></a>
Release 2.9.0 (<a
href="https://redirect.github.com/ia0/data-encoding/issues/138">#138</a>)</li>
<li><a
href="d81616352a"><code>d816163</code></a>
Add encode_mut_str to guarantee UTF-8 for safe callers (<a
href="https://redirect.github.com/ia0/data-encoding/issues/137">#137</a>)</li>
<li><a
href="ec53217669"><code>ec53217</code></a>
Update doc badge in README.md (<a
href="https://redirect.github.com/ia0/data-encoding/issues/135">#135</a>)</li>
<li>See full diff in <a
href="https://github.com/ia0/data-encoding/compare/v2.8.0...v2.9.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=data-encoding&package-manager=cargo&previous-version=2.8.0&new-version=2.9.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>
2025-04-16 22:14:36 +08:00
zc he
cd4560e97a
fix(lsp): a panic caused by completion with decl_id out of range (#15576)
Fixes a bug caused by #15536 
Sorry about that, @fdncred 

# Description

I've made the panic reproducible in the test case.

TLDR: completer will sometimes return new decl_ids outside of the range
of the engine_state passed in.

# User-Facing Changes

bug fix

# Tests + Formatting

+1

# After Submitting
2025-04-16 06:43:21 -05:00
zc he
24cc2f9d87
fix(completion): quoted cell path completion (#15546)
Closes #15525 

# Description

# User-Facing Changes

bug fix

# Tests + Formatting

+1

# After Submitting
2025-04-16 01:26:45 -04:00
Jack Wright
8f81812ef9
fix cannot find issue when performing collect on an eager dataframe (#15577)
# Description
Performing a `polars collect` on an eager dataframe should be a no-op
operation. However, when used with a pipeline and not saving to a value
a cache error occurs. This addresses that cache error.
2025-04-15 14:25:11 -05:00
Mussar
2229370b13
replace repeat().take() with repeat_n() (#15575)
# Description

This updates `string_expand()` in nu-table's util.rs to use the
`std::iter` library's `repeat_n()` function, which was suggested as a
more readable version of the existing `repeat().take()` implementation.

# User-Facing Changes
 
Should have no user facing changes.

# Tests + Formatting

All green circles!
```
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib
```
2025-04-15 23:29:32 +08:00
pyz4
a33650a69e
fix(polars): cast as date now returns Date type instead of Datetime<ns> (#15574)
<!--
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.
-->
This PR fixes the bug where various commands that cast a column as a
`date` type would return `datetime<ns>` rather than the intended type
`date`. Affected commands include `polars into-df --schema`, `polars
into-lazy --schema`, `polars as-date`, and `polars cast date`.

This bug derives from the fact that Nushell uses the `date` type to
denote a datetime type whereas polars differentiates between `Date` and
`Datetime` types. By default, this PR retains the behavior that a
Nushell `date` type will be mapped to a polars `Datetime<ns>` unless
otherwise specified.

```nushell
#  Current (erroneous) implementation
> [[a]; [2025-03-20]] | polars into-df --schema {a: "date"} | polars schema
╭───┬──────────────╮
│ a │ datetime<ns> │
╰───┴──────────────╯

#  Fixed implementation
> [[a]; [2025-03-20]] | polars into-df --schema {a: "date"} | polars schema
╭───┬──────╮
│ a │ date │
╰───┴──────╯

#  Fixed implementation: by default, Nushell dates map to datetime<ns>
> [[a]; [2025-03-20]] | polars into-df | polars schema
╭───┬───────────────────╮
│ a │ datetime<ns, UTC> │
╰───┴───────────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Soft breaking change: users previously who wanted to cast a date column
to type `date` can now expect the output to be type `date` instead of
`datetime<ns>`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-15 08:20:54 -07:00
zc he
56d7e4bb89
refactor(completion, lsp): include decl_id in suggetion_kind for later usage (#15536)
# Description

Should be more performant, calling for `find_decl` by name for all
entries is generally a heavy op.

# User-Facing Changes

NA

# Tests + Formatting

# After Submitting
2025-04-15 07:24:56 -05:00
zc he
e5f589ccdd
refactor(lsp): flat_map with mutable accumulator (#15567)
# Description

Mainly performance improvement of lsp operations involving flat_map on
AST nodes.
Previous flat_map traversing is functional, which is a nice property to
have, but the heavy cost of vector collection on each tree node makes it
undesirable.

This PR mitigates the problem with a mutable accumulator.

# User-Facing Changes

Should be none.

# Tests + Formatting

# After Submitting
2025-04-15 07:21:23 -05:00
Renan Ribeiro
8c4d3eaa7e
config commands now add frozen jobs to job table (#15556)
# Description

`config nu/env` used to ignore the frozen wait job status response and
did not add processes to the job table when they were frozen.

This PR refactors the PostWaitCallback used in run_external and allows
frozen processes spawned by `config_.rs` to be added to the job table.

Closes #15389



# User-Facing Changes

`config nu` now respects the job freezing semantics.

# Tests + Formatting
This behavior can be verified by running `config nu` or `config env`,
hitting Ctrl-Z, and then running `job list`.
2025-04-15 06:36:08 -05:00
Jack Wright
89322f59f2
Fix output type of polars schema (#15572)
# Description
Output type of `polars schema` signature output type is of dataframe. It
should be of type record.

# User-Facing Changes
- `polars schema` - how has an output type of record
2025-04-15 06:27:31 -05:00
pyz4
4e307480e4
polars: extend NuExpression::extract_exprs to handle records (#15553)
<!--
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.
-->
This PR seeks to simplify the syntax for commands that handle a list of
expressions (e.g., `select`, `with-column`, and `agg`) by enabling the
user to replace a list of expressions each aliased with `polars as` to a
single record where the key is the alias for the value. See below for
examples in several contexts.

```nushell
#  Select a column from a dataframe using a record
  > [[a b]; [6 2] [4 2] [2 2]] | polars into-df | polars select {c: ((polars col a) * 2)}
  ╭───┬────╮
  │ # │ c  │
  ├───┼────┤
  │ 0 │ 12 │
  │ 1 │  8 │
  │ 2 │  4 │
  ╰───┴────╯

#  Select a column from a dataframe using a mix of expressions and record of expressions
  > [[a b]; [6 2] [4 2] [2 2]] | polars into-df | polars select a b {c: ((polars col a) * 2)}
  ╭───┬───┬───┬────╮
  │ # │ a │ b │ c  │
  ├───┼───┼───┼────┤
  │ 0 │ 6 │ 2 │ 12 │
  │ 1 │ 4 │ 2 │  8 │
  │ 2 │ 2 │ 2 │  4 │
  ╰───┴───┴───┴────╯

#  Add series to the dataframe using a record
  > [[a b]; [1 2] [3 4]]
    | polars into-lazy
    | polars with-column {
        c: ((polars col a) * 2)
        d: ((polars col a) * 3)
      }
    | polars collect
  ╭───┬───┬───┬───┬───╮
  │ # │ a │ b │ c │ d │
  ├───┼───┼───┼───┼───┤
  │ 0 │ 1 │ 2 │ 2 │ 3 │
  │ 1 │ 3 │ 4 │ 6 │ 9 │
  ╰───┴───┴───┴───┴───╯

#  Group by and perform an aggregation using a record
  > [[a b]; [1 2] [1 4] [2 6] [2 4]]
                | polars into-lazy
                | polars group-by a
                | polars agg {
                    b_min: (polars col b | polars min)
                    b_max: (polars col b | polars max)
                    b_sum: (polars col b | polars sum)
                 }
                | polars collect
                | polars sort-by a
  ╭───┬───┬───────┬───────┬───────╮
  │ # │ a │ b_min │ b_max │ b_sum │
  ├───┼───┼───────┼───────┼───────┤
  │ 0 │ 1 │     2 │     4 │     6 │
  │ 1 │ 2 │     4 │     6 │    10 │
  ╰───┴───┴───────┴───────┴───────╯

```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users now can use a mix of lists of expressions and
records of expressions where previously only lists of expressions were
accepted (e.g., in `select`, `with-column`, and `agg`).

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Example tests were added to `select`, `with-column`, and `agg`.

# 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.
-->
2025-04-14 16:56:52 -07:00
Mussar
d601abaee0
chore: move 'job' to experimental category (#15568)
# Description

The 'job' command was incorrectly placed into the "Strings" category
rather than the "Experimental" category like its subcommands. This PR
resolves that issues.

# User-Facing Changes

Changes to where the `job` command is found when using the `help`
command or reading the documentation.
2025-04-14 22:28:16 +02:00
pyz4
ceaa0f9375
polars: add new command polars over (#15551)
<!--
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.
-->
Introducing a basic implementation of the polars expression for window
functions: `over`
(https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.Expr.over.html).
Note that this PR only implements the default values for the sorting and
`mapping_strategy` parameters. Implementations for other values for
these parameters may be added in a future PR, as the demand arises.

```nushell
 # Compute expression over an aggregation window
  > [[a b]; [x 2] [x 4] [y 6] [y 4]]
        | polars into-lazy
        | polars select a (polars col b | polars cumulative sum | polars over a | polars as cum_b)
        | polars collect
  ╭───┬───┬───────╮
  │ # │ a │ cum_b │
  ├───┼───┼───────┤
  │ 0 │ x │     2 │
  │ 1 │ x │     6 │
  │ 2 │ y │     6 │
  │ 3 │ y │    10 │
  ╰───┴───┴───────╯

# Compute expression over an aggregation window where partitions are defined by expressions
  > [[a b]; [x 2] [X 4] [Y 6] [y 4]]
        | polars into-lazy
        | polars select a (polars col b | polars cumulative sum | polars over (polars col a | polars lowercase) | polars as cum_b)
        | polars collect
  ╭───┬───┬───────╮
  │ # │ a │ cum_b │
  ├───┼───┼───────┤
  │ 0 │ x │     2 │
  │ 1 │ X │     6 │
  │ 2 │ Y │     6 │
  │ 3 │ y │    10 │
  ╰───┴───┴───────╯
```
 
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. This PR seeks to add a new command only.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-14 08:59:48 -07:00
pyz4
d31b7024d8
polars: update get- datetime components commands to allow expressions as inputs (#15557)
<!--
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.
-->
This PR updates the following functions so they may also be used in a
polars expression:

- `polars get-day`
- `polars get-hour`
- `polars get-minute`
- `polars get-month`
- `polars get-nanosecond`
- `polars get-ordinal`
- `polars get-second`
- `polars get-week`
- `polars get-weekday`
- `polars get-year`

Below examples provide a comparison of the two contexts in which each of
these commands may be used:

```nushell
# Returns day from a date (current use case)
  > let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
    let df = ([$dt $dt] | polars into-df);
    $df | polars get-day
  ╭───┬───╮
  │ # │ 0 │
  ├───┼───┤
  │ 0 │ 4 │
  │ 1 │ 4 │
  ╰───┴───╯

# Returns day from a date in an expression (additional use case provided by this PR)
  > let dt = ('2020-08-04T16:39:18+00:00' | into datetime --timezone 'UTC');
    let df = ([$dt $dt] | polars into-df);
    $df | polars select (polars col 0 | polars get-day)
  ╭───┬───╮
  │ # │ 0 │
  ├───┼───┤
  │ 0 │ 4 │
  │ 1 │ 4 │
  ╰───┴───╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Each of these functions retains its current
behavior and gains the benefit that they can now be used in an
expression as well.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Tests have been added to each of the examples.

# 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.
-->
2025-04-14 08:59:02 -07:00
pyz4
9dd30d7756
polars: update polars lit to handle nushell Value::Duration and Value::Date types (#15564)
<!--
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.
-->
This PR seeks to expand `polars lit` to handle additional nushell types:
Value::Date and Value::Duration. This change is especially relevant to
the `polars filter` command, where expressions would then directly
incorporate Value::Date and Value::Duration types as literals. See one
such example below.

```nushell
#  Filter dataframe for rows where dt is within the last 2 days of the maximum dt value
  > [[dt val]; [2025-04-01 1] [2025-04-02 2] [2025-04-03 3] [2025-04-04 4]] | polars into-df | polars filter ((polars col dt) > ((polars col dt | polars max | $in - 2day)))
  ╭───┬─────────────────────┬─────╮
  │ # │          dt         │ val │
  ├───┼─────────────────────┼─────┤
  │ 0 │ 04/03/25 12:00:00AM │   3 │
  │ 1 │ 04/04/25 12:00:00AM │   4 │
  ╰───┴─────────────────────┴─────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users now can directly access Value::Date and
Value::Duration types as literals in polars expressions.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Several additional examples added to `polars lit` and `polars filter`

# 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.
-->
2025-04-14 08:58:07 -07:00
Firegem
eff9305eb3
Allow spreading arguments of kill command (#15558)
<!--
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.
-->
This changes the signature of `kill` from `kill pid ...rest` to `kill
...pid`.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Users will now be able to spread a list of pids to the `kill` command,
whereas they'd have to specify the first separately before.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-13 07:50:04 -05:00
pyz4
885b87a842
polars: add new command polars convert-time-zone (#15550)
<!--
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.
-->
This is a direct port of the python polars command `convert_time_zone`
(https://docs.pola.rs/api/python/stable/reference/series/api/polars.Series.dt.convert_time_zone.html).
Consistent with the rust/python implementation, naive datetimes are
treated as if they are in UTC time.

```nushell
  # Convert timezone for timezone-aware datetime
  > ["2025-04-10 09:30:00 -0400" "2025-04-10 10:30:00 -0400"] | polars into-df
                    | polars as-datetime "%Y-%m-%d %H:%M:%S %z"
                    | polars select (polars col datetime | polars convert-time-zone "Europe/Lisbon")
  ╭───┬───────────────────────╮
  │ # │       datetime        │
  ├───┼───────────────────────┤
  │ 0 │ 04/10/2025 02:30:00PM │
  │ 1 │ 04/10/2025 03:30:00PM │
  ╰───┴───────────────────────╯

  # Timezone conversions for timezone-naive datetime will assume the original timezone is UTC
  > ["2025-04-10 09:30:00" "2025-04-10 10:30:00"] | polars into-df
                    | polars as-datetime "%Y-%m-%d %H:%M:%S" --naive
                    | polars select (polars col datetime | polars convert-time-zone "America/New_York")
  ╭───┬───────────────────────╮
  │ # │       datetime        │
  ├───┼───────────────────────┤
  │ 0 │ 04/10/2025 05:30:00AM │
  │ 1 │ 04/10/2025 06:30:00AM │
  ╰───┴───────────────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
No breaking changes. Users have access to a new command `polars
convert-time-zone`

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# 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.
-->
2025-04-11 14:08:40 -07:00
whi
017daeed18
create nu_plugin_node_example.js (#15482)
example like
[nu_plugin_python_example.py](https://github.com/nushell/nushell/blob/main/crates/nu_plugin_python/nu_plugin_python_example.py)
2025-04-11 21:18:46 +02:00
Loïc Riegel
c8c018452f
Bugfix chrono panic + hotifx PR15544 (#15549)
Closes  #13972

# Description
First commit: a hotfix concerning my last PR #15544! I had a
``unwrap_or_default`` that resulted in all years before ~1800 being
considered as "now", because the ``num_nanoseconds()`` overflowed.
Cc @fdncred 

Second: about #13972
Negative years are not allowed with RFC 2822 formatting, so I fallback
RTC 3339 in such cases.

If you want you might Rebase and Merge, and not squash.

# User-Facing Changes
On master 🔴 :
```nu
~> {year: 1900} | into datetime
Mon, 1 Jan 1900 00:00:00 +0200 (125 years ago)
# OK

~> {year: 1000} | into datetime
Wed, 1 Jan 1000 00:00:00 +0200 (now)
# NOT OK: now?

~> {year: -1000} | into datetime
-1000-01-01T00:00:00+02:00 (now)
# NOT OK: now?

~> {year: -1000} | into datetime | format date 
Error:   × Main thread panicked.
  ├─▶ at C:\Users\RIL1RT\.cargo\registry\src\index.crates.io-6f17d22bba15001f\chrono-0.4.39\src\datetime\mod.rs:626:14
  ╰─▶ writing rfc2822 datetime to string should never fail: Error
  help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace.
# NOT OK: panics
```

On this branch 🟢 :
```nu
~> {year: 1900} | into datetime
Mon, 1 Jan 1900 00:00:00 +0200 (in 125 years)
~>  {year: 1000} | into datetime
Wed, 1 Jan 1000 00:00:00 +0200 (1025 years ago)
~> {year: -1000} | into datetime
-1000-01-01T00:00:00+02:00 (3025 years ago)
~> {year: -1000} | into datetime | format date
-1000-01-01T00:00:00+02:00
~> '3000 years ago' | date from-human | format date
-0975-04-11T18:18:24.301641100+02:00
```

# Tests + Formatting

# After Submitting
Nothing required IMO
2025-04-11 11:52:42 -05:00
pyz4
1a0778d77e
polars: add new command polars replace-time-zone (#15538)
<!--
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.
-->
This PR seeks to add a direct port of the python polars
`replace_time_zone` command in the `dt` namespace
(https://docs.pola.rs/api/python/stable/reference/series/api/polars.Series.dt.replace_time_zone.html).

Please note: I opted for two keywords "dt" and "replace-time-zone" to
map directly with the implementation in both the rust and python
packages, but I'm open to simplifying it to just one keyword, or `polars
replace-time-zone`

```nushell
#  Apply timezone to a naive datetime
  > ["2021-12-30 00:00:00" "2021-12-31 00:00:00"] | polars into-df
                    | polars as-datetime "%Y-%m-%d %H:%M:%S" --naive
                    | polars select (polars col datetime | polars dt replace-time-zone "America/New_York")
  ╭───┬─────────────────────╮
  │ # │      datetime       │
  ├───┼─────────────────────┤
  │ 0 │ 12/30/21 12:00:00AM │
  │ 1 │ 12/31/21 12:00:00AM │
  ╰───┴─────────────────────╯

#  Apply timezone with ambiguous datetime
  > ["2025-11-02 00:00:00", "2025-11-02 01:00:00", "2025-11-02 02:00:00", "2025-11-02 03:00:00"]
                    | polars into-df
                    | polars as-datetime "%Y-%m-%d %H:%M:%S" --naive
                    | polars select (polars col datetime | polars dt replace-time-zone "America/New_York" --ambiguous null)
  ╭───┬─────────────────────╮
  │ # │      datetime       │
  ├───┼─────────────────────┤
  │ 0 │ 11/02/25 12:00:00AM │
  │ 1 │                     │
  │ 2 │ 11/02/25 02:00:00AM │
  │ 3 │ 11/02/25 03:00:00AM │
  ╰───┴─────────────────────╯

#  Apply timezone with nonexistent datetime
  > ["2025-03-09 01:00:00", "2025-03-09 02:00:00", "2025-03-09 03:00:00", "2025-03-09 04:00:00"]
                    | polars into-df
                    | polars as-datetime "%Y-%m-%d %H:%M:%S" --naive
                    | polars select (polars col datetime | polars dt replace-time-zone "America/New_York" --nonexistent null)
  ╭───┬─────────────────────╮
  │ # │      datetime       │
  ├───┼─────────────────────┤
  │ 0 │ 03/09/25 01:00:00AM │
  │ 1 │                     │
  │ 2 │ 03/09/25 03:00:00AM │
  │ 3 │ 03/09/25 04:00:00AM │
  ╰───┴─────────────────────╯
```

# User-Facing Changes
No breaking changes. The user will be able to access the new command.

# Tests + Formatting
See example tests.

# After Submitting
2025-04-11 09:09:37 -07:00
Maxim Zhiburt
d75aa7ed1b
fix f25525b (#15500)
This addresses color issue; Yeees just got forgotten it :(
As far as I understand an acceptance test can't be created because ansi
got stripped in `nu!`. (for future regressions)

But wrapping I need to take a deeper look.
Maybe in an hour.

cc: @fdncred
2025-04-11 08:02:01 -05:00
Loïc Riegel
39edd7e080
Bugfix: datetime parsing and local timezones (#15544)
Hi,
This PR should close 3 issues
- [DMY date format is parsed inconsistently
#14123](https://github.com/nushell/nushell/issues/14123)
- [into datetime doesnt't work with --format and ignores user's locale
#11015](https://github.com/nushell/nushell/issues/11015)
- [into datetime: iinconsistent and incrrect behaviour regarding
timezones #13823](https://github.com/nushell/nushell/issues/13823)


# Description
- Allow to parse only dates or only times with --format
- Use local timezone depending on the input. Ex: I'm in France, so show
dates with +0100 in winter and +0200 in summer.

```nushell
# Concerning #13823

> "2020-01-01 12:00" | into datetime
Wed, 1 Jan 2020 12:00:00 +0100 (5 years ago)
# OK, it's my timezone in winter time

> "2020-06-01 12:00" | into datetime
Mon, 1 Jun 2020 12:00:00 +0200 (4 years ago)
# OK, it's my timezone in summertime

> ("2024-10-27 12:00" | into datetime) - ("2024-10-27 00:00" | into datetime)
13hr
# Ok, because we switched from summer to winter time on 2025-10-27, so there are actually 13h between midnight and noon

> "2020-01-01 12:00" | into datetime --format "%Y-%m-%d %H:%M"
Wed, 1 Jan 2020 12:00:00 +0100 (5 years ago)
# OK: timezone is assumed to be local, and +0100 is my timezone in winter

# Concerning #14123 and #11015
# Flexible parsing still works like before, which could be counter-intuitive, but it's flexible parsing
# with one difference: the timezone is local
> '12-01-2001' | into datetime
Sat, 1 Dec 2001 00:00:00 +0100 (23 years ago)
# OK, +0100 is my timezone in winter time. If I run it with nushell 0.103.0 in summer time, I get +0200
> '13-01-2001' | into datetime
Sat, 13 Jan 2001 00:00:00 +0100 (24 years ago)

## If you want, you can use the --format option to parse a date or a time (before, it had to be a date + time)
## Notice here again the timezone is correct depending on winter/summer time
~> "06.03.2023" | into datetime -f "%d.%m.%Y"
Mon, 6 Mar 2023 00:00:00 +0100 (2 years ago)
~> "06.03.2023" | into datetime -f "%m.%d.%Y"
Sat, 3 Jun 2023 00:00:00 +0200 (2 years ago)
> "10:00" | into datetime --format "%H:%M"
Thu, 10 Apr 2025 10:00:00 +0200 (9 hours ago)
```

# User-Facing Changes
See above

# Tests + Formatting


# After Submitting
I'll down something for the release notes, if this is merged in time 😄
2025-04-11 07:48:39 -05:00
vansh284
61dbcf3de6
Substring Match Algorithm (#15511)
<!--
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.
-->
This PR should close #15474 .

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
When users set the match algorithm to 'substring' by modifying
`$env.config` to `$env.config.completions.algorithm = "substring"``),
completions are done based on substring matches.
This was previously possible by setting `positional` to be false in
custom completers, but doing so now logs a warning as this feature is
set to be deprecated and replaced by the new way of setting the matching
algorithm to substring based.
2025-04-11 05:15:36 -04:00
Jack Wright
f8ed4b45fd
Introducing polars into-schema (#15534)
# Description
Introduces `polars into-schema` which allows converting Values such as
records to a schema. This implicitly happens when when passing records
into commands like `polars into-df` today. This allows you to convert to
a schema object ahead of time and reuse the schema object. This can be
useful for guaranteeing your schema object is correct.

```nu
> ❯ : let schema = ({name: str, type: str} | polars into-schema)

> ❯ : ls | select name type | polars into-lazy -s $schema | polars schema
╭──────┬─────╮
│ name │ str │
│ type │ str │
╰──────┴─────╯
```

# User-Facing Changes
- Introduces `polars into-schema` allowing records to be converted to
schema objects.
2025-04-10 16:07:44 -07:00
Stefan Holderbach
7b57f132bb
Bump crossbeam-channel (#15541)
Resolves https://rustsec.org/advisories/RUSTSEC-2025-0024.html
2025-04-10 17:07:42 +02:00
Loïc Riegel
dfca117551
Feat: construct datetime from record (#15455)
Issue #12289, can be closed when this is merged

# Description
Currently, the ``into datetime`` command's signature indicates that it
supports input as record, but it was actually not supported.

This PR implements this feature.

# User-Facing Changes

``into datetime``'s signature changed (see comments)

**Happy paths**

Note: I'm in +02:00 timezone.

```nushell
> date now | into record | into datetime
Fri, 4 Apr 2025 18:32:34 +0200 (now)

> {year: 2025, month: 12, day: 6, second: 59} | into datetime | into record
╭─────────────┬────────╮
│ year        │ 2025   │
│ month       │ 12     │
│ day         │ 6      │
│ hour        │ 0      │
│ minute      │ 0      │
│ second      │ 59     │
│ millisecond │ 0      │
│ microsecond │ 0      │
│ nanosecond  │ 0      │
│ timezone    │ +02:00 │
╰─────────────┴────────╯

> {day: 6, second: 59, timezone: '-06:00'} | into datetime | into record
╭─────────────┬────────╮
│ year        │ 2025   │
│ month       │ 4      │
│ day         │ 6      │
│ hour        │ 0      │
│ minute      │ 0      │
│ second      │ 59     │
│ millisecond │ 0      │
│ microsecond │ 0      │
│ nanosecond  │ 0      │
│ timezone    │ -06:00 │
╰─────────────┴────────╯
```

**Edge cases**

```nushell
{} | into datetime
Fri, 4 Apr 2025 18:35:19 +0200 (now)
```

**Error paths**

- A key has a wrong type
  ```nushell
  > {month: 12, year: '2023'} | into datetime
  Error: nu:🐚:only_supports_this_input_type

    × Input type not supported.
    ╭─[entry #8:1:19]
  1 │ {month: 12, year: '2023'} | into datetime
    ·                   ───┬──    ──────┬──────
· │ ╰── only int input data is supported
    ·                      ╰── input type: string
    ╰────
  ```
  ```nushell
  > {month: 12, year: 2023, timezone: 100} | into datetime
  Error: nu:🐚:only_supports_this_input_type

    × Input type not supported.
    ╭─[entry #10:1:35]
  1 │ {month: 12, year: 2023, timezone: 100} | into datetime
    ·                                   ─┬─    ──────┬──────
· │ ╰── only string input data is supported
    ·                                    ╰── input type: int
    ╰────
  ```
- Key has the right type but value invalid (e.g. month=13, or day=0)
  ```nushell
  > {month: 13, year: 2023} | into datetime
  Error: nu:🐚:incorrect_value

    × Incorrect value.
    ╭─[entry #9:1:1]
  1 │ {month: 13, year: 2023} | into datetime
    · ───────────┬───────────   ──────┬──────
· │ ╰── one of more values are incorrect and do not represent valid date
    ·            ╰── encountered here
    ╰────
  ```
  ```nushell
  > {hour: 1, minute: 1, second: 70} | into datetime
  Error: nu:🐚:incorrect_value
  
    × Incorrect value.
     ╭─[entry #3:1:1]
   1 │ {hour: 1, minute: 1, second: 70} | into datetime
     · ────────────────┬───────────────   ──────┬──────
· │ ╰── one of more values are incorrect and do not represent valid time
     ·                 ╰── encountered here
     ╰────
  ```
- Timezone has right type but is invalid
  ```nushell
  > {month: 12, year: 2023, timezone: "+100:00"} | into datetime
  Error: nu:🐚:incorrect_value

    × Incorrect value.
    ╭─[entry #11:1:35]
  1 │ {month: 12, year: 2023, timezone: "+100:00"} | into datetime
    ·                                   ────┬────    ──────┬──────
· │ ╰── encountered here
    ·                                       ╰── invalid timezone
    ╰────
  ```
- Record contains an invalid key
  ```nushell
  > {month: 12, year: 2023, unknown: 1} | into datetime
  Error: nu:🐚:unsupported_input

    × Unsupported input
    ╭─[entry #12:1:1]
  1 │ {month: 12, year: 2023, unknown: 1} | into datetime
    · ─────────────────┬─────────────────   ──────┬──────
· │ ╰── Column 'unknown' is not valid for a structured datetime. Allowed
columns are: year, month, day, hour, minute, second, millisecond,
microsecond, nanosecond, timezone
    ·                  ╰── value originates from here
    ╰────
  ```
- If several issues are present, the user can get the error msg for only
one, though
  ```nushell
  > {month: 20, year: '2023'} | into datetime
  Error: nu:🐚:only_supports_this_input_type

    × Input type not supported.
    ╭─[entry #7:1:19]
  1 │ {month: 20, year: '2023'} | into datetime
    ·                   ───┬──    ──────┬──────
· │ ╰── only int input data is supported
    ·                      ╰── input type: string
    ╰
  ```


# Tests + Formatting
Tests added
Fmt + clippy OK

# After Submitting
Maybe indicate that in the release notes
I added an example in the command, so the documentation will be
automatically updated.
2025-04-10 15:33:06 +02:00
Darren Schroeder
29eb109b1e
try to fix datetime-diff for ms, us, ns (#15537)
# Description

This PR tries to fix the datetime-diff custom command so that it
includes ms, us, ns.

Difference in the banner in 2 separate starts.

### Old
```nushell
It's been this long since Nushell's first commit:
5yrs 10months 29days 9hrs 1min 47secs
```

### New
```nushell
It's been this long since Nushell's first commit:
5yrs 10months 29days 9hrs 1min 22secs 49ms 885µs
```

There should be ns above on the new one, not sure why there isn't. It
could have something to do with how the banner works but i'll save that
for another PR.

🤔 It could be because there are no fractional seconds in the math?
`datetime-diff (date now) 2019-05-10T09:59:12-07:00`. However, I'm not
sure why `date now` has no nanoseconds. Oh, wait. I think that's because
MacOS doesn't have nanosecond precision?
```
❯ ^date +%s.%N
1744251636.365003000
```

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

/cc @NotTheDr01ds 

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-10 06:52:11 -05:00
zc he
70d8163181
fix(lsp): more accurate command name highlight/rename (#15540)
# Description

The `command` version of #15523 

# User-Facing Changes

Before:

<img width="394" alt="image"
src="https://github.com/user-attachments/assets/cdd1954d-c120-4aa4-8625-8a0f817ddebf"
/>

After:

<img width="431" alt="image"
src="https://github.com/user-attachments/assets/66fa17cd-2e6f-4305-a08a-df1c1617cfe8"
/>

And the renaming of that command finally works as expected.

Of course the identification of module prefixes in command calls is
still missing. I kinda feel there's no power-efficient way to do it.
I'll put low priority to that feature.

# Tests + Formatting

+1

# After Submitting
2025-04-10 06:26:43 -05:00
zc he
e4cef8a154
fix(lsp): several edge cases of inaccurate references (#15523)
# Description

Sometimes recognizing identical concepts in nushell can be difficult.
This PR fixes some cases.

# User-Facing Changes

## Before:

<img width="317" alt="image"
src="https://github.com/user-attachments/assets/40567fd2-4cf4-44bb-8845-5f39935f41bb"
/>
<img width="317" alt="image"
src="https://github.com/user-attachments/assets/0cc21aab-8c8a-4bdd-adaf-70117e46c88d"
/>
<img width="276" alt="image"
src="https://github.com/user-attachments/assets/2820f958-b1aa-4bf1-b2ec-36e3191dd1aa"
/>
<img width="311" alt="image"
src="https://github.com/user-attachments/assets/407fb20f-ca5a-42a2-b0ac-791a7ee8497a"
/>

## After:

<img width="317" alt="image"
src="https://github.com/user-attachments/assets/91ca595f-36c5-4081-ba19-4800eb89cbec"
/>
<img width="317" alt="image"
src="https://github.com/user-attachments/assets/222aa0d1-b9c6-441c-8ecd-66ae91c7d397"
/>
<img width="275" alt="image"
src="https://github.com/user-attachments/assets/7b3122d3-ed5a-4bee-8e35-5ef01abc25a1"
/>
<img width="316" alt="image"
src="https://github.com/user-attachments/assets/2c026055-5962-4d4c-97d4-c453a2fef82b"
/>

# Tests + Formatting

+3

# After Submitting
2025-04-09 21:15:35 -05:00
zc he
15146e68ad
fix(lsp): workspace wide ops may panic in certain conditions (#15514)
# Description

I've made the panic reproducible in test case
`workspace::tests::quoted_command_reference_in_workspace`.
This PR fixes that by parsing + merging 1 more time, IMO it's a small
price to pay for workspace-wide heavy requests.

# User-Facing Changes

bug fix

# Tests + Formatting

made 1 case harder

# After Submitting
2025-04-09 20:38:17 -05:00
Jack Wright
b0f9cda9b5
Introduction of NuDataType and polars dtype (#15529)
# Description
This pull request does a lot of the heavy lifting needed to supported
more complex dtypes like categorical dtypes. It introduces a new
CustomValue, NuDataType and makes NuSchema a full CustomValue. Further
more it introduces a new command `polars into-dtype` that allows a dtype
to be created. This can then be passed into schemas when they are
created.

```nu
> ❯ : let dt = ("str" | polars to-dtype)

> ❯ : [[a b]; ["one" "two"]] | polars into-df -s {a: $dt, b: str} | polars schema
╭───┬─────╮
│ a │ str │
│ b │ str │
╰───┴─────╯
```

# User-Facing Changes
- Introduces new command `polars into-dtype`, allows dtype variables to
be passed in during schema creation.
2025-04-09 08:13:49 -07:00
dependabot[bot]
173162df2e
build(deps): bump tokio from 1.44.1 to 1.44.2 (#15521)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.1 to 1.44.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tokio/releases">tokio's
releases</a>.</em></p>
<blockquote>
<h2>Tokio v1.44.2</h2>
<p>This release fixes a soundness issue in the broadcast channel. The
channel
accepts values that are <code>Send</code> but <code>!Sync</code>.
Previously, the channel called
<code>clone()</code> on these values without synchronizing. This release
fixes the channel
by synchronizing calls to <code>.clone()</code> (Thanks Austin Bonander
for finding and
reporting the issue).</p>
<h3>Fixed</h3>
<ul>
<li>sync: synchronize <code>clone()</code> call in broadcast channel (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>:
<a
href="https://redirect.github.com/tokio-rs/tokio/pull/7232">tokio-rs/tokio#7232</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ec4b1d7215"><code>ec4b1d7</code></a>
chore: forward port 1.43.x</li>
<li><a
href="e3c3a56718"><code>e3c3a56</code></a>
Merge branch 'tokio-1.43.x' into forward-port-1.43.x</li>
<li><a
href="a7b658c35b"><code>a7b658c</code></a>
chore: prepare Tokio v1.43.1 release</li>
<li><a
href="c1c8d1033d"><code>c1c8d10</code></a>
Merge remote-tracking branch 'origin/tokio-1.38.x' into
forward-port-1.38.x</li>
<li><a
href="aa303bc205"><code>aa303bc</code></a>
chore: prepare Tokio v1.38.2 release</li>
<li><a
href="7b6ccb515f"><code>7b6ccb5</code></a>
chore: backport CI fixes</li>
<li><a
href="4b174ce2c9"><code>4b174ce</code></a>
sync: fix cloning value when receiving from broadcast channel</li>
<li>See full diff in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio&package-manager=cargo&previous-version=1.44.1&new-version=1.44.2)](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)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nushell/nushell/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-09 13:15:44 +08:00
dependabot[bot]
c0b944edb6
build(deps): bump indexmap from 2.8.0 to 2.9.0 (#15531)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.8.0 to
2.9.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md">indexmap's
changelog</a>.</em></p>
<blockquote>
<h2>2.9.0 (2025-04-04)</h2>
<ul>
<li>Added a <code>get_disjoint_mut</code> method to
<code>IndexMap</code>, matching Rust 1.86's
<code>HashMap</code> method.</li>
<li>Added a <code>get_disjoint_indices_mut</code> method to
<code>IndexMap</code> and <code>map::Slice</code>,
matching Rust 1.86's <code>get_disjoint_mut</code> method on
slices.</li>
<li>Deprecated the <code>borsh</code> feature in favor of their own
<code>indexmap</code> feature,
solving a cyclic dependency that occured via
<code>borsh-derive</code>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1818d4140d"><code>1818d41</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/387">#387</a>
from cuviper/release-2.9.0</li>
<li><a
href="9f4998341b"><code>9f49983</code></a>
Release 2.9.0</li>
<li><a
href="582a90fda3"><code>582a90f</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/386">#386</a>
from cuviper/de-borsh</li>
<li><a
href="90117397b6"><code>9011739</code></a>
Deprecate the &quot;borsh&quot; feature</li>
<li><a
href="0a836e8648"><code>0a836e8</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/238">#238</a>
from NiklasJonsson/get_many_mut</li>
<li><a
href="434d7ac6d1"><code>434d7ac</code></a>
Avoid let-else for MSRV's sake</li>
<li><a
href="5be552d557"><code>5be552d</code></a>
Implement additional suggestions from review</li>
<li><a
href="4e1d8cef47"><code>4e1d8ce</code></a>
Address review feedback</li>
<li><a
href="5aec9ec674"><code>5aec9ec</code></a>
Implement get_disjoint_mut for arrays of keys</li>
<li><a
href="d10de30e74"><code>d10de30</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/385">#385</a>
from iajoiner/docs/macros</li>
<li>Additional commits viewable in <a
href="https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.9.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=indexmap&package-manager=cargo&previous-version=2.8.0&new-version=2.9.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>
2025-04-09 13:15:29 +08:00
dependabot[bot]
26699d96eb
build(deps): bump titlecase from 3.4.0 to 3.5.0 (#15530)
Bumps [titlecase](https://github.com/wezm/titlecase) from 3.4.0 to
3.5.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/wezm/titlecase/releases">titlecase's
releases</a>.</em></p>
<blockquote>
<h2>Version 3.5.0</h2>
<ul>
<li>Preserve uppcase text in brackets (like acronyms) by <a
href="https://github.com/carlocorradini"><code>@​carlocorradini</code></a>
in <a
href="https://redirect.github.com/wezm/titlecase/pull/35">#35</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/wezm/titlecase/compare/v3.4.0...v3.5.0">https://github.com/wezm/titlecase/compare/v3.4.0...v3.5.0</a></p>
<ul>
<li><a
href="https://releases.wezm.net/titlecase/v3.5.0/titlecase-v3.5.0-amd64-unknown-freebsd.tar.gz">FreeBSD
13+ amd64</a></li>
<li><a
href="https://releases.wezm.net/titlecase/v3.5.0/titlecase-v3.5.0-x86_64-unknown-linux-musl.tar.gz">Linux
x86_64</a></li>
<li><a
href="https://releases.wezm.net/titlecase/v3.5.0/titlecase-v3.5.0-universal-apple-darwin.tar.gz">MacOS
Universal</a></li>
<li><a
href="https://releases.wezm.net/titlecase/v3.5.0/titlecase-v3.5.0-x86_64-pc-windows-msvc.zip">Windows
x86_64</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/wezm/titlecase/blob/v3.5.0/Changelog.md">titlecase's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/wezm/titlecase/releases/tag/v3.5.0">3.5.0</a></h2>
<ul>
<li>Preserve uppercase text in brackets, such as acronyms
<a href="https://redirect.github.com/wezm/titlecase/pull/35">#35</a>.
Thanks <a
href="https://github.com/carlocorradini"><code>@​carlocorradini</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ec84ce433b"><code>ec84ce4</code></a>
Version 3.5.0</li>
<li><a
href="97faf731b4"><code>97faf73</code></a>
fix: allow acronyms between '/'</li>
<li><a
href="106e3d4103"><code>106e3d4</code></a>
feat: acronym</li>
<li>See full diff in <a
href="https://github.com/wezm/titlecase/compare/v3.4.0...v3.5.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=titlecase&package-manager=cargo&previous-version=3.4.0&new-version=3.5.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>
2025-04-09 13:15:24 +08:00
Loïc Riegel
08940ba4f8
bugfix: wrong display of human readable string (#15522)
I think after that we can close  #14790

# Description
So the issue was the tiny time delta between the moment the "date
form-human" command is executed, and the moment the value gets
displayed, using chrono_humanize.

When in inputing "in 30 seconds", we currently get:
```
[crates\nu-protocol\src\value\mod.rs:950:21] HumanTime::from(*val) = HumanTime(
    TimeDelta {
        secs: 29,
        nanos: 992402700,
    },
)```
And with "now":
```
crates\nu-protocol\src\value\mod.rs:950:21] HumanTime::from(*val) =
HumanTime(
    TimeDelta {
        secs: -1,
        nanos: 993393200,
    },
)
```

My solution is to round this timedelta to seconds and pass this to chrono_humanize.
Example: instead of passing (-1s + 993393200ns), we pass 0s.
Example: instead of passing (29s + 992402700ns), we pass 30s


# User-Facing Changes
Before 🔴 
```nushell
~> "in 3 days" | date from-human
Fri, 11 Apr 2025 09:06:36 +0200 (in 2 days)
~> "in 30 seconds" | date from-human
Tue, 8 Apr 2025 09:07:09 +0200 (in 29 seconds)
```

After those changes 🟢 
```nushell
~> "in 3 days" | date from-human
Fri, 11 Apr 2025 09:03:47 +0200 (in 3 days)
~> "in 30 seconds" | date from-human
Tue, 8 Apr 2025 09:04:28 +0200 (in 30 seconds)
```

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
2025-04-08 06:29:16 -05:00
Stefan Holderbach
ecb9799b6a
Fix future clippy lints (#15519)
- suggestions for tersity using helpers
2025-04-08 08:51:12 +08:00
zc he
a886e30e04
fix(lsp): parser_info based id detection for use/overlay keywords (#15517)
# Description

Now, with PWD correctly set in #15470 , identifiers in
`use/hide/overlay` commands can be identified using a more robust
method, i.e. module_id from `parser_info`.

# User-Facing Changes

bug fix

# Tests + Formatting

+1 (fails without this PR)

# After Submitting
2025-04-07 19:31:03 -05:00
pyz4
147009a161
polars into-df/polars into-lazy: --schema will not throw error if only some columns are defined (#15473)
# Description
The current implementation of `polars into-df` and `polars into-lazy`
will throw an error if `--schema` is provided but not all columns are
defined. This PR seeks to remove this requirement so that when a partial
`--schema` is provided, the types on the defined columns are overridden
while the remaining columns take on their default types.

**Current Implementation**
```
$ [[a b]; [1 "foo"] [2 "bar"]] | polars into-df -s {a: str} | polars schema
Error:   × Schema does not contain column: b
   ╭─[entry #88:1:12]
 1 │ [[a b]; [1 "foo"] [2 "bar"]] | polars into-df -s {a: str} | polars schema
   ·            ─────
   ╰────
```

**New Implementation (no error thrown on partial schema definition)**
Column b is not defined in `--schema`
```
$ [[a b]; [1 "foo"] [2 "bar"]] | polars into-df --schema {a: str} | polars schema
╭───┬─────╮
│ a │ str │
│ b │ str │
╰───┴─────╯
```

# User-Facing Changes
Soft breaking change: The user's previous (erroneous) code that would
have thrown an error would no longer throw an error. The user's previous
working code will still work.

# Tests + Formatting


# After Submitting
2025-04-07 15:58:37 -07:00
Loïc Riegel
12a1eefe73
Move human date parsing into new command date from-human (#15495)
No related issue.
Decided in nushell's weekly meeting: see [meeting
notes](https://hackmd.io/rA1YecqjRh6I5m8dTq7BHw)

# Description
Converting a date as a human readable string to a datetime:
- currently: using the ``into datetime`` command
- after this change: using ``date from-human`` command

Also moved the ``--list-human`` flag to the new command.

# User-Facing Changes
- Users have to use a new command for parsing human readable datetimes.

Result:
```nushell
~> date from-human --list
╭────┬───────────────────────────────────┬──────────────╮
│  # │ parseable human datetime examples │    result    │
├────┼───────────────────────────────────┼──────────────┤
│  0 │ Today 18:30                       │ in 6 hours   │
│  1 │ 2022-11-07 13:25:30               │ 2 years ago  │
│  2 │ 15:20 Friday                      │ in 6 days    │
│  3 │ This Friday 17:00                 │ in 6 days    │
│  4 │ 13:25, Next Tuesday               │ in 3 days    │
│  5 │ Last Friday at 19:45              │ 16 hours ago │
│  6 │ In 3 days                         │ in 2 days    │
│  7 │ In 2 hours                        │ in 2 hours   │
│  8 │ 10 hours and 5 minutes ago        │ 10 hours ago │
│  9 │ 1 years ago                       │ a year ago   │
│ 10 │ A year ago                        │ a year ago   │
│ 11 │ A month ago                       │ a month ago  │
│ 12 │ A week ago                        │ a week ago   │
│ 13 │ A day ago                         │ a day ago    │
│ 14 │ An hour ago                       │ an hour ago  │
│ 15 │ A minute ago                      │ a minute ago │
│ 16 │ A second ago                      │ now          │
│ 17 │ Now                               │ now          │
╰────┴───────────────────────────────────┴──────────────╯

~> "2 days ago" | date from-human
Thu, 3 Apr 2025 12:03:33 +0200 (2 days ago)

~> "2 days ago" | into datetime
Error: nu:🐚:datetime_parse_error

  × Unable to parse datetime: [2 days ago].
   ╭─[entry #5:1:1]
 1 │ "2 days ago" | into datetime
   · ──────┬─────
   ·       ╰── datetime parsing failed
   ╰────
  help: Examples of supported inputs:
         * "5 pm"
         * "2020/12/4"
         * "2020.12.04 22:10 +2"
         * "2020-04-12 22:10:57 +02:00"
         * "2020-04-12T22:10:57.213231+02:00"
         * "Tue, 1 Jul 2003 10:52:37 +0200"
```

# Tests + Formatting
Fmt, clippy 🆗 
Tests 🆗 

> Note: I was able to reactivate one unit test in the ``into datetime``
command

# After Submitting
Here since the user facing changes are significant, I think we should
communicate in the released notes. Otherwise the automatically generated
documentation should be enough IMO.
2025-04-07 07:44:55 -05:00
Stefan Holderbach
0f8f3bcf9a
Fix Exbibyte parsing (#15515)
Closes #15502

# Description
The parsing of Exbibytes used the wrong base unit before converting.

# User-Facing Changes
`1EiB` etc. will now be parsed correctly

# Tests + Formatting
(-)
2025-04-07 13:36:23 +02:00
Loïc Riegel
639f4bd499
Replace some PipelineMismatch by OnlySupportsThisInputType by shell error (#15447)
sub-issue of #10698 according to @sholderbach 

(Description largely edited, since the scope of the PR changed)

# Description
Context: `ShellError::OnlySupportsThisInputType` was a duplicate of
`ShellError::PipelineMismatch`

so I
- replaced some occurences of PipelineMismatch by
OnlySupportsThisInputType

For another PR
- replace the remaining occurences
- removed OnlySupportsThisInputType from nu-protocol

# User-Facing Changes
The error message will be different -> but consistent

# Tests + Formatting
OK

# After Submitting
Nothing required
2025-04-07 12:25:27 +02:00
Douglas
e82df7c1c9
Reminder comment to update doc when adding $nu constants (#15481)
# Description

As requested in review on
https://github.com/nushell/nushell.github.io/pull/1860 - This adds a
reminder comment requesting that contributors update that doc page when
adding new constants.

# User-Facing Changes

None

# Tests + Formatting

Comment-only

# After Submitting

This PR should only be merged after
https://github.com/nushell/nushell.github.io/pull/1860 is merged into
the doc.
2025-04-07 00:38:17 -04:00
zc he
41f4d0dcbc
refactor(lsp): align markdown doc string with output of --help (#15508)
#15499 reminds me of the discrepancies between lsp hover docs and
`--help` outputs.

# Description

# User-Facing Changes

Before:

<img width="610" alt="image"
src="https://github.com/user-attachments/assets/f73f7ace-5c1b-4380-9921-fb4783bdb187"
/>

After:

<img width="610" alt="image"
src="https://github.com/user-attachments/assets/96de3ffe-e37b-41b1-88bb-123eeb72ced2"
/>

Output of `if -h` as a reference:

```
Usage:
  > if <cond> <then_block> (else <else_expression>)

Flags:
  -h, --help: Display the help message for this command

Parameters:
  cond <variable>: Condition to check.
  then_block <block>: Block to run if check succeeds.
  "else" + <one_of(block, expression)>: Expression or block to run when the condition is false. (optional)

```

# Tests + Formatting

Refined

# After Submitting
2025-04-06 08:37:59 -05:00
zc he
eb2a91ea7c
fix(lsp): keywords in completion snippets (#15499)
# Description

Fixes some leftover issues for keyword snippets of #15494

# Tests + Formatting

Adjusted
2025-04-06 08:36:59 -05:00
dependabot[bot]
b81d46574c
build(deps): bump openssl from 0.10.70 to 0.10.72 (#15493)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.70
to 0.10.72.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sfackler/rust-openssl/releases">openssl's
releases</a>.</em></p>
<blockquote>
<h2>openssl-v0.10.72</h2>
<h2>What's Changed</h2>
<ul>
<li>make set_rsa_oaep_md visible to boringssl config by <a
href="https://github.com/frncs-rss"><code>@​frncs-rss</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2372">sfackler/rust-openssl#2372</a></li>
<li>Fix typo in openssl-sys build script by <a
href="https://github.com/rushilmehra"><code>@​rushilmehra</code></a> in
<a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2375">sfackler/rust-openssl#2375</a></li>
<li>Unify the two BoringSSL codepaths a bit and simplify init by <a
href="https://github.com/davidben"><code>@​davidben</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2377">sfackler/rust-openssl#2377</a></li>
<li>pkey_ctx: Fix link to the corresponding OpenSSL function by <a
href="https://github.com/Jakuje"><code>@​Jakuje</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2378">sfackler/rust-openssl#2378</a></li>
<li>fix test on MSRV by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2383">sfackler/rust-openssl#2383</a></li>
<li>Add support for AWS-LC to openssl and openssl-sys crates by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/1805">sfackler/rust-openssl#1805</a></li>
<li>Enable additional capabilities for AWS-LC by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2386">sfackler/rust-openssl#2386</a></li>
<li>Use --experimental with bindgen-cli with aws-lc build by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2389">sfackler/rust-openssl#2389</a></li>
<li>Fixed two UAFs and bumped versions for release by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2390">sfackler/rust-openssl#2390</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jakuje"><code>@​Jakuje</code></a> made
their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2378">sfackler/rust-openssl#2378</a></li>
<li><a href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a>
made their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/1805">sfackler/rust-openssl#1805</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72">https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72</a></p>
<h2>openssl-v0.10.71</h2>
<h2>What's Changed</h2>
<ul>
<li>Expose rc2 ciphers on symm::Cipher by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2361">sfackler/rust-openssl#2361</a></li>
<li>add full Apache license file to openssl by <a
href="https://github.com/frncs-rss"><code>@​frncs-rss</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2366">sfackler/rust-openssl#2366</a></li>
<li>Release openssl v0.10.71 and openssl-sys v0.9.106 by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2369">sfackler/rust-openssl#2369</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/frncs-rss"><code>@​frncs-rss</code></a>
made their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2366">sfackler/rust-openssl#2366</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.70...openssl-v0.10.71">https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.70...openssl-v0.10.71</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="87085bd678"><code>87085bd</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2390">#2390</a>
from alex/uaf-fix</li>
<li><a
href="d1a12e2157"><code>d1a12e2</code></a>
Fixed two UAFs and bumped versions for release</li>
<li><a
href="7c7b2e6c9f"><code>7c7b2e6</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2389">#2389</a>
from skmcgrail/aws-lc-follow-up</li>
<li><a
href="34a477bff2"><code>34a477b</code></a>
Use --experimental with bindgen-cli with aws-lc build</li>
<li><a
href="d4bf071064"><code>d4bf071</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2386">#2386</a>
from skmcgrail/aws-lc-follow-up</li>
<li><a
href="a86bf670c4"><code>a86bf67</code></a>
Remove comment</li>
<li><a
href="705dbfb2ee"><code>705dbfb</code></a>
Fix test</li>
<li><a
href="e0df413d46"><code>e0df413</code></a>
Skip final call for LibreSSL 4.1.0 for CCM mode</li>
<li><a
href="2f1164b5e8"><code>2f1164b</code></a>
Enable additional capabilities for AWS-LC</li>
<li><a
href="dde9ffb360"><code>dde9ffb</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/1805">#1805</a>
from skmcgrail/aws-lc-support-final</li>
<li>Additional commits viewable in <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.70...openssl-v0.10.72">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=openssl&package-manager=cargo&previous-version=0.10.70&new-version=0.10.72)](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)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nushell/nushell/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-06 10:24:55 +02:00
Wind
1c6c85d35d
Fix clippy (#15489)
# Description
There are some clippy(version 0.1.86) errors on nushell repo. This pr is
trying to fix it.

# User-Facing Changes
Hopefully none.

# Tests + Formatting
NaN

# After Submitting
NaN
2025-04-06 09:49:28 +08:00
Carson Riker
67ea25afca
Limit Allowed serde_json Versions to Match Usage (#15504)
Fixes #15503 

# Description

Our usage of `serde_json::Error::io_error_kind` is improperly handled in
the workspace version specifier.

We use this method in `nu-plugin-core`


f25525be6c/crates/nu-plugin-core/src/serializers/json.rs (L77-L106)

It was added in [`serde_json`
v1.0.97](https://github.com/serde-rs/json/releases/tag/v1.0.97).
Previously, we specified our version requirement only as `1.0`. Now, it
is `>=1.0.97,<1.1`, which correctly describes our maximum range of
compatibility.

# User-Facing Changes
None

# Tests + Formatting
No code has changed. Recent releases are identical. This only effect
usage of nushell as a library

# After Submitting
No doc changes should be needed. This prevents certain compiler errors,
but will not change the behavior of any compiled project.
2025-04-05 23:31:05 +02:00
Darren Schroeder
f25525be6c
Revert "Fix #15394 for table -e wrapping issue" (#15498)
Reverts nushell/nushell#15407
Reopens https://github.com/nushell/nushell/issues/15394

@zhiburt Reverting due to some strange coloring I didn't notice before.
Notice the last row. This is the command that produced this table `help
commands | group-by command_type | get external`

![image](https://github.com/user-attachments/assets/ea2d14e3-0efd-4ef2-a3a9-bccbf41a3eae)

This is what it looks like after the revert. Notice the column header
colors. Wrapping is also a little bit different even though my terminal
size didn't change. Notice `search_terms` was kind of eaten above.

![image](https://github.com/user-attachments/assets/526eb8e2-eb87-4aeb-89c1-b88f65354368)
2025-04-05 09:24:16 -05:00
zc he
a72f94f452
feat(lsp): snippet style completion for commands (#15494)
# Description

For example: here's what happens after selecting the `if` command from
the completion menu:

<img width="318" alt="image"
src="https://github.com/user-attachments/assets/752a3bae-ce92-4473-bc96-01032d9295aa"
/>

<img width="319" alt="image"
src="https://github.com/user-attachments/assets/c4bf0c25-ec42-4416-b93e-4925a4650e73"
/>

Missing arguments are inserted as placeholders in a snippet, just as
function name completions in other lsp servers like rust-analyzer and
clangd.

# User-Facing Changes

Press tab to navigate
Flags still need to be added manually

# Tests + Formatting

Refined

# After Submitting
2025-04-05 09:23:27 -05:00
zc he
210c6f1c43
fix(lsp): more accurate PWD: from env -> parent dir of current file (#15470)
# Description

Some editors like neovim will provide "workspace root" as PWD, which can
mess up file completion results.

# User-Facing Changes

bug fix

# Tests + Formatting

adjusted

# After Submitting
2025-04-05 08:41:34 -05:00
Maxim Zhiburt
0cd90e2388
Fix #15394 for table -e wrapping issue (#15407)
close #15394
cc @fdncred
2025-04-05 08:26:50 -05:00
pyz4
7ca2a6f8ac
FIX polars as-datetime: ignores timezone information on conversion (#15490)
# Description
This PR seeks to fix an error in `polars as-datetime` where timezone
information is entirely ignored. This behavior raises a host of silent
errors when dealing with datetime conversions (see example below).

## Current Implementation
Timezones are entirely ignored and datetimes with different timezones
are converted to the same naive datetimes even when the user
specifically indicates that the timezone should be parsed. For example,
"2021-12-30 00:00:00 +0000" and "2021-12-30 00:00:00 -0400" will both be
parsed to "2021-12-30 00:00:00" even when the format string specifically
includes "%z".

```
$ ["2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z"
╭───┬───────────────────────╮
│ # │       datetime        │
├───┼───────────────────────┤
│ 0 │ 12/30/2021 12:00:00AM │ 
│ 1 │ 12/30/2021 12:00:00AM │ <-- Same datetime even though the first is +0000 and second is -0400
╰───┴───────────────────────╯

$ ["2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z" | polars schema
╭──────────┬──────────────╮
│ datetime │ datetime<ns> │
╰──────────┴──────────────╯
```

## New Implementation
Datetimes are converted to UTC and timezone information is retained.

```
$ "2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z"
╭───┬───────────────────────╮
│ # │       datetime        │
├───┼───────────────────────┤
│ 0 │ 12/30/2021 12:00:00AM │
│ 1 │ 12/30/2021 04:00:00AM │ <-- Converted to UTC
╰───┴───────────────────────╯

$ ["2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z" | polars schema
╭──────────┬───────────────────╮
│ datetime │ datetime<ns, UTC> │
╰──────────┴───────────────────╯
```

The user may intentionally ignore timezone information by setting the
`--naive` flag.
```
$ ["2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z" --naive
╭───┬───────────────────────╮
│ # │       datetime        │
├───┼───────────────────────┤
│ 0 │ 12/30/2021 12:00:00AM │
│ 1 │ 12/30/2021 12:00:00AM │ <-- the -0400 offset is ignored when --naive is set
╰───┴───────────────────────╯

$ ["2021-12-30 00:00:00 +0000" "2021-12-30 00:00:00 -0400"] | polars into-df | polars as-datetime "%Y-%m-%d %H:%M:%S %z" --naive | polars schema
╭──────────┬──────────────╮
│ datetime │ datetime<ns> │
╰──────────┴──────────────╯
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
`polars as-datetime` will now account for timezone information and
return type `datetime<ns,UTC>` rather than `datetime<ns>` by default.
The user can replicate the previous behavior by setting `--naive`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Tests that incorporated `polars as-datetime` had to be tweaked to
include `--naive` flag to replicate previous behavior.

# 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.
-->
2025-04-04 09:43:21 -07:00
Nils Feierabend
237a685605
Consider PATH when running command is nuscript in windows (#15486)
<!--
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 #15476

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

Consider PATH when checking for potential_nuscript_in_windows to allow
executing scripts which are in PATH without having to full path address
them. It previously only checked the current working directory so only
relative paths to cwd and full path worked.

The current implementation runs this then through cmd.exe /D /C which
can run it with assoc and ftype set for nushell scripts.
We could instead run it through nu as `std::env::current_exe()` avoiding
the cmd call and the need for assoc and ftype (see:
8b25173f02).
But ive left the current implementation for this intact to not change
implementation details, avoid a bigger change and leave this open for
discussion here since im not sure if this has any major implications.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
This would now run every external command through PATH an additional
time on windows, so potentially twice. I dont think this has any bigger
effect.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-04 06:35:36 -05:00
Darren Schroeder
2bf0397d80
bump to the latest rust version (#15483)
# Description

This PR bumps nushell to use the latest rust version 1.84.1.
2025-04-03 21:08:59 +02:00
Wind
5ec823996a
update shadow-rs to version 1 (#15462)
# Description
Noticed there is a build failure in #15420, because `ShadowBuilder`
struct is guarded by `build` feature. This pr is going to update it.

# User-Facing Changes
Hopefully none.

# Tests + Formatting
None

# After Submitting
None

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-04-03 14:08:51 +02:00
Loïc Riegel
67b6188b19
feat: into duration accepts floats (#15297)
Issue #9887 which can be closed after this is merged.

# Description

This allows the "into duration" command to accept floats as inputs.

Examples:
<img width="767" alt="image"
src="https://github.com/user-attachments/assets/da181f2a-7ad6-4efb-a6db-f9c6d8929c71"
/>

<img width="710" alt="image"
src="https://github.com/user-attachments/assets/78623a39-33ad-42a0-9324-a147be86f95c"
/>

**How it works:**

Using strings, like `"1.234sec" | into duration`, is already working, so
if a user inputs `1.234 | into duration --sec`, I just convert this back
to a string and use the previous conversion functions.

**Limitations:**

there are some limitation to using floats, but it's a general limitation
that is already present for other use cases:
- only 3 digits are taken into account in the decimal part
- floating durations in nano seconds are always floored and not rounded

<img width="761" alt="image"
src="https://github.com/user-attachments/assets/a9076aab-da03-43f2-927c-c9703fc4f955"
/>


# User-Facing Changes
Users can inject floats with `into duration`

# Tests + Formatting
cargo fmt and clippy OK
Tests OK

# After Submitting
The example I added will automatically become part of the doc, I think
that's enough for documentation.
2025-04-03 14:05:18 +02:00
zc he
df74a0c961
refactor: command identified by name instead of span content (#15471)
This should be a more robust method.

# Description

Previously, `export use` with double-space in between will fail to be
recognized as command `export use`.

# User-Facing Changes

minor bug fix

# Tests + Formatting

test cases made harder

# After Submitting
2025-04-02 13:12:38 +02:00
dependabot[bot]
af6c4bdc9c
build(deps): bump bytesize from 1.3.2 to 1.3.3 (#15468)
Bumps [bytesize](https://github.com/bytesize-rs/bytesize) from 1.3.2 to
1.3.3.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="603a713824"><code>603a713</code></a>
chore: prepare release v1.3.3</li>
<li>See full diff in <a
href="https://github.com/bytesize-rs/bytesize/compare/v1.3.2...v1.3.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bytesize&package-manager=cargo&previous-version=1.3.2&new-version=1.3.3)](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>
2025-04-02 12:18:43 +08:00
dependabot[bot]
d7f26b177a
build(deps): bump crate-ci/typos from 1.31.0 to 1.31.1 (#15469) 2025-04-02 08:59:12 +08:00
pyz4
470d130289
polars cast: add decimal option for dtype parameter (#15464)
<!--
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
This PR expands the `dtype` parameter of the `polars cast` command to
include `decimal<precision, scale>` type. Setting precision to "*" will
compel inferring the value. Note, however, setting scale to a
non-integer value will throw an explicit error (the underlying polars
crate assigns scale = 0 in such a case, but I opted for throwing an
error instead). .

```
$ [[a b]; [1 2] [3 4]] | polars into-df | polars cast decimal<4,2> a | polars schema
╭───┬──────────────╮
│ a │ decimal<4,2> │
│ b │ i64          │
╰───┴──────────────╯

$ [[a b]; [10.5 2] [3.1 4]] | polars into-df | polars cast decimal<*,2> a | polars schema
╭───┬──────────────╮
│ a │ decimal<*,2> │
│ b │ i64          │
╰───┴──────────────╯

$ [[a b]; [10.05 2] [3.1 4]] | polars into-df | polars cast decimal<5,*> a | polars schema
rror:   × Invalid polars data type
   ╭─[entry #25:1:47]
 1 │ [[a b]; [10.05 2] [3.1 4]] | polars into-df | polars cast decimal<5,*> a | polars schema
   ·                                               ─────┬─────
   ·                                                    ╰── `*` is not a permitted value for scale
   ╰────
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
There are no breaking changes. The user has the additional option to
`polars cast` to a decimal type

# Tests + Formatting
Tests have been added to
`nu_plugin_polars/src/dataframe/values/nu_schema.rs`
2025-04-01 16:22:05 -07:00
Darren Schroeder
a23e96c945
update human-date-parser to 3.0 (#15426)
# Description

There's been much debate about whether to keep human-date-parser in
`into datetime`. We saw recently that a new version of the crate was
released that addressed some of our concerns. This PR is to make it
easier to test those fixes.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-04-01 07:18:11 -05:00
132ikl
9ba16dbdaf
Add boolean examples to any and all (#15442)
<!--
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.
-->

Follow-up to #15277 and #15392.

Adds examples to `any` and `all` demonstrating using `any {}` or `all
{}` with lists of booleans.

We have a couple options that work for this use-case, but not sure which
we should recommend. The PR currently uses (1).
1. `any {}` / `all {}`
2. `any { $in }` / `all { $in }`
3. `any { $in == true }` / `all { $in == true }`

Would love to hear your thoughts on the above @fennewald @mtimaN
@fdncred @NotTheDr01ds @ysthakur

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
* Added an extra example for `any` and `all`

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
N/A
# 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.
-->
N/A
2025-04-01 07:17:36 -05:00
Wind
43f9ec295f
remove -s, -p in do (#15456)
# Description
Closes #15450

# User-Facing Changes
do can't use `-s`, `-p` after this pr

# Tests + Formatting
Removed 3 tests.

# After Submitting
NaN
2025-04-01 07:17:05 -05:00
Wind
f39e5b3f37
Update rand and rand_chacha to 0.9 (#15463)
# Description
As description, I think it's worth to move forward to update rand and
rand_chacha to 0.9.

# User-Facing Changes
Hopefully none

# Tests + Formatting
NaN

# After Submitting
NaN
2025-04-01 07:15:39 -05:00
zc he
6c0b65b570
feat(completion): stdlib virtual path completion & exportable completion (#15270)
# Description

More completions for `use` command.

~Also optimizes the span fix of #15238 to allow changing the text after
the cursor.~

# User-Facing Changes

<img width="299" alt="image"
src="https://github.com/user-attachments/assets/a5c45f46-40e4-4c50-9408-7b147ed11dc4"
/>

<img width="383" alt="image"
src="https://github.com/user-attachments/assets/fbeec173-511e-4c72-8995-bc1caa3ef0d3"
/>


# Tests + Formatting

+3

# After Submitting
2025-04-01 07:13:07 -05:00
dependabot[bot]
1dcaffb792
build(deps): bump array-init-cursor from 0.2.0 to 0.2.1 (#15460)
Bumps [array-init-cursor](https://github.com/planus-org/planus) from
0.2.0 to 0.2.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/planus-org/planus/blob/main/CHANGELOG.md">array-init-cursor's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0/">Keep a Changelog</a>,
and this project adheres to <a
href="https://semver.org/spec/v2.0.0.html">Semantic Versioning</a>.</p>
<h2>[Unreleased]</h2>
<h3>Added</h3>
<h3>Fixed</h3>
<h3>Removed</h3>
<h2>[1.1.1] - 2025-03-02</h2>
<h3>Added</h3>
<h3>Fixed</h3>
<ul>
<li>[Rust]: Fix the alignment of structs in unions <a
href="https://redirect.github.com/planus-org/planus/pull/289">#289</a></li>
</ul>
<h3>Removed</h3>
<h2>[1.1.0] - 2025-03-02</h2>
<h3>Added</h3>
<ul>
<li>Bump the Minimum Support Rust Version (MSRV) to 1.75.0</li>
<li>The <code>Primitive</code> and <code>VectorWrite</code> traits are
now marked as unsafe to remind implementers of alignment
constraints</li>
<li>[Rust]: Add support for union vectors <a
href="https://redirect.github.com/planus-org/planus/pull/287">#287</a></li>
<li>Add support for displaying union vectors with <code>planus
view</code> <a
href="https://redirect.github.com/planus-org/planus/pull/287">#287</a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Added extra unsafe blocks to templates to fix warnings for the 2024
edition</li>
<li>Updated tests for the 2024 edition</li>
</ul>
<h3>Removed</h3>
<h2>[1.0.0] - 2024-09-29</h2>
<h3>Added</h3>
<ul>
<li>[Rust]: Added <code>#[allow(dead_code)]</code> to the root of the
generated rust code <a
href="https://redirect.github.com/planus-org/planus/pull/204">#204</a></li>
<li>Added the option <code>ignore_docstring_errors</code> to the app. <a
href="https://redirect.github.com/planus-org/planus/pull/216">#216</a></li>
<li>Get rid of dependency on <code>atty</code> and bump the Minimum
Support Rust Version (MSRV) to 1.70.0. <a
href="https://redirect.github.com/planus-org/planus/pull/220">#220</a></li>
<li>[Rust]: Allow default implementations to be generated for tables
that have fields with (required) vectors, strings, integers and bools.
<a
href="https://redirect.github.com/planus-org/planus/pull/243">#243</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="be6f99afde"><code>be6f99a</code></a>
Add a soundness fix for array-init-cursor (<a
href="https://redirect.github.com/planus-org/planus/issues/294">#294</a>)</li>
<li><a
href="1cf18d16af"><code>1cf18d1</code></a>
Release 1.1.1 (<a
href="https://redirect.github.com/planus-org/planus/issues/290">#290</a>)</li>
<li><a
href="e1928da42c"><code>e1928da</code></a>
Fix alignment of large structs in unions (<a
href="https://redirect.github.com/planus-org/planus/issues/289">#289</a>)</li>
<li><a
href="060ffc788a"><code>060ffc7</code></a>
Release version 1.1.0 (<a
href="https://redirect.github.com/planus-org/planus/issues/288">#288</a>)</li>
<li><a
href="d96b907d3f"><code>d96b907</code></a>
Implement union vectors (<a
href="https://redirect.github.com/planus-org/planus/issues/287">#287</a>)</li>
<li><a
href="08d8c012a5"><code>08d8c01</code></a>
Small fixes (<a
href="https://redirect.github.com/planus-org/planus/issues/286">#286</a>)</li>
<li><a
href="b8129d7691"><code>b8129d7</code></a>
Mark <code>Primitive</code> and <code>VectorWrite</code> as unsafe (<a
href="https://redirect.github.com/planus-org/planus/issues/280">#280</a>)</li>
<li><a
href="b5d9d8194a"><code>b5d9d81</code></a>
Update the test suite (<a
href="https://redirect.github.com/planus-org/planus/issues/283">#283</a>)</li>
<li><a
href="4f04f66577"><code>4f04f66</code></a>
Add extra unsafe blocks as required by 2024 edition (<a
href="https://redirect.github.com/planus-org/planus/issues/282">#282</a>)</li>
<li><a
href="44ffb38190"><code>44ffb38</code></a>
New rust version, new clippy issues to fix</li>
<li>Additional commits viewable in <a
href="https://github.com/planus-org/planus/compare/v0.2.0...array-init-cursor-v0.2.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=array-init-cursor&package-manager=cargo&previous-version=0.2.0&new-version=0.2.1)](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)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nushell/nushell/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 07:03:03 +08:00
migraine-user
ca4222277e
Fix typo in doc_config.nu + small description (#15461)
# Description

```
# table.*
# table_mode (string):
# One of: "default", "basic", "compact", "compact_double", "heavy", "light", "none", "reinforced",
# "rounded", "thin", "with_love", "psql", "markdown", "dots", "restructured", "ascii_rounded",
# or "basic_compact"
# Can be overridden by passing a table to `| table --theme/-t`
$env.config.table.mode = "default"
```
In `doc_config.nu`, it refers to `table_mode` which does not exist under
`$env.config.table`. There is now a short description of this field as
well.
2025-03-31 21:38:50 +02:00
Yash Thakur
5c2bcd068b
Enable exact match behavior for any path with slashes (#15458)
# Description

Closes #14794. This PR enables the strict exact match behavior requested
in #13204 and #14794 for any path containing a slash (#13302 implemented
this for paths ending in slashes).

If any of the components along the way *don't* exactly match a
directory, then the next components will use the old Fish-like
completion behavior rather than the strict behavior.

This change only affects those using prefix matching. Fuzzy matching
remains unaffected.

# User-Facing Changes

Suppose you have the following directory structure:
```
- foo
  - bar
    - xyzzy
  - barbaz
    - xyzzy
- foobar
  - bar
    - xyzzy
  - barbaz
    - xyzzy
```

- If you type `cd foo<TAB>`, you will be suggested `[foo, foobar]`
- This is because `foo` is the last component of the path, so the strict
behavior isn't activated
  - Similarly, `foo/bar` will show you `[foo/bar, foo/barbaz]`
- If you type `foo/bar/x`, you will be suggested `[foo/bar/xyzzy]`
  - This is because `foo` and `bar` both exactly matched a directory
- If you type `foo/b/x`, you will be suggested `[foo/bar/xyzzy,
foo/barbaz/xyzzy]`
- This is because `foo` matches a directory exactly, so `foobar/*` won't
be suggested, but `b` doesn't exactly match a directory, so both `bar`
and `barbaz` are suggested
- If you type `f/b/x`, you will be suggested all four of the `xyzzy`
files above
- If you type `f/bar/x`, you will be suggested all four of the `xyzzy`
files above
- Since `f` doesn't exactly match a directory, every component after it
won't use the strict matching behavior (even though `bar` exactly
matches a directory)

# Tests + Formatting

# After Submitting

This is a pretty minor change but should be mentioned somewhere in the
release notes in case it surprises someone.

---------

Co-authored-by: 132ikl <132@ikl.sh>
2025-03-31 14:19:09 -04:00
Yash Thakur
9aba96604b
Revert "Improve completions for exact matches (Issue #14794)" (#15457)
Reverts nushell/nushell#15387

As pointed out by @132ikl in
https://github.com/nushell/nushell/pull/15387#issuecomment-2764852850,
#15387 had the unintended side effect of not showing all suggestions in
certain cases when that wasn't desired.
2025-03-30 23:41:42 -04:00
vansh284
7be90c2644
Improve completions for exact matches (Issue #14794) (#15387)
<!--
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 #14794.
# 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.
-->

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Makes it so that (even if) the command ends in a slash, exact matches
are still preferred over partial matches.
For example, `foo/bar/as` -> `foo/bar/asdf` but not `foo/bars/asdf`.
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Yash Thakur <45539777+ysthakur@users.noreply.github.com>
2025-03-30 19:56:11 -04:00
dependabot[bot]
7e9e93cf82
build(deps): bump crate-ci/typos from 1.29.10 to 1.30.3 (#15418)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.29.10
to 1.30.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.30.3</h2>
<h2>[1.30.3] - 2025-03-24</h2>
<h3>Features</h3>
<ul>
<li>Support detecting <code>go.work</code> and <code>go.work.sum</code>
files</li>
</ul>
<h2>v1.30.2</h2>
<h2>[1.30.2] - 2025-03-10</h2>
<h3>Features</h3>
<ul>
<li>Add <code>--highlight-words</code> and
<code>--highlight-identifiers</code> for easier debugging of config</li>
</ul>
<h2>v1.30.1</h2>
<h2>[1.30.1] - 2025-03-04</h2>
<h3>Features</h3>
<ul>
<li><em>(action)</em> Create <code>v1</code> tag</li>
</ul>
<h2>v1.30.0</h2>
<h2>[1.30.0] - 2025-03-01</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1221">February
2025</a> changes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h2>[1.30.3] - 2025-03-24</h2>
<h3>Features</h3>
<ul>
<li>Support detecting <code>go.work</code> and <code>go.work.sum</code>
files</li>
</ul>
<h2>[1.30.2] - 2025-03-10</h2>
<h3>Features</h3>
<ul>
<li>Add <code>--highlight-words</code> and
<code>--highlight-identifiers</code> for easier debugging of config</li>
</ul>
<h2>[1.30.1] - 2025-03-04</h2>
<h3>Features</h3>
<ul>
<li><em>(action)</em> Create <code>v1</code> tag</li>
</ul>
<h2>[1.30.0] - 2025-03-01</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1221">February
2025</a> changes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d08e4083f1"><code>d08e408</code></a>
chore: Release</li>
<li><a
href="6f7dfef019"><code>6f7dfef</code></a>
docs: Update changelog</li>
<li><a
href="e601194a5d"><code>e601194</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1261">#1261</a>
from epage/go</li>
<li><a
href="9a82085508"><code>9a82085</code></a>
fix(type): Include support for go.work</li>
<li><a
href="8c7c9e5c7c"><code>8c7c9e5</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1259">#1259</a>
from j-g00da/patch-1</li>
<li><a
href="62bb5ad3c6"><code>62bb5ad</code></a>
docs: fix a typo in README.md</li>
<li><a
href="b48ba0f02b"><code>b48ba0f</code></a>
docs(gh): Mention v1 tag</li>
<li><a
href="7bc041cbb7"><code>7bc041c</code></a>
chore: Release</li>
<li><a
href="4af8a5a1fb"><code>4af8a5a</code></a>
docs: Update changelog</li>
<li><a
href="ec626a1e53"><code>ec626a1</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1257">#1257</a>
from epage/highlight</li>
<li>Additional commits viewable in <a
href="https://github.com/crate-ci/typos/compare/v1.29.10...v1.30.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.29.10&new-version=1.30.3)](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>
2025-03-30 07:59:29 -05:00
Justin Ma
6d1f7cb3e3
Fix upgrading and checking of typos (#15454)
<!--
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.
-->

Fix upgrading and checking of typos
2025-03-30 06:51:52 -05:00
zc he
334cf1862a
feat(lsp): parse_warnings in diagnostics report (#15449)
# Description

Add parse warnings to LSP diagnostics, not particularly useful but
technically should be done.

# User-Facing Changes

# Tests + Formatting

There's no deprecated command to test for now.

# After Submitting
2025-03-29 07:16:44 -05:00
Douglas
49d86855ce
Fixes clip copy stripping control characters when de-ansifying (#15428)
Fixes #15414 by changing the method used to de-ansi-fy the input. Control characters will now be kept when using `clip copy`, but ANSI escape codes will be removed (when not using `--ansi (-a)`)
2025-03-28 19:15:17 -04:00
zc he
5fe97b8d59
fix(completion): completions.external.enable config option not respected (#15443)
Fixes #15441 

# Description

Actually I made a small change to the original behavior:

```
^foo<tab>
```
will still show external commands, regardless of whether it's enabled or
not. I think that's the only thing people want to see when they press
tab with a `^` prefix.

# User-Facing Changes

# Tests + Formatting

+1

# After Submitting

Should I document that minor behavior change somewhere in GitHub.io?

---------

Co-authored-by: Yash Thakur <45539777+ysthakur@users.noreply.github.com>
2025-03-28 18:00:05 -04:00
Loïc Riegel
2bad1371f0
Bugfix/into datetime ignores timezone with format (#15370)
Close #15119 when this is merged

# Description

> Note: my locale is +1

**Before the changes 🔴**

![2025-03-21_00h07_22](https://github.com/user-attachments/assets/6b7db5a7-5541-4a84-9b6a-466a72a6fece)

See the issue for more detailed description of the problem.

**After the changes 🟢**

![2025-03-21_00h07_36](https://github.com/user-attachments/assets/92ec79d8-351c-4fa6-a21d-f0a867a76283)

# User-Facing Changes
The ``into datetime`` command will now work with formatting and time
zones or offset together

# Tests + Formatting
Fmt + clippy OK

**Note about the tests I added**: those tests don't really test my
changes, as they were already passing before my changes. Nevertheless I
thought I could push them

# After Submitting
I don't think anything is necessary
2025-03-28 10:51:42 -05:00
Douglas
3030608de0
Ignore problematic overlapping tests for SHLVL (#15430)
The `$env.SHLVL` tests, while improved, still cause CI (usually local)
an irritating percentage of the time. Until we can come with a better
way of testing, we're going to ignore them.
2025-03-27 14:47:43 -04:00
Loïc Riegel
5d32cd2c40
refactor: ensure range is bounded (#15429)
No linked issue, it's a follow-up of 2 PRs I recently made to improve
some math commands. (#15319)

# Description
Small refactor to simplify the code. It was suggested in the comments of
my previous PR.

# User-Facing Changes
None

# Tests + Formatting
Tests, fmt and clippy OK

# After Submitting
Nothing more required
2025-03-27 14:25:55 +01:00
莯凛
07be33c119
fix(nu-command): support ACL, SELinux, e.g. in cd have_permission check (#15360)
fixes #8095


# Description


This approach is a bit straightforward, call access() check with the
flag `X_OK`.

Zsh[^1], Fish perform this check by the same approach.

[^1]:
435cb1b748/Src/exec.c (L6406)

It could also avoid manual xattrs check on other *nix platforms.

BTW, the execution bit for directories in *nix world means permission to
access it's content,
while the read bit means to list it's content. [^0]

[^0]: https://superuser.com/a/169418

# User-Facing Changes

Users could face less permission check bugs in their `cd` usage.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-03-27 14:23:41 +01:00
Jack Wright
eaf522b41f
Polars cut (#15431)
- fixes #15366 

# Description
Introducing binning commands, `polars cut` and `polars qcut`

# User-Facing Changes
- New command `polars cut`
- New command `polars qcut`
2025-03-27 06:58:34 -05:00
Solomon
e76586ede4
reset argument/redirection state after eval_call errors (#15400)
Closes #15395

# User-Facing Changes

Certain errors no longer leave the argument stack in an unexpected
state:

```diff
 let x: any = 1; try { $x | get path } catch { print caught }
-$.path # extra `print` argument from the failed `get` call
 caught
```

# Description

If `eval_call` fails in `check_input_types` or `gather_arguments`, the
cleanup code is still executed.
2025-03-26 19:41:16 -04:00
dependabot[bot]
1979b61a92
build(deps): bump tokio from 1.43.0 to 1.44.1 (#15419) 2025-03-26 14:12:42 +00:00
zc he
02fcc485fb
fix(parser): skip eval_const if parsing errors detected to avoid panic (#15364)
Fixes #14972 #15321 #14706

# Description

Early returns `NotAConstant` if parsing errors exist in the
subexpression.

I'm not sure when the span of a block will be None, and whether there're
better ways to handle none block spans, like a more suitable ShellError
type.

# User-Facing Changes

# Tests + Formatting

+1, but possibly not the easiest way to do it.

# After Submitting
2025-03-26 15:02:26 +01:00
zc he
55e05be0d8
fix(parser): comments in subexpressions of let/mut (#15375)
Closes #15305

# Description

Basically turns off `skip_comments` of the lex function for right hand
side expressions of `let`/`mut`, just as in `parse_const`.

# User-Facing Changes

Should be none.

# Tests + Formatting

+1

# After Submitting
2025-03-25 21:28:06 +01:00
zc he
e10ac2ede6
fix: command open sets default flags when calling "from xxx" converters (#15383)
Fixes #13722

# Description

Simple solution: `eval_block` -> `eval_call` with empty arguments

# User-Facing Changes

Should be none.

# Tests + Formatting

+1

# After Submitting
2025-03-25 17:40:20 +01:00
zc he
bf1f2d5ebd
fix(completion): ls_color for ~/xxx symlinks (#15403)
# Description

Get style with expanded real path, so that symlinks get highlighted
correctly.

# User-Facing Changes

Before:

<img width="255" alt="image"
src="https://github.com/user-attachments/assets/b1a11cb8-e3d3-4287-bb3b-7d0ec36ba51f"
/>

After:

<img width="255" alt="image"
src="https://github.com/user-attachments/assets/71476b2c-6a31-4d37-8d25-b187a6b4e4d5"
/>


# Tests + Formatting

# After Submitting
2025-03-24 07:50:38 -05:00
Douglas
6aed1b42ae
Add current exe directory to default $NU_PLUGIN_DIRS (#15380)
Quality-of-life improvement - Since core plugins are installed into the
same directory as the Nushell binary, this simply adds that directory to
the default `$NU_PLUGIN_DIRS`.

User-facing changes:

The default directory for core plugins is automatically added to the
`$NU.PLUGIN_DIRS` with no user action necessary. Uses can immediately,
out-of-the-box:

```nushell
plugin add nu_plugin_polars
plugin use polars
```
2025-03-24 08:27:02 -04:00
Firegem
f33a26123c
Fix path add bug when given a record (#15379)
`path add`, when given a record, sets `$env.PATH` according to the value
of the key matching `$nu.os-info.name`. There already existed a check in
place to ensure the correct column existed, but it was never reached
because of an early error on `path expand`ing `null`. This has been
fixed, as well as the out-of-date reference to "darwin" instead of
"macos" in the example.

# User-Facing Changes

`path add` now simply ignores a record that doesn't include a key for the current OS

`path add` also will no longer add duplicate paths.
2025-03-22 08:42:20 -04:00
Douglas
7c160725ed
Rename user-facing 'date' to 'datetime' (#15264)
We only have one valid `datetime` type, but the string representation of
that type was `date`. This PR updates the string representation of the
`datetime` type to be `datetime` and updates other affected
dependencies:

* A `describe` example that used `date`
* The style computer automatically recognized the new change, but also
changed the default `date: purple` to `datetime: purple`.
* Likewise, changed the `default_config.nu` to populate
`$env.config.color_config.datetime`
* Likewise, the dark and light themes in `std/config`
* Updates tests
* Unrelated, but changed the `into value` error messages to use
*"datetime"* if there's an issue.

Fixes #9916 and perhaps others.

## Breaking Changes:

* Code that expected `describe` to return a `date` will now return a
`datetime`
* User configs and themes that override `$env.config.color_config.date`
will need to be updated to use `datetime`
2025-03-21 13:36:21 -04:00
zc he
5832823dff
fix: flatten of empty closures (#15374)
Closes #15373

# Description

Now `ast -f "{||}"` will return

```
╭─content─┬─────shape─────┬─────span──────╮
│ {||}    │ shape_closure │ ╭───────┬───╮ │
│         │               │ │ start │ 0 │ │
│         │               │ │ end   │ 4 │ │
│         │               │ ╰───────┴───╯ │
╰─────────┴───────────────┴───────────────╯
```

Similar to those of `ast -f "[]"`/`ast -f "{}"`

# User-Facing Changes

# Tests + Formatting

I didn't find the right place to do the test, except for the examples of
`ast` command.

# After Submitting
2025-03-21 06:35:18 -05:00
Solomon
3fe355c4a6
enable streaming in random binary/chars (#15361)
# User-Facing Changes

- `random binary` and `random chars` now stream, reducing memory usage
  and allowing interruption with ctrl-c
2025-03-20 19:51:22 +01:00
Solomon
dd56c813f9
preserve variable capture spans in blocks (#15334)
Closes #15160

# User-Facing Changes

Certain "variable not found" errors no longer highlight the surrounding
block.

Before:

```nushell
do {
  match foo {
    _ => $in
  }
}

Error: nu:🐚:variable_not_found

  × Variable not found
   ╭─[entry #1:1:1]
 1 │ ╭─▶ do {
 2 │ │     match foo {
 3 │ │       _ => $in
 4 │ │     }
 5 │ ├─▶ }
   · ╰──── variable not found
```

After:

```nushell
Error: nu:🐚:variable_not_found

  × Variable not found
   ╭─[entry #1:3:10]
 2 │   match foo {
 3 │     _ => $in
   ·          ─┬─
   ·           ╰── variable not found
```
2025-03-20 14:20:28 -04:00
Stefan Holderbach
7a6cfa24fc
Fix to nuon --serialize of closure (#15357)
# Description
Closes #15351

Adds quotes that were missed in #14698 with the proper escaping.


# User-Facing Changes
`to nuon --serialize` will now produce a quoted string instead of
illegal nuon when given a closure

# Tests + Formatting
Reenable the `to nuon` rejection of closures in the base state test.
Added test for quoting.
2025-03-20 17:50:36 +01:00
Loïc Riegel
2ea2a904e8
Math commands can work with bounded ranges and produce list of numbers (#15319)
No associated issue, but follows up #15135. See also discussion on
[discord](https://discord.com/channels/601130461678272522/1349139634281513093/1349139639356624966)
with @sholderbach

# Description

### Math commands `range -> list<number>`

This enables the following math commands:
- abs
- ceil
- floor
- log
- round

to work with ranges. When a range is given, the command will apply the
command on each item of the range, thus producing a list of number as
output.

Example

![image](https://github.com/user-attachments/assets/cff12724-5b26-4dbb-a979-a91c1b5652fc)

The commands still do not work work with unbounded ranges:


![image](https://github.com/user-attachments/assets/40c766a8-763f-461d-971b-2d58d11fc3a6)

And I left out the "mode" command because I think it does not make sense
to use it on ranges...

### Math commands `range -> number`

This was the topic of my previous PR, but for whatever reason I didn't
do `math variance` and `math stddev`.
I had to use `input.try_expand_range` to convert the range into a list
before computing the variance/stddev.


![image](https://github.com/user-attachments/assets/803954e7-1c2a-4c86-8b16-e16518131138)

And same, does not work in infinite ranges:


![image](https://github.com/user-attachments/assets/8bfaae2b-34cc-453d-8764-e42c815d28d3)

### Also done:
- find link in documentation

# User-Facing Changes
- Command signatures changes
- ability to use some commands with unbounded ranges
- ability to use variance and stddev with bounded ranges

# Tests + Formatting
Cargo fmt and clippy OK
Tests OK

# After Submitting
I guess nothing, or maybe release notes?
2025-03-20 17:35:50 +01:00
Ian Manske
dfba62da00
Remove nu-glob's dependency on nu-protocol (#15349)
# Description

This PR solves a circular dependency issue (`nu-test-support` needs
`nu-glob` which needs `nu-protocol` which needs `nu-test-support`). This
was done by making the glob functions that any type that implements
`Interruptible` to remove the dependency on `Signals`.

# After Submitting

Make `Paths.next()` a O(1) operation so that cancellation/interrupt
handling can be moved to the caller (e.g., by wrapping the `Paths`
iterator in a cancellation iterator).
2025-03-20 17:32:41 +01:00
dependabot[bot]
b241e9edd5
build(deps): bump mockito from 1.6.1 to 1.7.0 (#15343) 2025-03-20 15:51:22 +00:00
dependabot[bot]
946cef77f1
build(deps): bump uuid from 1.12.0 to 1.16.0 (#15346) 2025-03-20 15:46:25 +00:00
dependabot[bot]
c99c8119fe
build(deps): bump indexmap from 2.7.0 to 2.8.0 (#15345) 2025-03-20 15:45:58 +00:00
zc he
2b4914608e
fix(completion): inline defined custom completion (#15318)
Fixes #6001 

# Description

<img width="485" alt="image"
src="https://github.com/user-attachments/assets/5aad23ee-07ec-4f1b-8410-a484c2210cd3"
/>

# User-Facing Changes

# Tests + Formatting

+1

# After Submitting
2025-03-20 16:44:41 +01:00
132ikl
8b80ceac32
Add From<IoError> for LabeledError (#15327)
<!--
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.
-->
Adds an `impl From<IoError> for LabeledError`, similar to the existing
`From<ShellError>` implementation. Helpful for plugins.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
N/A 
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
N/A
# 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.
-->
N/A
2025-03-20 11:42:31 -04:00
zc he
e89bb2ee96
fix(lsp): verbose signature help response for less well supported editors (#15353)
# Description

Some editors (like zed) will fail to mark the active parameter if not
set in the outmost structure.

# User-Facing Changes

# Tests + Formatting

Adjusted

# After Submitting
2025-03-20 09:55:03 -05:00
Darren Schroeder
862d53bb6e
add more columns to macos ps -l (#15341)
# Description

This PR adds a few more columns to the macos version of `ps -l` to bring
it more inline with the Linux and Windows version.

Columns added: user_id, priority, process_threads

I also added some comments that describe the TaskInfo structure. I
couldn't find any good information to add to the BSDInfo structure.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-03-20 09:53:19 -05:00
Darren Schroeder
820d0c0959
bump uutils crates to 0.0.30 (#15316)
# Description

Bump the uutils crates to 0.0.30. This bump changed a lot of deps in the
lock file. I'm not sure if we should wait a bit on this or just go for
it.

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

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

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

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-03-20 09:51:48 -05:00
Ian Manske
968eb45fb2
Don't collect job output (#15365)
# Description

Fixes #15359.

# User-Facing Changes

Bug fix.
2025-03-20 09:49:12 -04:00
zc he
2c1d261cca
fix(explore): do not create extra layer for empty entries (#15367)
Fixes #15329

# Description

Stops entering empty list/record with the following message:

<img width="283" alt="image"
src="https://github.com/user-attachments/assets/99cf5ab0-7fd3-4cf7-9db9-00554815a2a7"
/>

# User-Facing Changes

# Tests + Formatting

+5, all vibe coded.

# After Submitting
2025-03-20 06:53:06 -05:00
132ikl
69d1c8e948
Add compile-time assertion of Value's size (#15362)
# Description

Adds an assertion of `Value`'s size, similar to `Instruction` and
`Expr`.
2025-03-20 02:59:06 +00:00
Yash Thakur
2c7ab6e898
Bump to 0.103.1 dev version (#15347)
# Description

Marks development or hotfix
2025-03-19 00:12:01 -04:00
352 changed files with 13819 additions and 4876 deletions

40
.github/labeler.yml vendored Normal file
View File

@ -0,0 +1,40 @@
# A bot for automatically labelling pull requests
# See https://github.com/actions/labeler
dataframe:
- changed-files:
- any-glob-to-any-file:
- crates/nu_plugin_polars/**
std-library:
- changed-files:
- any-glob-to-any-file:
- crates/nu-std/**
ci:
- changed-files:
- any-glob-to-any-file:
- .github/workflows/**
LSP:
- changed-files:
- any-glob-to-any-file:
- crates/nu-lsp/**
parser:
- changed-files:
- any-glob-to-any-file:
- crates/nu-parser/**
pr:plugins:
- changed-files:
- any-glob-to-any-file:
# plugins API
- crates/nu-plugin/**
- crates/nu-plugin-core/**
- crates/nu-plugin-engine/**
- crates/nu-plugin-protocol/**
- crates/nu-plugin-test-support/**
# specific plugins (like polars)
- crates/nu_plugin_*/**

19
.github/workflows/labels.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# Automatically labels PRs based on the configuration file
# you are probably looking for 👉 `.github/labeler.yml`
name: Label PRs
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
if: github.repository_owner == 'nushell'
steps:
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true

View File

@ -8,6 +8,7 @@
name: Nightly Build
on:
workflow_dispatch:
push:
branches:
- nightly # Just for test purpose only with the nightly repo
@ -39,7 +40,7 @@ jobs:
uses: hustcer/setup-nu@v3
if: github.repository == 'nushell/nightly'
with:
version: 0.101.0
version: 0.103.0
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
- name: Prepare for Nightly Release
@ -139,7 +140,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu
@ -197,7 +198,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
# Keep the last a few releases
- name: Delete Older Releases

View File

@ -117,14 +117,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# ----------------------------------------------------------------------------
# Build for Windows without static-link-openssl feature
# ----------------------------------------------------------------------------
if $os in ['windows-latest'] {
if $os =~ 'windows' {
cargo-build-nu
}
# ----------------------------------------------------------------------------
# Prepare for the release archive
# ----------------------------------------------------------------------------
let suffix = if $os == 'windows-latest' { '.exe' }
let suffix = if $os =~ 'windows' { '.exe' }
# nu, nu_plugin_* were all included
let executable = $'target/($target)/release/($bin)*($suffix)'
print $'Current executable file: ($executable)'
@ -148,10 +148,10 @@ For more information, refer to https://www.nushell.sh/book/plugins.html
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
print $'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' {
(do -i { .\output\nu.exe -c 'version' }) | str join
let ver = if $os =~ 'windows' {
(do -i { .\output\nu.exe -c 'version' }) | default '' | str join
} else {
(do -i { ./output/nu -c 'version' }) | str join
(do -i { ./output/nu -c 'version' }) | default '' | str join
}
if ($ver | str trim | is-empty) {
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
@ -177,7 +177,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
} else if $os == 'windows-latest' {
} else if $os =~ 'windows' {
let releaseStem = $'($bin)-($version)-($target)'
@ -221,7 +221,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
}
def 'cargo-build-nu' [] {
if $os == 'windows-latest' {
if $os =~ 'windows' {
cargo build --release --all --target $target
} else {
cargo build --release --all --target $target --features=static-link-openssl

View File

@ -89,7 +89,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu

View File

@ -10,4 +10,4 @@ jobs:
uses: actions/checkout@v4.1.7
- name: Check spelling
uses: crate-ci/typos@v1.29.10
uses: crate-ci/typos@v1.31.1

507
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.83.0"
version = "0.103.0"
rust-version = "1.84.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -66,12 +66,12 @@ alphanumeric-sort = "1.5"
ansi-str = "0.8"
anyhow = "1.0.82"
base64 = "0.22.1"
bracoxide = "0.1.5"
bracoxide = "0.1.6"
brotli = "7.0"
byteorder = "1.5"
bytes = "1"
bytesize = "1.3.1"
calamine = "0.26.1"
bytesize = "1.3.3"
calamine = "0.27"
chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3"
@ -91,8 +91,8 @@ fancy-regex = "0.14"
filesize = "0.2"
filetime = "0.2"
heck = "0.5.0"
human-date-parser = "0.2.0"
indexmap = "2.7"
human-date-parser = "0.3.0"
indexmap = "2.9"
indicatif = "0.17"
interprocess = "2.2.0"
is_executable = "1.0"
@ -110,7 +110,7 @@ md5 = { version = "0.10", package = "md-5" }
miette = "7.5"
mime = "0.3.17"
mime_guess = "2.0"
mockito = { version = "1.6", default-features = false }
mockito = { version = "1.7", default-features = false }
multipart-rs = "0.1.13"
native-tls = "0.2"
nix = { version = "0.29", default-features = false }
@ -135,22 +135,22 @@ quick-xml = "0.37.0"
quickcheck = "1.0"
quickcheck_macros = "1.0"
quote = "1.0"
rand = "0.8"
rand = "0.9"
getrandom = "0.2" # pick same version that rand requires
rand_chacha = "0.3.1"
rand_chacha = "0.9"
ratatui = "0.29"
rayon = "1.10"
reedline = "0.39.0"
reedline = "0.40.0"
rmp = "0.8"
rmp-serde = "1.3"
roxmltree = "0.20"
rstest = { version = "0.23", default-features = false }
rstest_reuse = "0.7"
rusqlite = "0.31"
rust-embed = "8.6.0"
rust-embed = "8.7.0"
scopeguard = { version = "1.2.0" }
serde = { version = "1.0" }
serde_json = "1.0"
serde_json = "1.0.97"
serde_urlencoded = "0.7.1"
serde_yaml = "0.9.33"
sha2 = "0.10"
@ -161,24 +161,24 @@ syn = "2.0"
sysinfo = "0.33"
tabled = { version = "0.17.0", default-features = false }
tempfile = "3.15"
titlecase = "3.4"
titlecase = "3.5"
toml = "0.8"
trash = "5.2"
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
umask = "2.1"
unicode-segmentation = "1.12"
unicode-width = "0.2"
ureq = { version = "2.12", default-features = false }
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
url = "2.2"
uu_cp = "0.0.29"
uu_mkdir = "0.0.29"
uu_mktemp = "0.0.29"
uu_mv = "0.0.29"
uu_touch = "0.0.29"
uu_whoami = "0.0.29"
uu_uname = "0.0.29"
uucore = "0.0.29"
uuid = "1.12.0"
uu_cp = "0.0.30"
uu_mkdir = "0.0.30"
uu_mktemp = "0.0.30"
uu_mv = "0.0.30"
uu_touch = "0.0.30"
uu_whoami = "0.0.30"
uu_uname = "0.0.30"
uucore = "0.0.30"
uuid = "1.16.0"
v_htmlescape = "0.15.0"
wax = "0.6"
web-time = "1.1.0"
@ -197,22 +197,22 @@ unchecked_duration_subtraction = "warn"
workspace = true
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.103.0" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.103.0" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.103.0" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.103.0", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.103.0" }
nu-command = { path = "./crates/nu-command", version = "0.103.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.103.0" }
nu-explore = { path = "./crates/nu-explore", version = "0.103.0" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.103.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.103.0" }
nu-path = { path = "./crates/nu-path", version = "0.103.0" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.103.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.103.0" }
nu-std = { path = "./crates/nu-std", version = "0.103.0" }
nu-system = { path = "./crates/nu-system", version = "0.103.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.103.0" }
nu-cli = { path = "./crates/nu-cli", version = "0.104.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.1" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.1", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.1" }
nu-command = { path = "./crates/nu-command", version = "0.104.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.104.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.104.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.104.1" }
nu-path = { path = "./crates/nu-path", version = "0.104.1" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.104.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.104.1" }
nu-std = { path = "./crates/nu-std", version = "0.104.1" }
nu-system = { path = "./crates/nu-system", version = "0.104.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.104.1" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true }
@ -241,9 +241,9 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.103.0" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.103.0" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.103.0" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.104.1" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.104.1" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.104.1" }
assert_cmd = "2.0"
dirs = { workspace = true }
tango-bench = "0.6"

View File

@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
- [Dorothy](http://github.com/bevry/dorothy)
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
- [x-cmd](https://x-cmd.com/mod/nu)
- [vfox](https://github.com/version-fox/vfox)
## Contributing

View File

@ -5,28 +5,29 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.103.0"
version = "0.104.1"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
nu-command = { path = "../nu-command", version = "0.103.0" }
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-command = { path = "../nu-command", version = "0.104.1" }
nu-std = { path = "../nu-std", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
rstest = { workspace = true, default-features = false }
tempfile = { workspace = true }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
nu-engine = { path = "../nu-engine", version = "0.103.0", features = ["os"] }
nu-glob = { path = "../nu-glob", version = "0.103.0" }
nu-path = { path = "../nu-path", version = "0.103.0" }
nu-parser = { path = "../nu-parser", version = "0.103.0" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.103.0", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.103.0", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.103.0" }
nu-color-config = { path = "../nu-color-config", version = "0.103.0" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["os"] }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }

View File

@ -105,10 +105,9 @@ impl Command for History {
.ok()
})
.map(move |entries| {
entries
.into_iter()
.enumerate()
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
entries.into_iter().enumerate().map(move |(idx, entry)| {
create_sqlite_history_record(idx, entry, long, head)
})
})
.ok_or(IoError::new(
std::io::ErrorKind::NotFound,
@ -140,7 +139,7 @@ impl Command for History {
}
}
fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
//1. Format all the values
//2. Create a record of either short or long columns and values
@ -151,11 +150,8 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
.unwrap_or_default(),
head,
);
let start_timestamp_value = Value::string(
entry
.start_timestamp
.map(|time| time.to_string())
.unwrap_or_default(),
let start_timestamp_value = Value::date(
entry.start_timestamp.unwrap_or_default().fixed_offset(),
head,
);
let command_value = Value::string(entry.command_line, head);

View File

@ -26,7 +26,7 @@ impl Command for HistoryImport {
fn extra_description(&self) -> &str {
r#"Can import history from input, either successive command lines or more detailed records. If providing records, available fields are:
command_line, id, start_timestamp, hostname, cwd, duration, exit_status.
command, start_timestamp, hostname, cwd, duration, exit_status.
If no input is provided, will import all history items from existing history in the other format: if current history is stored in sqlite, it will store it in plain text and vice versa.

View File

@ -27,7 +27,7 @@ impl Completer for AttributeCompletion {
let attr_commands =
working_set.find_commands_by_predicate(|s| s.starts_with(b"attr "), true);
for (name, desc, ty) in attr_commands {
for (decl_id, name, desc, ty) in attr_commands {
let name = name.strip_prefix(b"attr ").unwrap_or(&name);
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
@ -41,7 +41,7 @@ impl Completer for AttributeCompletion {
},
append_whitespace: false,
},
kind: Some(SuggestionKind::Command(ty)),
kind: Some(SuggestionKind::Command(ty, Some(decl_id))),
});
}
@ -78,7 +78,7 @@ impl Completer for AttributableCompletion {
},
append_whitespace: false,
},
kind: Some(SuggestionKind::Command(cmd.command_type())),
kind: Some(SuggestionKind::Command(cmd.command_type(), None)),
});
}

View File

@ -1,7 +1,7 @@
use crate::completions::CompletionOptions;
use nu_protocol::{
engine::{Stack, StateWorkingSet},
Span,
DeclId, Span,
};
use reedline::Suggestion;
@ -28,7 +28,7 @@ pub struct SemanticSuggestion {
// TODO: think about name: maybe suggestion context?
#[derive(Clone, Debug, PartialEq)]
pub enum SuggestionKind {
Command(nu_protocol::engine::CommandType),
Command(nu_protocol::engine::CommandType, Option<DeclId>),
Value(nu_protocol::Type),
CellPath,
Directory,

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
use nu_engine::{column::get_columns, eval_variable};
use nu_protocol::{
@ -17,14 +19,14 @@ pub struct CellPathCompletion<'a> {
fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) {
let (prefix_str, start) = match member {
PathMember::String { val, span, .. } => (val.clone(), span.start),
PathMember::Int { val, span, .. } => (val.to_string(), span.start),
PathMember::String { val, span, .. } => (val, span.start),
PathMember::Int { val, span, .. } => (&val.to_string(), span.start),
};
let prefix_str = prefix_str
.get(..pos + 1 - start)
.map(str::to_string)
.unwrap_or(prefix_str);
(prefix_str, Span::new(start, pos + 1))
let prefix_str = prefix_str.get(..pos + 1 - start).unwrap_or(prefix_str);
// strip wrapping quotes
let quotations = ['"', '\'', '`'];
let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(prefix_str);
(prefix_str.to_string(), Span::new(start, pos + 1))
}
impl Completer for CellPathCompletion<'_> {
@ -101,21 +103,35 @@ pub(crate) fn eval_cell_path(
} else {
eval_constant(working_set, head)
}?;
head_value.follow_cell_path(path_members, false)
head_value
.follow_cell_path(path_members, false)
.map(Cow::into_owned)
}
fn get_suggestions_by_value(
value: &Value,
current_span: reedline::Span,
) -> Vec<SemanticSuggestion> {
let to_suggestion = |s: String, v: Option<&Value>| SemanticSuggestion {
suggestion: Suggestion {
value: s,
span: current_span,
description: v.map(|v| v.get_type().to_string()),
..Suggestion::default()
},
kind: Some(SuggestionKind::CellPath),
let to_suggestion = |s: String, v: Option<&Value>| {
// Check if the string needs quoting
let value = if s.is_empty()
|| s.chars()
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
{
format!("{:?}", s)
} else {
s
};
SemanticSuggestion {
suggestion: Suggestion {
value,
span: current_span,
description: v.map(|v| v.get_type().to_string()),
..Suggestion::default()
},
kind: Some(SuggestionKind::CellPath),
}
};
match value {
Value::Record { val, .. } => val

View File

@ -75,7 +75,10 @@ impl CommandCompletion {
append_whitespace: true,
..Default::default()
},
kind: Some(SuggestionKind::Command(CommandType::External)),
kind: Some(SuggestionKind::Command(
CommandType::External,
None,
)),
},
);
}
@ -112,7 +115,7 @@ impl Completer for CommandCompletion {
},
true,
);
for (name, description, typ) in filtered_commands {
for (decl_id, name, description, typ) in filtered_commands {
let name = String::from_utf8_lossy(&name);
internal_suggs.insert(
name.to_string(),
@ -124,7 +127,7 @@ impl Completer for CommandCompletion {
append_whitespace: true,
..Suggestion::default()
},
kind: Some(SuggestionKind::Command(typ)),
kind: Some(SuggestionKind::Command(typ, Some(decl_id))),
},
);
}

View File

@ -1,21 +1,20 @@
use crate::completions::{
base::{SemanticSuggestion, SuggestionKind},
AttributableCompletion, AttributeCompletion, CellPathCompletion, CommandCompletion, Completer,
CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion, FileCompletion,
FlagCompletion, OperatorCompletion, VariableCompletion,
CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion,
ExportableCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
};
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
use nu_engine::eval_block;
use nu_parser::{flatten_expression, parse};
use nu_parser::{flatten_expression, parse, parse_module_file_or_dir};
use nu_protocol::{
ast::{Argument, Block, Expr, Expression, FindMapResult, Traverse},
ast::{Argument, Block, Expr, Expression, FindMapResult, ListItem, Traverse},
debugger::WithoutDebug,
engine::{Closure, EngineState, Stack, StateWorkingSet},
PipelineData, Span, Type, Value,
};
use reedline::{Completer as ReedlineCompleter, Suggestion};
use std::{str, sync::Arc};
use super::base::{SemanticSuggestion, SuggestionKind};
use std::sync::Arc;
/// Used as the function `f` in find_map Traverse
///
@ -57,8 +56,13 @@ fn find_pipeline_element_by_position<'a>(
Expr::FullCellPath(fcp) => fcp
.head
.find_map(working_set, &closure)
.or(Some(expr))
.map(FindMapResult::Found)
// e.g. use std/util [<tab>
.or_else(|| {
(fcp.head.span.contains(pos) && matches!(fcp.head.expr, Expr::List(_)))
.then_some(FindMapResult::Continue)
})
.or(Some(FindMapResult::Found(expr)))
.unwrap_or_default(),
Expr::Var(_) => FindMapResult::Found(expr),
Expr::AttributeBlock(ab) => ab
@ -127,6 +131,18 @@ struct Context<'a> {
offset: usize,
}
/// For argument completion
struct PositionalArguments<'a> {
/// command name
command_head: &'a str,
/// indices of positional arguments
positional_arg_indices: Vec<usize>,
/// argument list
arguments: &'a [Argument],
/// expression of current argument
expr: &'a Expression,
}
impl Context<'_> {
fn new<'a>(
working_set: &'a StateWorkingSet,
@ -328,7 +344,8 @@ impl NuCompleter {
// NOTE: the argument to complete is not necessarily the last one
// for lsp completion, we don't trim the text,
// so that `def`s after pos can be completed
for arg in call.arguments.iter() {
let mut positional_arg_indices = Vec::new();
for (arg_idx, arg) in call.arguments.iter().enumerate() {
let span = arg.span();
if span.contains(pos) {
// if customized completion specified, it has highest priority
@ -378,10 +395,16 @@ impl NuCompleter {
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(),
// complete according to expression type and command head
Argument::Positional(expr) => {
let command_head = working_set.get_span_contents(call.head);
let command_head = working_set.get_decl(call.decl_id).name();
positional_arg_indices.push(arg_idx);
self.argument_completion_helper(
command_head,
expr,
PositionalArguments {
command_head,
positional_arg_indices,
arguments: &call.arguments,
expr,
},
pos,
&ctx,
suggestions.is_empty(),
)
@ -389,6 +412,8 @@ impl NuCompleter {
_ => vec![],
});
break;
} else if !matches!(arg, Argument::Named(_)) {
positional_arg_indices.push(arg_idx);
}
}
}
@ -486,9 +511,10 @@ impl NuCompleter {
externals: bool,
strip: bool,
) -> Vec<SemanticSuggestion> {
let config = self.engine_state.get_config();
let mut command_completions = CommandCompletion {
internals,
externals,
externals: !internals || (externals && config.completions.external.enable),
};
let (new_span, prefix) = strip_placeholder_if_any(working_set, &span, strip);
let ctx = Context::new(working_set, new_span, prefix, offset);
@ -497,20 +523,97 @@ impl NuCompleter {
fn argument_completion_helper(
&self,
command_head: &[u8],
expr: &Expression,
argument_info: PositionalArguments,
pos: usize,
ctx: &Context,
need_fallback: bool,
) -> Vec<SemanticSuggestion> {
let PositionalArguments {
command_head,
positional_arg_indices,
arguments,
expr,
} = argument_info;
// special commands
match command_head {
// complete module file/directory
// TODO: if module file already specified,
// should parse it to get modules/commands/consts to complete
b"use" | b"export use" | b"overlay use" | b"source-env" => {
return self.process_completion(&mut DotNuCompletion, ctx);
"use" | "export use" | "overlay use" | "source-env"
if positional_arg_indices.len() == 1 =>
{
return self.process_completion(
&mut DotNuCompletion {
std_virtual_path: command_head != "source-env",
},
ctx,
);
}
b"which" => {
// NOTE: if module file already specified,
// should parse it to get modules/commands/consts to complete
"use" | "export use" => {
let Some(Argument::Positional(Expression {
expr: Expr::String(module_name),
span,
..
})) = positional_arg_indices
.first()
.and_then(|i| arguments.get(*i))
else {
return vec![];
};
let module_name = module_name.as_bytes();
let (module_id, temp_working_set) = match ctx.working_set.find_module(module_name) {
Some(module_id) => (module_id, None),
None => {
let mut temp_working_set =
StateWorkingSet::new(ctx.working_set.permanent_state);
let Some(module_id) = parse_module_file_or_dir(
&mut temp_working_set,
module_name,
*span,
None,
) else {
return vec![];
};
(module_id, Some(temp_working_set))
}
};
let mut exportable_completion = ExportableCompletion {
module_id,
temp_working_set,
};
let mut complete_on_list_items = |items: &[ListItem]| -> Vec<SemanticSuggestion> {
for item in items {
let span = item.expr().span;
if span.contains(pos) {
let offset = span.start.saturating_sub(ctx.span.start);
let end_offset =
ctx.prefix.len().min(pos.min(span.end) - ctx.span.start + 1);
let new_ctx = Context::new(
ctx.working_set,
Span::new(span.start, ctx.span.end.min(span.end)),
ctx.prefix.get(offset..end_offset).unwrap_or_default(),
ctx.offset,
);
return self.process_completion(&mut exportable_completion, &new_ctx);
}
}
vec![]
};
match &expr.expr {
Expr::String(_) => {
return self.process_completion(&mut exportable_completion, ctx);
}
Expr::FullCellPath(fcp) => match &fcp.head.expr {
Expr::List(items) => {
return complete_on_list_items(items);
}
_ => return vec![],
},
_ => return vec![],
}
}
"which" => {
let mut completer = CommandCompletion {
internals: true,
externals: true,
@ -543,7 +646,6 @@ impl NuCompleter {
case_sensitive: config.completions.case_sensitive,
match_algorithm: config.completions.algorithm.into(),
sort: config.completions.sort,
..Default::default()
};
completer.fetch(

View File

@ -22,18 +22,22 @@ pub struct PathBuiltFromString {
/// Recursively goes through paths that match a given `partial`.
/// built: State struct for a valid matching path built so far.
///
/// `want_directory`: Whether we want only directories as completion matches.
/// Some commands like `cd` can only be run on directories whereas others
/// like `ls` can be run on regular files as well.
///
/// `isdir`: whether the current partial path has a trailing slash.
/// Parsing a path string into a pathbuf loses that bit of information.
///
/// want_directory: Whether we want only directories as completion matches.
/// Some commands like `cd` can only be run on directories whereas others
/// like `ls` can be run on regular files as well.
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components
/// of the path matched a directory exactly.
fn complete_rec(
partial: &[&str],
built_paths: &[PathBuiltFromString],
options: &CompletionOptions,
want_directory: bool,
isdir: bool,
enable_exact_match: bool,
) -> Vec<PathBuiltFromString> {
if let Some((&base, rest)) = partial.split_first() {
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
@ -46,7 +50,14 @@ fn complete_rec(
built
})
.collect();
return complete_rec(rest, &built_paths, options, want_directory, isdir);
return complete_rec(
rest,
&built_paths,
options,
want_directory,
isdir,
enable_exact_match,
);
}
}
@ -86,27 +97,26 @@ fn complete_rec(
// Serves as confirmation to ignore longer completions for
// components in between.
if !rest.is_empty() || isdir {
// Don't show longer completions if we have an exact match (#13204, #14794)
let exact_match = enable_exact_match
&& (if options.case_sensitive {
entry_name.eq(base)
} else {
entry_name.eq_ignore_case(base)
});
completions.extend(complete_rec(
rest,
&[built],
options,
want_directory,
isdir,
exact_match,
));
} else {
completions.push(built);
}
// For https://github.com/nushell/nushell/issues/13204
if isdir && options.match_algorithm == MatchAlgorithm::Prefix {
let exact_match = if options.case_sensitive {
entry_name.eq(base)
} else {
entry_name.to_folded_case().eq(&base.to_folded_case())
};
if exact_match {
break;
}
} else {
completions.push(built);
}
}
None => {
@ -140,7 +150,7 @@ impl OriginalCwd {
}
}
fn surround_remove(partial: &str) -> String {
pub fn surround_remove(partial: &str) -> String {
for c in ['`', '"', '\''] {
if partial.starts_with(c) {
let ret = partial.strip_prefix(c).unwrap_or(partial);
@ -199,10 +209,9 @@ pub fn complete_item(
let ls_colors = (engine_state.config.completions.use_ls_colors
&& engine_state.config.use_ansi_coloring.get(engine_state))
.then(|| {
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
None => None,
};
let ls_colors_env_str = stack
.get_env_var(engine_state, "LS_COLORS")
.and_then(|v| env_to_string("LS_COLORS", v, engine_state, stack).ok());
get_ls_colors(ls_colors_env_str)
});
@ -256,6 +265,7 @@ pub fn complete_item(
options,
want_directory,
isdir,
options.match_algorithm == MatchAlgorithm::Prefix,
)
.into_iter()
.map(|mut p| {
@ -264,15 +274,12 @@ pub fn complete_item(
}
let is_dir = p.isdir;
let path = original_cwd.apply(p, path_separator);
let real_path = expand_to_real_path(&path);
let metadata = std::fs::symlink_metadata(&real_path).ok();
let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(
&path,
std::fs::symlink_metadata(expand_to_real_path(&path))
.ok()
.as_ref(),
)
.map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default()
lsc.style_for_path_with_metadata(&real_path, metadata.as_ref())
.map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default()
});
FileSuggestion {
span,

View File

@ -18,6 +18,12 @@ pub enum MatchAlgorithm {
/// "git switch" is matched by "git sw"
Prefix,
/// Only show suggestions which have a substring matching with the given input
///
/// Example:
/// "git checkout" is matched by "checkout"
Substring,
/// Only show suggestions which contain the input chars at any place
///
/// Example:
@ -36,6 +42,10 @@ enum State<T> {
/// Holds (haystack, item)
items: Vec<(String, T)>,
},
Substring {
/// Holds (haystack, item)
items: Vec<(String, T)>,
},
Fuzzy {
matcher: Matcher,
atom: Atom,
@ -64,6 +74,18 @@ impl<T> NuMatcher<'_, T> {
state: State::Prefix { items: Vec::new() },
}
}
MatchAlgorithm::Substring => {
let lowercase_needle = if options.case_sensitive {
needle.to_owned()
} else {
needle.to_folded_case()
};
NuMatcher {
options,
needle: lowercase_needle,
state: State::Substring { items: Vec::new() },
}
}
MatchAlgorithm::Fuzzy => {
let atom = Atom::new(
needle,
@ -102,11 +124,21 @@ impl<T> NuMatcher<'_, T> {
} else {
Cow::Owned(haystack.to_folded_case())
};
let matches = if self.options.positional {
haystack_folded.starts_with(self.needle.as_str())
let matches = haystack_folded.starts_with(self.needle.as_str());
if matches {
if let Some(item) = item {
items.push((haystack.to_string(), item));
}
}
matches
}
State::Substring { items } => {
let haystack_folded = if self.options.case_sensitive {
Cow::Borrowed(haystack)
} else {
haystack_folded.contains(self.needle.as_str())
Cow::Owned(haystack.to_folded_case())
};
let matches = haystack_folded.contains(self.needle.as_str());
if matches {
if let Some(item) = item {
items.push((haystack.to_string(), item));
@ -148,7 +180,7 @@ impl<T> NuMatcher<'_, T> {
/// Get all the items that matched (sorted)
pub fn results(self) -> Vec<T> {
match self.state {
State::Prefix { mut items, .. } => {
State::Prefix { mut items, .. } | State::Substring { mut items, .. } => {
items.sort_by(|(haystack1, _), (haystack2, _)| {
let cmp_sensitive = haystack1.cmp(haystack2);
if self.options.case_sensitive {
@ -195,6 +227,7 @@ impl From<CompletionAlgorithm> for MatchAlgorithm {
fn from(value: CompletionAlgorithm) -> Self {
match value {
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
CompletionAlgorithm::Substring => MatchAlgorithm::Substring,
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
}
}
@ -206,6 +239,7 @@ impl TryFrom<String> for MatchAlgorithm {
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"prefix" => Ok(Self::Prefix),
"substring" => Ok(Self::Substring),
"fuzzy" => Ok(Self::Fuzzy),
_ => Err(InvalidMatchAlgorithm::Unknown),
}
@ -230,7 +264,6 @@ impl std::error::Error for InvalidMatchAlgorithm {}
#[derive(Clone)]
pub struct CompletionOptions {
pub case_sensitive: bool,
pub positional: bool,
pub match_algorithm: MatchAlgorithm,
pub sort: CompletionSort,
}
@ -239,7 +272,6 @@ impl Default for CompletionOptions {
fn default() -> Self {
Self {
case_sensitive: true,
positional: true,
match_algorithm: MatchAlgorithm::Prefix,
sort: Default::default(),
}
@ -256,6 +288,9 @@ mod test {
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
#[case(MatchAlgorithm::Substring, "example text", "", true)]
#[case(MatchAlgorithm::Substring, "example text", "text", true)]
#[case(MatchAlgorithm::Substring, "example text", "mplxt", false)]
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]

View File

@ -1,11 +1,12 @@
use crate::completions::{
completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion,
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
SemanticSuggestion,
};
use nu_engine::eval_call;
use nu_protocol::{
ast::{Argument, Call, Expr, Expression},
debugger::WithoutDebug,
engine::{Stack, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
DeclId, PipelineData, Span, Type, Value,
};
use std::collections::HashMap;
@ -42,28 +43,37 @@ impl<T: Completer> Completer for CustomCompletion<T> {
) -> Vec<SemanticSuggestion> {
// Call custom declaration
let mut stack_mut = stack.clone();
let result = eval_call::<WithoutDebug>(
working_set.permanent_state,
&mut stack_mut,
&Call {
decl_id: self.decl_id,
head: span,
arguments: vec![
Argument::Positional(Expression::new_unknown(
Expr::String(self.line.clone()),
Span::unknown(),
Type::String,
)),
Argument::Positional(Expression::new_unknown(
Expr::Int(self.line_pos as i64),
Span::unknown(),
Type::Int,
)),
],
parser_info: HashMap::new(),
},
PipelineData::empty(),
);
let mut eval = |engine_state: &EngineState| {
eval_call::<WithoutDebug>(
engine_state,
&mut stack_mut,
&Call {
decl_id: self.decl_id,
head: span,
arguments: vec![
Argument::Positional(Expression::new_unknown(
Expr::String(self.line.clone()),
Span::unknown(),
Type::String,
)),
Argument::Positional(Expression::new_unknown(
Expr::Int(self.line_pos as i64),
Span::unknown(),
Type::Int,
)),
],
parser_info: HashMap::new(),
},
PipelineData::empty(),
)
};
let result = if self.decl_id.get() < working_set.permanent_state.num_decls() {
eval(working_set.permanent_state)
} else {
let mut engine_state = working_set.permanent_state.clone();
let _ = engine_state.merge_delta(working_set.delta.clone());
eval(&engine_state)
};
let mut completion_options = orig_options.clone();
let mut should_sort = true;
@ -93,10 +103,10 @@ impl<T: Completer> Completer for CustomCompletion<T> {
{
completion_options.case_sensitive = case_sensitive;
}
if let Some(positional) =
options.get("positional").and_then(|val| val.as_bool().ok())
{
completion_options.positional = positional;
let positional =
options.get("positional").and_then(|val| val.as_bool().ok());
if positional.is_some() {
log::warn!("Use of the positional option is deprecated. Use the substring match algorithm instead.");
}
if let Some(algorithm) = options
.get("completion_algorithm")
@ -104,6 +114,11 @@ impl<T: Completer> Completer for CustomCompletion<T> {
.and_then(|option| option.try_into().ok())
{
completion_options.match_algorithm = algorithm;
if let Some(false) = positional {
if completion_options.match_algorithm == MatchAlgorithm::Prefix {
completion_options.match_algorithm = MatchAlgorithm::Substring
}
}
}
}

View File

@ -1,18 +1,23 @@
use crate::completions::{file_path_completion, Completer, CompletionOptions};
use crate::completions::{
completion_common::{surround_remove, FileSuggestion},
completion_options::NuMatcher,
file_path_completion, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_path::expand_tilde;
use nu_protocol::{
engine::{Stack, StateWorkingSet},
engine::{Stack, StateWorkingSet, VirtualPath},
Span,
};
use reedline::Suggestion;
use std::{
collections::HashSet,
path::{is_separator, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR},
path::{is_separator, PathBuf, MAIN_SEPARATOR_STR},
};
use super::{SemanticSuggestion, SuggestionKind};
pub struct DotNuCompletion;
pub struct DotNuCompletion {
/// e.g. use std/a<tab>
pub std_virtual_path: bool,
}
impl Completer for DotNuCompletion {
fn fetch(
@ -102,7 +107,7 @@ impl Completer for DotNuCompletion {
// Fetch the files filtering the ones that ends with .nu
// and transform them into suggestions
let completions = file_path_completion(
let mut completions = file_path_completion(
span,
partial,
&search_dirs
@ -113,17 +118,60 @@ impl Completer for DotNuCompletion {
working_set.permanent_state,
stack,
);
if self.std_virtual_path {
let mut matcher = NuMatcher::new(partial, options);
let base_dir = surround_remove(&base_dir);
if base_dir == "." {
let surround_prefix = partial
.chars()
.take_while(|c| "`'\"".contains(*c))
.collect::<String>();
for path in ["std", "std-rfc"] {
let path = format!("{}{}", surround_prefix, path);
matcher.add(
path.clone(),
FileSuggestion {
span,
path,
style: None,
is_dir: true,
},
);
}
} else if let Some(VirtualPath::Dir(sub_paths)) =
working_set.find_virtual_path(&base_dir)
{
for sub_vp_id in sub_paths {
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
let path = path
.strip_prefix(&format!("{}/", base_dir))
.unwrap_or(path)
.to_string();
matcher.add(
path.clone(),
FileSuggestion {
path,
span,
style: None,
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
},
);
}
}
completions.extend(matcher.results());
}
completions
.into_iter()
// Different base dir, so we list the .nu files or folders
.filter(|it| {
// for paths with spaces in them
let path = it.path.trim_end_matches('`');
path.ends_with(".nu") || path.ends_with(SEP)
path.ends_with(".nu") || it.is_dir
})
.map(|x| {
let append_whitespace =
x.path.ends_with(".nu") && (!start_with_backquote || end_with_backquote);
let append_whitespace = !x.is_dir && (!start_with_backquote || end_with_backquote);
// Re-calculate the span to replace
let mut span_offset = 0;
let mut value = x.path.to_string();

View File

@ -0,0 +1,112 @@
use crate::completions::{
completion_common::surround_remove, completion_options::NuMatcher, Completer,
CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_protocol::{
engine::{Stack, StateWorkingSet},
ModuleId, Span,
};
use reedline::Suggestion;
pub struct ExportableCompletion<'a> {
pub module_id: ModuleId,
pub temp_working_set: Option<StateWorkingSet<'a>>,
}
/// If name contains space, wrap it in quotes
fn wrapped_name(name: String) -> String {
if !name.contains(' ') {
return name;
}
if name.contains('\'') {
format!("\"{}\"", name.replace('"', r#"\""#))
} else {
format!("'{name}'")
}
}
impl Completer for ExportableCompletion<'_> {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: impl AsRef<str>,
span: Span,
offset: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::<()>::new(surround_remove(prefix.as_ref()), options);
let mut results = Vec::new();
let span = reedline::Span {
start: span.start - offset,
end: span.end - offset,
};
// TODO: use matcher.add_lazy to lazy evaluate an item if it matches the prefix
let mut add_suggestion = |value: String,
description: Option<String>,
extra: Option<Vec<String>>,
kind: SuggestionKind| {
results.push(SemanticSuggestion {
suggestion: Suggestion {
value,
span,
description,
extra,
..Suggestion::default()
},
kind: Some(kind),
});
};
let working_set = self.temp_working_set.as_ref().unwrap_or(working_set);
let module = working_set.get_module(self.module_id);
for (name, decl_id) in &module.decls {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let cmd = working_set.get_decl(*decl_id);
add_suggestion(
wrapped_name(name),
Some(cmd.description().to_string()),
None,
// `None` here avoids arguments being expanded by snippet edit style for lsp
SuggestionKind::Command(cmd.command_type(), None),
);
}
}
for (name, module_id) in &module.submodules {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let comments = working_set.get_module_comments(*module_id).map(|spans| {
spans
.iter()
.map(|sp| {
String::from_utf8_lossy(working_set.get_span_contents(*sp)).into()
})
.collect::<Vec<String>>()
});
add_suggestion(
wrapped_name(name),
Some("Submodule".into()),
comments,
SuggestionKind::Module,
);
}
}
for (name, var_id) in &module.constants {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let var = working_set.get_variable(*var_id);
add_suggestion(
wrapped_name(name),
var.const_val
.as_ref()
.and_then(|v| v.clone().coerce_into_string().ok()),
None,
SuggestionKind::Variable,
);
}
}
results
}
}

View File

@ -1,12 +1,12 @@
use crate::completions::{completion_options::NuMatcher, Completer, CompletionOptions};
use crate::completions::{
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_protocol::{
engine::{Stack, StateWorkingSet},
DeclId, Span,
};
use reedline::Suggestion;
use super::{SemanticSuggestion, SuggestionKind};
#[derive(Clone)]
pub struct FlagCompletion {
pub decl_id: DeclId,

View File

@ -8,6 +8,7 @@ mod completion_options;
mod custom_completions;
mod directory_completions;
mod dotnu_completions;
mod exportable_completions;
mod file_completions;
mod flag_completions;
mod operator_completions;
@ -22,6 +23,7 @@ pub use completion_options::{CompletionOptions, MatchAlgorithm};
pub use custom_completions::CustomCompletion;
pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion;
pub use exportable_completions::ExportableCompletion;
pub use file_completions::{file_path_completion, FileCompletion};
pub use flag_completions::FlagCompletion;
pub use operator_completions::OperatorCompletion;

View File

@ -864,7 +864,7 @@ fn do_auto_cd(
path.to_string_lossy().to_string()
};
if let PermissionResult::PermissionDenied(_) = have_permission(path.clone()) {
if let PermissionResult::PermissionDenied = have_permission(path.clone()) {
report_shell_error(
engine_state,
&ShellError::Io(IoError::new_with_additional_context(

View File

@ -10,7 +10,8 @@ use nu_cli::NuCompleter;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_path::expand_tilde;
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, Config, PipelineData};
use nu_std::load_standard_library;
use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest};
use support::{
@ -227,14 +228,12 @@ fn customcompletions_override_options() {
let mut completer = custom_completer_with_options(
r#"$env.config.completions.algorithm = "fuzzy"
$env.config.completions.case_sensitive = false"#,
r#"completion_algorithm: "prefix",
positional: false,
r#"completion_algorithm: "substring",
case_sensitive: true,
sort: true"#,
&["Foo Abcdef", "Abcdef", "Acd Bar"],
);
// positional: false should make it do substring matching
// sort: true should force sorting
let expected: Vec<_> = vec!["Abcdef", "Foo Abcdef"];
let suggestions = completer.complete("my-command Abcd", 15);
@ -350,9 +349,24 @@ fn custom_arguments_vs_subcommands() {
match_suggestions(&expected, &suggestions);
}
#[test]
fn custom_completions_defined_inline() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let completion_str = "def animals [] { [cat dog] }
export def say [
animal: string@animals
] { }; say ";
let suggestions = completer.complete(completion_str, completion_str.len());
// including only subcommand completions
let expected: Vec<_> = vec!["cat", "dog"];
match_suggestions(&expected, &suggestions);
}
/// External command only if starts with `^`
#[test]
fn external_commands_only() {
fn external_commands() {
let engine = new_external_engine();
let mut completer = NuCompleter::new(
Arc::new(engine),
@ -375,6 +389,31 @@ fn external_commands_only() {
match_suggestions(&expected, &suggestions);
}
/// Disable external commands except for those start with `^`
#[test]
fn external_commands_disabled() {
let mut engine = new_external_engine();
let mut config = Config::default();
config.completions.external.enable = false;
engine.set_config(config);
let stack = nu_protocol::engine::Stack::new();
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let completion_str = "ls; ^sleep";
let suggestions = completer.complete(completion_str, completion_str.len());
#[cfg(windows)]
let expected: Vec<_> = vec!["sleep.exe"];
#[cfg(not(windows))]
let expected: Vec<_> = vec!["sleep"];
match_suggestions(&expected, &suggestions);
let completion_str = "sleep";
let suggestions = completer.complete(completion_str, completion_str.len());
let expected: Vec<_> = vec!["sleep"];
match_suggestions(&expected, &suggestions);
}
/// Which completes both internals and externals
#[test]
fn which_command_completions() {
@ -473,7 +512,7 @@ fn dotnu_completions() {
match_suggestions(&vec!["sub.nu`"], &suggestions);
let expected = vec![
let mut expected = vec![
"asdf.nu",
"bar.nu",
"bat.nu",
@ -506,6 +545,8 @@ fn dotnu_completions() {
match_suggestions(&expected, &suggestions);
// Test use completion
expected.push("std");
expected.push("std-rfc");
let completion_str = "use ";
let suggestions = completer.complete(completion_str, completion_str.len());
@ -537,6 +578,66 @@ fn dotnu_completions() {
match_dir_content_for_dotnu(dir_content, &suggestions);
}
#[test]
fn dotnu_stdlib_completions() {
let (_, _, mut engine, stack) = new_dotnu_engine();
assert!(load_standard_library(&mut engine).is_ok());
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// `export use` should be recognized as command `export use`
let completion_str = "export use std/ass";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["assert"], &suggestions);
let completion_str = "use `std-rfc/cli";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["clip"], &suggestions);
let completion_str = "use \"std";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["\"std", "\"std-rfc"], &suggestions);
let completion_str = "overlay use \'std-rfc/cli";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["clip"], &suggestions);
}
#[test]
fn exportable_completions() {
let (_, _, mut engine, mut stack) = new_dotnu_engine();
let code = r#"export module "🤔🐘" {
export const foo = "🤔🐘";
}"#;
assert!(support::merge_input(code.as_bytes(), &mut engine, &mut stack).is_ok());
assert!(load_standard_library(&mut engine).is_ok());
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let completion_str = "use std null";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["null-device", "null_device"], &suggestions);
let completion_str = "export use std/assert eq";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["equal"], &suggestions);
let completion_str = "use std/assert \"not eq";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["'not equal'"], &suggestions);
let completion_str = "use std-rfc/clip ['prefi";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["prefix"], &suggestions);
let completion_str = "use std/math [E, `TAU";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["TAU"], &suggestions);
let completion_str = "use 🤔🐘 'foo";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&vec!["foo"], &suggestions);
}
#[test]
fn dotnu_completions_const_nu_lib_dirs() {
let (_, _, engine, stack) = new_dotnu_engine();
@ -911,10 +1012,11 @@ fn partial_completions() {
// Create the expected values
let expected_paths = [
file(dir.join("partial").join("hello.txt")),
folder(dir.join("partial").join("hol")),
file(dir.join("partial-a").join("have_ext.exe")),
file(dir.join("partial-a").join("have_ext.txt")),
file(dir.join("partial-a").join("hello")),
file(dir.join("partial-a").join("hola")),
folder(dir.join("partial-a").join("hola")),
file(dir.join("partial-b").join("hello_b")),
file(dir.join("partial-b").join("hi_b")),
file(dir.join("partial-c").join("hello_c")),
@ -931,11 +1033,12 @@ fn partial_completions() {
// Create the expected values
let expected_paths = [
file(dir.join("partial").join("hello.txt")),
folder(dir.join("partial").join("hol")),
file(dir.join("partial-a").join("anotherfile")),
file(dir.join("partial-a").join("have_ext.exe")),
file(dir.join("partial-a").join("have_ext.txt")),
file(dir.join("partial-a").join("hello")),
file(dir.join("partial-a").join("hola")),
folder(dir.join("partial-a").join("hola")),
file(dir.join("partial-b").join("hello_b")),
file(dir.join("partial-b").join("hi_b")),
file(dir.join("partial-c").join("hello_c")),
@ -1902,6 +2005,35 @@ fn table_cell_path_completions() {
match_suggestions(&expected, &suggestions);
}
#[test]
fn quoted_cell_path_completions() {
let (_, _, mut engine, mut stack) = new_engine();
let command = r#"let foo = {'foo bar':1 'foo\\"bar"': 1 '.': 1 '|': 1 1: 1 "": 1}"#;
assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok());
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
let expected: Vec<_> = vec![
"\"\"",
"\".\"",
"\"1\"",
"\"foo bar\"",
"\"foo\\\\\\\\\\\"bar\\\"\"",
"\"|\"",
];
let completion_str = "$foo.";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&expected, &suggestions);
let expected: Vec<_> = vec!["\"foo bar\"", "\"foo\\\\\\\\\\\"bar\\\"\""];
let completion_str = "$foo.`foo";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&expected, &suggestions);
let completion_str = "$foo.foo";
let suggestions = completer.complete(completion_str, completion_str.len());
match_suggestions(&expected, &suggestions);
}
#[test]
fn alias_of_command_and_flags() {
let (_, _, mut engine, mut stack) = new_engine();
@ -2175,15 +2307,43 @@ fn exact_match() {
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Troll case to test if exact match logic works case insensitively
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
let suggestions = completer.complete(&target_dir, target_dir.len());
// Since it's an exact match, only 'partial' should be suggested, not
// 'partial-a' and stuff. Implemented in #13302
match_suggestions(
&vec![file(dir.join("partial").join("hello.txt")).as_str()],
&vec![
file(dir.join("partial").join("hello.txt")).as_str(),
folder(dir.join("partial").join("hol")).as_str(),
],
&suggestions,
);
let target_dir = format!("open {}", file(dir.join("partial").join("h")));
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(
&vec![
file(dir.join("partial").join("hello.txt")).as_str(),
folder(dir.join("partial").join("hol")).as_str(),
],
&suggestions,
);
// Even though "hol" is an exact match, the first component ("part") wasn't an
// exact match, so we include partial-a/hola
let target_dir = format!("open {}", file(dir.join("part").join("hol")));
let suggestions = completer.complete(&target_dir, target_dir.len());
match_suggestions(
&vec![
folder(dir.join("partial").join("hol")).as_str(),
folder(dir.join("partial-a").join("hola")).as_str(),
],
&suggestions,
);
// Exact match behavior shouldn't be enabled if the path has no slashes
let target_dir = format!("open {}", file(dir.join("partial")));
let suggestions = completer.complete(&target_dir, target_dir.len());
assert!(suggestions.len() > 1);
}
#[ignore = "was reverted, still needs fixing"]

View File

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

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.103.0"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,13 +16,13 @@ bench = false
workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
nu-json = { version = "0.103.0", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.103.0" }
nu-pretty-hex = { version = "0.103.0", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-json = { version = "0.104.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-pretty-hex = { version = "0.104.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
# Potential dependencies for extras
heck = { workspace = true }
@ -37,6 +37,6 @@ itertools = { workspace = true }
mime = { workspace = true }
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
nu-command = { path = "../nu-command", version = "0.103.0" }
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-command = { path = "../nu-command", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }

View File

@ -135,7 +135,7 @@ where
(min, max) => (rhs, lhs, max, min),
};
let pad = iter::repeat(0).take(max_len - min_len);
let pad = iter::repeat_n(0, max_len - min_len);
let mut a;
let mut b;
@ -159,9 +159,10 @@ where
}
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
Value::error(
ShellError::PipelineMismatch {
ShellError::OnlySupportsThisInputType {
exp_input_type: "input, and argument, to be both int or both binary"
.to_string(),
wrong_type: "int and binary".to_string(),
dst_span: rhs.span(),
src_span: span,
},

View File

@ -249,7 +249,7 @@ fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -
Last | Only => lhs << bit_shift,
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
})
.chain(iter::repeat(0).take(byte_shift))
.chain(iter::repeat_n(0, byte_shift))
.collect::<Vec<u8>>()
}

View File

@ -253,12 +253,11 @@ fn format_record(
optional: false,
})
.collect();
match data_as_value.clone().follow_cell_path(&path_members, false) {
Ok(value_at_column) => {
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
}
Err(se) => return Err(se),
}
let expanded_string = data_as_value
.follow_cell_path(&path_members, false)?
.to_expanded_string(", ", config);
output.push_str(expanded_string.as_str())
}
}
}

View File

@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021"
license = "MIT"
name = "nu-cmd-lang"
version = "0.103.0"
version = "0.104.1"
[lib]
bench = false
@ -15,16 +15,16 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.103.0" }
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
itertools = { workspace = true }
shadow-rs = { version = "0.38", default-features = false }
shadow-rs = { version = "1.1", default-features = false }
[build-dependencies]
shadow-rs = { version = "0.38", default-features = false }
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
[dev-dependencies]
quickcheck = { workspace = true }

View File

@ -18,4 +18,4 @@ A base crate is one with minimal dependencies in our system so that other develo
### Background on nu-cmd-lang
This crate was designed to be a small, concise set of tools or commands that serve as the *foundation layer* of both nu and nushell. These are the core commands needed to have a nice working version of the *nu language* without all of the support that the other commands provide inside nushell. Prior to the launch of this crate all of our commands were housed in the crate *nu-command*. Moving forward we would like to *slowly* break out the commands in nu-command into different crates; the naming and how this will work and where all the commands will be located is a "work in progress" especially now that the *standard library* is starting to become more popular as a location for commands. As time goes on some of our commands written in rust will be migrated to nu and when this happens they will be moved into the *standard library*.
This crate was designed to be a small, concise set of tools or commands that serve as the *foundation layer* of both nu and nushell. These are the core commands needed to have a nice working version of the *nu language* without all of the support that the other commands provide inside nushell. Prior to the launch of this crate all of our commands were housed in the crate *nu-command*. Moving forward we would like to *slowly* break out the commands in nu-command into different crates; the naming and how this will work and where all the commands will be located is a "work in progress" especially now that the *standard library* is starting to become more popular as a location for commands. As time goes on some of our commands written in rust will be migrated to nu and when this happens they will be moved into the *standard library*.

View File

@ -1,6 +1,9 @@
use nu_engine::command_prelude::*;
use nu_protocol::{engine::StateWorkingSet, ByteStreamSource, PipelineMetadata};
use nu_protocol::{
engine::{Closure, StateWorkingSet},
BlockId, ByteStreamSource, Category, PipelineMetadata, Signature,
};
use std::any::type_name;
#[derive(Clone)]
pub struct Describe;
@ -73,39 +76,116 @@ impl Command for Describe {
"{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d",
result: Some(Value::test_record(record!(
"type" => Value::test_string("record"),
"detailed_type" => Value::test_string("record<shell: string, uwu: bool, features: record<bugs: bool, multiplatform: bool, speed: int>, fib: list<int>, on_save: closure, first_commit: datetime, my_duration: duration>"),
"columns" => Value::test_record(record!(
"shell" => Value::test_string("string"),
"uwu" => Value::test_string("bool"),
"shell" => Value::test_record(record!(
"type" => Value::test_string("string"),
"detailed_type" => Value::test_string("string"),
"rust_type" => Value::test_string("&alloc::string::String"),
"value" => Value::test_string("true"),
)),
"uwu" => Value::test_record(record!(
"type" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(true),
)),
"features" => Value::test_record(record!(
"type" => Value::test_string("record"),
"detailed_type" => Value::test_string("record<bugs: bool, multiplatform: bool, speed: int>"),
"columns" => Value::test_record(record!(
"bugs" => Value::test_string("bool"),
"multiplatform" => Value::test_string("bool"),
"speed" => Value::test_string("int"),
"bugs" => Value::test_record(record!(
"type" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(false),
)),
"multiplatform" => Value::test_record(record!(
"type" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(true),
)),
"speed" => Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(10),
)),
)),
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
)),
"fib" => Value::test_record(record!(
"type" => Value::test_string("list"),
"detailed_type" => Value::test_string("list<int>"),
"length" => Value::test_int(6),
"values" => Value::test_list(vec![
Value::test_string("int"),
Value::test_string("int"),
Value::test_string("int"),
Value::test_string("int"),
Value::test_string("int"),
Value::test_string("int"),
]),
"rust_type" => Value::test_string("&mut alloc::vec::Vec<nu_protocol::value::Value>"),
"value" => Value::test_list(vec![
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(1),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(1),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(2),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(3),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(5),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(8),
))]
),
)),
"on_save" => Value::test_record(record!(
"type" => Value::test_string("closure"),
"detailed_type" => Value::test_string("closure"),
"rust_type" => Value::test_string("&alloc::boxed::Box<nu_protocol::engine::closure::Closure>"),
"value" => Value::test_closure(Closure {
block_id: BlockId::new(1),
captures: vec![],
}),
"signature" => Value::test_record(record!(
"name" => Value::test_string(""),
"category" => Value::test_string("default"),
)),
)),
"first_commit" => Value::test_string("date"),
"my_duration" => Value::test_string("duration"),
"first_commit" => Value::test_record(record!(
"type" => Value::test_string("datetime"),
"detailed_type" => Value::test_string("datetime"),
"rust_type" => Value::test_string("chrono::datetime::DateTime<chrono::offset::fixed::FixedOffset>"),
"value" => Value::test_date("2019-05-10 00:00:00Z".parse().unwrap_or_default()),
)),
"my_duration" => Value::test_record(record!(
"type" => Value::test_string("duration"),
"detailed_type" => Value::test_string("duration"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_duration(260_000_000_000),
))
)),
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
))),
},
Example {
@ -175,7 +255,9 @@ fn run(
Value::record(
record! {
"type" => Value::string(type_, head),
"type" => Value::string("bytestream", head),
"detailed_type" => Value::string(type_, head),
"rust_type" => Value::string(type_of(&stream), head),
"origin" => Value::string(origin, head),
"metadata" => metadata_to_value(metadata, head),
},
@ -192,6 +274,7 @@ fn run(
description
}
PipelineData::ListStream(stream, ..) => {
let type_ = type_of(&stream);
if options.detailed {
let subtype = if options.no_collect {
Value::string("any", head)
@ -201,6 +284,8 @@ fn run(
Value::record(
record! {
"type" => Value::string("stream", head),
"detailed_type" => Value::string("list stream", head),
"rust_type" => Value::string(type_, head),
"origin" => Value::string("nushell", head),
"subtype" => subtype,
"metadata" => metadata_to_value(metadata, head),
@ -229,45 +314,95 @@ fn run(
}
enum Description {
String(String),
Record(Record),
}
impl Description {
fn into_value(self, span: Span) -> Value {
match self {
Description::String(ty) => Value::string(ty, span),
Description::Record(record) => Value::record(record, span),
}
}
}
fn describe_value(value: Value, head: Span, engine_state: Option<&EngineState>) -> Value {
let record = match describe_value_inner(value, head, engine_state) {
Description::String(ty) => record! { "type" => Value::string(ty, head) },
Description::Record(record) => record,
};
let Description::Record(record) = describe_value_inner(value, head, engine_state);
Value::record(record, head)
}
fn type_of<T>(_: &T) -> String {
type_name::<T>().to_string()
}
fn describe_value_inner(
value: Value,
mut value: Value,
head: Span,
engine_state: Option<&EngineState>,
) -> Description {
let value_type = value.get_type().to_string();
match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Float { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::String { .. }
| Value::Glob { .. }
| Value::Nothing { .. } => Description::String(value.get_type().to_string()),
Value::Record { val, .. } => {
let mut columns = val.into_owned();
Value::Bool { val, .. } => Description::Record(record! {
"type" => Value::string("bool", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Int { val, .. } => Description::Record(record! {
"type" => Value::string("int", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Float { val, .. } => Description::Record(record! {
"type" => Value::string("float", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Filesize { val, .. } => Description::Record(record! {
"type" => Value::string("filesize", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Duration { val, .. } => Description::Record(record! {
"type" => Value::string("duration", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Date { val, .. } => Description::Record(record! {
"type" => Value::string("datetime", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Range { ref val, .. } => Description::Record(record! {
"type" => Value::string("range", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::String { ref val, .. } => Description::Record(record! {
"type" => Value::string("string", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Glob { ref val, .. } => Description::Record(record! {
"type" => Value::string("glob", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Nothing { .. } => Description::Record(record! {
"type" => Value::string("nothing", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string("", head),
"value" => value,
}),
Value::Record { ref val, .. } => {
let mut columns = val.clone().into_owned();
for (_, val) in &mut columns {
*val =
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
@ -275,25 +410,34 @@ fn describe_value_inner(
Description::Record(record! {
"type" => Value::string("record", head),
"columns" => Value::record(columns, head),
"detailed_type" => Value::string(value_type, head),
"columns" => Value::record(columns.clone(), head),
"rust_type" => Value::string(type_of(&val), head),
})
}
Value::List { mut vals, .. } => {
for val in &mut vals {
Value::List { ref mut vals, .. } => {
for val in &mut *vals {
*val =
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
}
Description::Record(record! {
"type" => Value::string("list", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(vals.len() as i64, head),
"values" => Value::list(vals, head),
"rust_type" => Value::string(type_of(&vals), head),
"value" => value,
})
}
Value::Closure { val, .. } => {
Value::Closure { ref val, .. } => {
let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id));
let mut record = record! { "type" => Value::string("closure", head) };
let mut record = record! {
"type" => Value::string("closure", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
};
if let Some(block) = block {
record.push(
"signature",
@ -308,21 +452,37 @@ fn describe_value_inner(
}
Description::Record(record)
}
Value::Error { error, .. } => Description::Record(record! {
Value::Error { ref error, .. } => Description::Record(record! {
"type" => Value::string("error", head),
"detailed_type" => Value::string(value_type, head),
"subtype" => Value::string(error.to_string(), head),
"rust_type" => Value::string(type_of(&error), head),
"value" => value,
}),
Value::Binary { val, .. } => Description::Record(record! {
Value::Binary { ref val, .. } => Description::Record(record! {
"type" => Value::string("binary", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(val.len() as i64, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::CellPath { val, .. } => Description::Record(record! {
Value::CellPath { ref val, .. } => Description::Record(record! {
"type" => Value::string("cell-path", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(val.members.len() as i64, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value
}),
Value::Custom { val, .. } => Description::Record(record! {
Value::Custom { ref val, .. } => Description::Record(record! {
"type" => Value::string("custom", head),
"detailed_type" => Value::string(value_type, head),
"subtype" => Value::string(val.type_name(), head),
"rust_type" => Value::string(type_of(&val), head),
"value" =>
match val.to_base_value(head) {
Ok(base_value) => base_value,
Err(err) => Value::error(err, head),
}
}),
}
}

View File

@ -31,16 +31,6 @@ impl Command for Do {
"ignore errors as the closure runs",
Some('i'),
)
.switch(
"ignore-shell-errors",
"ignore shell errors as the closure runs",
Some('s'),
)
.switch(
"ignore-program-errors",
"ignore external program errors as the closure runs",
Some('p'),
)
.switch(
"capture-errors",
"catch errors as the closure runs, and return them",
@ -71,36 +61,6 @@ impl Command for Do {
let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?;
let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?;
if call.has_flag(engine_state, caller_stack, "ignore-shell-errors")? {
nu_protocol::report_shell_warning(
engine_state,
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`--ignore-shell-errors` is deprecated and will be removed in 0.102.0."
.into(),
span: Some(call.head),
help: Some("Please use the `--ignore-errors(-i)`".into()),
inner: vec![],
},
);
}
if call.has_flag(engine_state, caller_stack, "ignore-program-errors")? {
nu_protocol::report_shell_warning(
engine_state,
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`--ignore-program-errors` is deprecated and will be removed in 0.102.0."
.into(),
span: Some(call.head),
help: Some("Please use the `--ignore-errors(-i)`".into()),
inner: vec![],
},
);
}
let ignore_shell_errors = ignore_all_errors
|| call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?;
let ignore_program_errors = ignore_all_errors
|| call.has_flag(engine_state, caller_stack, "ignore-program-errors")?;
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
let has_env = call.has_flag(engine_state, caller_stack, "env")?;
@ -206,7 +166,7 @@ impl Command for Do {
}
}
Ok(PipelineData::ByteStream(mut stream, metadata))
if ignore_program_errors
if ignore_all_errors
&& !matches!(
caller_stack.stdout(),
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
@ -218,10 +178,10 @@ impl Command for Do {
}
Ok(PipelineData::ByteStream(stream, metadata))
}
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_all_errors => {
Ok(PipelineData::empty())
}
Ok(PipelineData::ListStream(stream, metadata)) if ignore_shell_errors => {
Ok(PipelineData::ListStream(stream, metadata)) if ignore_all_errors => {
let stream = stream.map(move |value| {
if let Value::Error { .. } = value {
Value::nothing(head)

View File

@ -116,6 +116,8 @@ impl Command for OverlayUse {
// b) refreshing an active overlay (the origin module changed)
let module = engine_state.get_module(module_id);
// in such case, should also make sure that PWD is not restored in old overlays.
let cwd = caller_stack.get_env_var(engine_state, "PWD").cloned();
// Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block {
@ -160,11 +162,19 @@ impl Command for OverlayUse {
// The export-env block should see the env vars *before* activating this overlay
caller_stack.add_overlay(overlay_name);
// make sure that PWD is not restored in old overlays.
if let Some(cwd) = cwd {
caller_stack.add_env_var("PWD".to_string(), cwd);
}
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
} else {
caller_stack.add_overlay(overlay_name);
// make sure that PWD is not restored in old overlays.
if let Some(cwd) = cwd {
caller_stack.add_env_var("PWD".to_string(), cwd);
}
}
} else {
caller_stack.add_overlay(overlay_name);

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-plugin"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
version = "0.103.0"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,10 +13,10 @@ version = "0.103.0"
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.0" }
nu-path = { path = "../nu-path", version = "0.103.0" }
nu-protocol = { path = "../nu-protocol", version = "0.103.0", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.103.0" }
nu-engine = { path = "../nu-engine", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1" }
itertools = { workspace = true }

View File

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

View File

@ -120,7 +120,7 @@ impl<'a> StyleComputer<'a> {
("int".to_string(), ComputableStyle::Static(Color::White.normal())),
("filesize".to_string(), ComputableStyle::Static(Color::Cyan.normal())),
("duration".to_string(), ComputableStyle::Static(Color::White.normal())),
("date".to_string(), ComputableStyle::Static(Color::Purple.normal())),
("datetime".to_string(), ComputableStyle::Static(Color::Purple.normal())),
("range".to_string(), ComputableStyle::Static(Color::White.normal())),
("float".to_string(), ComputableStyle::Static(Color::White.normal())),
("string".to_string(), ComputableStyle::Static(Color::White.normal())),

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.103.0"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,21 +16,21 @@ bench = false
workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.0" }
nu-color-config = { path = "../nu-color-config", version = "0.103.0" }
nu-engine = { path = "../nu-engine", version = "0.103.0", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.103.0" }
nu-json = { path = "../nu-json", version = "0.103.0" }
nu-parser = { path = "../nu-parser", version = "0.103.0" }
nu-path = { path = "../nu-path", version = "0.103.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.103.0" }
nu-protocol = { path = "../nu-protocol", version = "0.103.0", default-features = false }
nu-system = { path = "../nu-system", version = "0.103.0" }
nu-table = { path = "../nu-table", version = "0.103.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.103.0" }
nu-utils = { path = "../nu-utils", version = "0.103.0", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-json = { path = "../nu-json", version = "0.104.1" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-system = { path = "../nu-system", version = "0.104.1" }
nu-table = { path = "../nu-table", version = "0.104.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.103.0" }
nuon = { path = "../nuon", version = "0.104.1" }
alphanumeric-sort = { workspace = true }
base64 = { workspace = true }
@ -129,7 +129,7 @@ v_htmlescape = { workspace = true }
wax = { workspace = true }
which = { workspace = true, optional = true }
unicode-width = { workspace = true }
data-encoding = { version = "2.8.0", features = ["alloc"] }
data-encoding = { version = "2.9.0", features = ["alloc"] }
web-time = { workspace = true }
[target.'cfg(windows)'.dependencies]
@ -209,8 +209,8 @@ sqlite = ["rusqlite"]
trash-support = ["trash"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.0" }
nu-test-support = { path = "../nu-test-support", version = "0.103.0" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
dirs = { workspace = true }
mockito = { workspace = true, default-features = false }

View File

@ -1,12 +1,29 @@
use crate::{generate_strftime_list, parse_date_from_string};
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, TimeZone, Utc};
use human_date_parser::{from_human_time, ParseResult};
use chrono::{
DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone,
Timelike, Utc,
};
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*;
const HOUR: i32 = 60 * 60;
const ALLOWED_COLUMNS: [&str; 10] = [
"year",
"month",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
"timezone",
];
#[derive(Clone, Debug)]
struct Arguments {
zone_options: Option<Spanned<Zone>>,
format_options: Option<DatetimeFormat>,
format_options: Option<Spanned<DatetimeFormat>>,
cell_paths: Option<Vec<CellPath>>,
}
@ -64,8 +81,12 @@ impl Command for IntoDatetime {
(Type::String, Type::Date),
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
(Type::Nothing, Type::table()),
// FIXME: https://github.com/nushell/nushell/issues/15485
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
(Type::record(), Type::Any),
(Type::record(), Type::record()),
(Type::record(), Type::Date),
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
// which aren't caught by the parser. see https://github.com/nushell/nushell/pull/14922 for more details
// only applicable for --list flag
@ -95,11 +116,6 @@ impl Command for IntoDatetime {
"Show all possible variables for use in --format flag",
Some('l'),
)
.switch(
"list-human",
"Show human-readable datetime parsing examples",
Some('n'),
)
.rest(
"rest",
SyntaxShape::CellPath,
@ -117,8 +133,6 @@ impl Command for IntoDatetime {
) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "list")? {
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
} else if call.has_flag(engine_state, stack, "list-human")? {
Ok(list_human_readable_examples(call.head).into_pipeline_data())
} else {
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
@ -138,13 +152,16 @@ impl Command for IntoDatetime {
};
let format_options = call
.get_flag::<String>(engine_state, stack, "format")?
.get_flag::<Spanned<String>>(engine_state, stack, "format")?
.as_ref()
.map(|fmt| DatetimeFormat(fmt.to_string()));
.map(|fmt| Spanned {
item: DatetimeFormat(fmt.item.to_string()),
span: fmt.span,
});
let args = Arguments {
format_options,
zone_options,
format_options,
cell_paths,
};
operate(action, args, input, call.head, engine_state.signals())
@ -220,6 +237,12 @@ impl Command for IntoDatetime {
#[allow(clippy::inconsistent_digit_grouping)]
result: example_result_1(1614434140_000000000),
},
Example {
description: "Using a record as input",
example: "{year: 2025, month: 3, day: 30, hour: 12, minute: 15, second: 59, timezone: '+02:00'} | into datetime",
#[allow(clippy::inconsistent_digit_grouping)]
result: example_result_1(1743329759_000000000),
},
Example {
description: "Convert list of timestamps to datetimes",
example: r#"["2023-03-30 10:10:07 -05:00", "2023-05-05 13:43:49 -05:00", "2023-06-05 01:37:42 -05:00"] | into datetime"#,
@ -253,26 +276,11 @@ impl Command for IntoDatetime {
Span::test_data(),
)),
},
Example {
description: "Parsing human readable datetimes",
example: "'Today at 18:30' | into datetime",
result: None,
},
Example {
description: "Parsing human readable datetimes",
example: "'Last Friday at 19:45' | into datetime",
result: None,
},
Example {
description: "Parsing human readable datetimes",
example: "'In 5 minutes and 30 seconds' | into datetime",
result: None,
},
]
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
struct DatetimeFormat(String);
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
@ -284,45 +292,44 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
return input.clone();
}
if let Value::Record { val: record, .. } = input {
if let Some(tz) = timezone {
return Value::error(
ShellError::IncompatibleParameters {
left_message: "got a record as input".into(),
left_span: head,
right_message: "the timezone should be included in the record".into(),
right_span: tz.span,
},
head,
);
}
if let Some(dt) = dateformat {
return Value::error(
ShellError::IncompatibleParameters {
left_message: "got a record as input".into(),
left_span: head,
right_message: "cannot be used with records".into(),
right_span: dt.span,
},
head,
);
}
let span = input.span();
return merge_record(record, head, span).unwrap_or_else(|err| Value::error(err, span));
}
// Let's try dtparse first
if matches!(input, Value::String { .. }) && dateformat.is_none() {
let span = input.span();
if let Ok(input_val) = input.coerce_str() {
match parse_date_from_string(&input_val, span) {
Ok(date) => return Value::date(date, span),
Err(_) => {
if let Ok(date) = from_human_time(&input_val) {
match date {
ParseResult::Date(date) => {
let time = Local::now().time();
let combined = date.and_time(time);
let local_offset = *Local::now().offset();
let dt_fixed =
TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
return Value::date(dt_fixed, span);
}
ParseResult::DateTime(date) => {
return Value::date(date.fixed_offset(), span)
}
ParseResult::Time(time) => {
let date = Local::now().date_naive();
let combined = date.and_time(time);
let local_offset = *Local::now().offset();
let dt_fixed =
TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
return Value::date(dt_fixed, span);
}
}
}
}
};
if let Ok(date) = parse_date_from_string(&input_val, span) {
return Value::date(date, span);
}
}
}
const HOUR: i32 = 60 * 60;
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
let timestamp = match input {
@ -403,23 +410,59 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let parse_as_string = |val: &str| {
match dateformat {
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
Ok(d) => Value::date ( d, head ),
Err(reason) => {
match NaiveDateTime::parse_from_str(val, &dt.0) {
Ok(d) => {
let dt_fixed =
Local.from_local_datetime(&d).single().unwrap_or_default();
Value::date(dt_fixed.into(),head)
}
Err(_) => {
Value::error (
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
head,
)
}
Some(dt_format) => match DateTime::parse_from_str(val, &dt_format.item.0) {
Ok(dt) => {
match timezone {
None => {
Value::date ( dt, head )
},
Some(Spanned { item, span }) => match item {
Zone::Utc => {
Value::date ( dt, head )
}
Zone::Local => {
Value::date(dt.with_timezone(&Local).into(), *span)
}
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
Some(eastoffset) => {
Value::date(dt.with_timezone(&eastoffset), *span)
}
None => Value::error(
ShellError::DatetimeParseError {
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
span: *span,
},
*span,
),
},
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
Some(westoffset) => {
Value::date(dt.with_timezone(&westoffset), *span)
}
None => Value::error(
ShellError::DatetimeParseError {
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
span: *span,
},
*span,
),
},
Zone::Error => Value::error(
// This is an argument error, not an input error
ShellError::TypeMismatch {
err_message: "Invalid timezone or offset".to_string(),
span: *span,
},
*span,
),
},
}
},
Err(reason) => {
parse_with_format(val, &dt_format.item.0, head).unwrap_or_else(|_| Value::error (
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt_format.item.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
head,
))
}
},
@ -454,42 +497,260 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}
}
fn list_human_readable_examples(span: Span) -> Value {
let examples: Vec<String> = vec![
"Today 18:30".into(),
"2022-11-07 13:25:30".into(),
"15:20 Friday".into(),
"This Friday 17:00".into(),
"13:25, Next Tuesday".into(),
"Last Friday at 19:45".into(),
"In 3 days".into(),
"In 2 hours".into(),
"10 hours and 5 minutes ago".into(),
"1 years ago".into(),
"A year ago".into(),
"A month ago".into(),
"A week ago".into(),
"A day ago".into(),
"An hour ago".into(),
"A minute ago".into(),
"A second ago".into(),
"Now".into(),
];
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
if let Some(invalid_col) = record
.columns()
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
{
let allowed_cols = ALLOWED_COLUMNS.join(", ");
return Err(ShellError::UnsupportedInput {
msg: format!(
"Column '{invalid_col}' is not valid for a structured datetime. Allowed columns are: {allowed_cols}"
),
input: "value originates from here".into(),
msg_span: head,
input_span: span
}
);
};
let records = examples
.iter()
.map(|s| {
Value::record(
record! {
"parseable human datetime examples" => Value::test_string(s.to_string()),
"result" => action(&Value::test_string(s.to_string()), &Arguments { zone_options: None, format_options: None, cell_paths: None }, span)
},
span,
)
})
.collect::<Vec<Value>>();
// Empty fields are filled in a specific way: the time units bigger than the biggest provided fields are assumed to be current and smaller ones are zeroed.
// And local timezone is used if not provided.
#[derive(Debug)]
enum RecordColumnDefault {
Now,
Zero,
}
let mut record_column_default = RecordColumnDefault::Now;
Value::list(records, span)
let now = Local::now();
let mut now_nanosecond = now.nanosecond();
let now_millisecond = now_nanosecond / 1_000_000;
now_nanosecond %= 1_000_000;
let now_microsecond = now_nanosecond / 1_000;
now_nanosecond %= 1_000;
let year: i32 = match record.get("year") {
Some(val) => {
record_column_default = RecordColumnDefault::Zero;
match val {
Value::Int { val, .. } => *val as i32,
other => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "int".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
});
}
}
}
None => now.year(),
};
let month = match record.get("month") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("month", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now.month(),
RecordColumnDefault::Zero => 1,
},
};
let day = match record.get("day") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("day", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now.day(),
RecordColumnDefault::Zero => 1,
},
};
let hour = match record.get("hour") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("hour", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now.hour(),
RecordColumnDefault::Zero => 0,
},
};
let minute = match record.get("minute") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("minute", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now.minute(),
RecordColumnDefault::Zero => 0,
},
};
let second = match record.get("second") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("second", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now.second(),
RecordColumnDefault::Zero => 0,
},
};
let millisecond = match record.get("millisecond") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("millisecond", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now_millisecond,
RecordColumnDefault::Zero => 0,
},
};
let microsecond = match record.get("microsecond") {
Some(col_val) => {
record_column_default = RecordColumnDefault::Zero;
parse_value_from_record_as_u32("microsecond", col_val, &head, &span)?
}
None => match record_column_default {
RecordColumnDefault::Now => now_microsecond,
RecordColumnDefault::Zero => 0,
},
};
let nanosecond = match record.get("nanosecond") {
Some(col_val) => parse_value_from_record_as_u32("nanosecond", col_val, &head, &span)?,
None => match record_column_default {
RecordColumnDefault::Now => now_nanosecond,
RecordColumnDefault::Zero => 0,
},
};
let offset: FixedOffset = match record.get("timezone") {
Some(timezone) => parse_timezone_from_record(timezone, &head, &timezone.span())?,
None => now.offset().to_owned(),
};
let total_nanoseconds = nanosecond + microsecond * 1_000 + millisecond * 1_000_000;
let date = match NaiveDate::from_ymd_opt(year, month, day) {
Some(d) => d,
None => {
return Err(ShellError::IncorrectValue {
msg: "one of more values are incorrect and do not represent valid date".to_string(),
val_span: head,
call_span: span,
})
}
};
let time = match NaiveTime::from_hms_nano_opt(hour, minute, second, total_nanoseconds) {
Some(t) => t,
None => {
return Err(ShellError::IncorrectValue {
msg: "one of more values are incorrect and do not represent valid time".to_string(),
val_span: head,
call_span: span,
})
}
};
let date_time = NaiveDateTime::new(date, time);
let date_time_fixed = match offset.from_local_datetime(&date_time).single() {
Some(d) => d,
None => {
return Err(ShellError::IncorrectValue {
msg: "Ambiguous or invalid timezone conversion".to_string(),
val_span: head,
call_span: span,
})
}
};
Ok(Value::date(date_time_fixed, span))
}
fn parse_value_from_record_as_u32(
col: &str,
col_val: &Value,
head: &Span,
span: &Span,
) -> Result<u32, ShellError> {
let value: u32 = match col_val {
Value::Int { val, .. } => {
if *val < 0 || *val > u32::MAX as i64 {
return Err(ShellError::IncorrectValue {
msg: format!("incorrect value for {}", col),
val_span: *head,
call_span: *span,
});
}
*val as u32
}
other => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "int".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: *head,
src_span: other.span(),
});
}
};
Ok(value)
}
fn parse_timezone_from_record(
timezone: &Value,
head: &Span,
span: &Span,
) -> Result<FixedOffset, ShellError> {
match timezone {
Value::String { val, .. } => {
let offset: FixedOffset = match val.parse() {
Ok(offset) => offset,
Err(_) => {
return Err(ShellError::IncorrectValue {
msg: "invalid timezone".to_string(),
val_span: *span,
call_span: *head,
})
}
};
Ok(offset)
}
other => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: *head,
src_span: other.span(),
}),
}
}
fn parse_with_format(val: &str, fmt: &str, head: Span) -> Result<Value, ()> {
// try parsing at date + time
if let Ok(dt) = NaiveDateTime::parse_from_str(val, fmt) {
let dt_native = Local.from_local_datetime(&dt).single().unwrap_or_default();
return Ok(Value::date(dt_native.into(), head));
}
// try parsing at date only
if let Ok(date) = NaiveDate::parse_from_str(val, fmt) {
if let Some(dt) = date.and_hms_opt(0, 0, 0) {
let dt_native = Local.from_local_datetime(&dt).single().unwrap_or_default();
return Ok(Value::date(dt_native.into(), head));
}
}
// try parsing at time only
if let Ok(time) = NaiveTime::parse_from_str(val, fmt) {
let now = Local::now().naive_local().date();
let dt_native = Local
.from_local_datetime(&now.and_time(time))
.single()
.unwrap_or_default();
return Ok(Value::date(dt_native.into(), head));
}
Err(())
}
#[cfg(test)]
@ -508,7 +769,10 @@ mod tests {
#[test]
fn takes_a_date_format_with_timezone() {
let date_str = Value::test_string("16.11.1984 8:00 am +0000");
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let fmt_options = Some(Spanned {
item: DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: None,
format_options: fmt_options,
@ -523,16 +787,12 @@ mod tests {
}
#[test]
#[ignore]
fn takes_a_date_format_without_timezone() {
// Ignoring this test for now because we changed the human-date-parser to use
// the users timezone instead of UTC. We may continue to tweak this behavior.
// Another hacky solution is to set the timezone to UTC in the test, which works
// on MacOS and Linux but hasn't been tested on Windows. Plus it kind of defeats
// the purpose of a "without_timezone" test.
// std::env::set_var("TZ", "UTC");
let date_str = Value::test_string("16.11.1984 8:00 am");
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P".to_string()));
let fmt_options = Some(Spanned {
item: DatetimeFormat("%d.%m.%Y %H:%M %P".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: None,
format_options: fmt_options,
@ -614,7 +874,10 @@ mod tests {
#[test]
fn takes_int_with_formatstring() {
let date_int = Value::test_int(1_614_434_140);
let fmt_options = Some(DatetimeFormat("%s".to_string()));
let fmt_options = Some(Spanned {
item: DatetimeFormat("%s".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: None,
format_options: fmt_options,
@ -629,6 +892,55 @@ mod tests {
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp_offset_as_int_with_formatting() {
let date_int = Value::test_int(1_614_434_140);
let timezone_option = Some(Spanned {
item: Zone::East(8),
span: Span::test_data(),
});
let fmt_options = Some(Spanned {
item: DatetimeFormat("%s".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: timezone_option,
format_options: fmt_options,
cell_paths: None,
};
let actual = action(&date_int, &args, Span::test_data());
let expected = Value::date(
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp_offset_as_int_with_local_timezone() {
let date_int = Value::test_int(1_614_434_140);
let timezone_option = Some(Spanned {
item: Zone::Local,
span: Span::test_data(),
});
let fmt_options = Some(Spanned {
item: DatetimeFormat("%s".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: timezone_option,
format_options: fmt_options,
cell_paths: None,
};
let actual = action(&date_int, &args, Span::test_data());
let expected = Value::date(
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp() {
let date_str = Value::test_string("1614434140000000000");
@ -643,7 +955,7 @@ mod tests {
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::date(
Local.timestamp_opt(1614434140, 0).unwrap().into(),
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
Span::test_data(),
);
@ -662,7 +974,7 @@ mod tests {
cell_paths: None,
};
let expected = Value::date(
Local.timestamp_opt(1614434140, 0).unwrap().into(),
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
Span::test_data(),
);
let actual = action(&expected, &args, Span::test_data());
@ -681,7 +993,7 @@ mod tests {
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::date(
Utc.timestamp_opt(1614434140, 0).unwrap().into(),
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
Span::test_data(),
);
@ -691,7 +1003,10 @@ mod tests {
#[test]
fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
let date_str = Value::test_string("16.11.1984 8:00 am Oops0000");
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let fmt_options = Some(Spanned {
item: DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()),
span: Span::test_data(),
});
let args = Arguments {
zone_options: None,
format_options: fmt_options,

View File

@ -1,8 +1,41 @@
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*;
use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
use nu_protocol::{ast::Expr, Unit};
const NS_PER_US: i64 = 1_000;
const NS_PER_MS: i64 = 1_000_000;
const NS_PER_SEC: i64 = 1_000_000_000;
const NS_PER_MINUTE: i64 = 60 * NS_PER_SEC;
const NS_PER_HOUR: i64 = 60 * NS_PER_MINUTE;
const NS_PER_DAY: i64 = 24 * NS_PER_HOUR;
const NS_PER_WEEK: i64 = 7 * NS_PER_DAY;
const ALLOWED_COLUMNS: [&str; 9] = [
"week",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
"sign",
];
const ALLOWED_SIGNS: [&str; 2] = ["+", "-"];
#[derive(Clone, Debug)]
struct Arguments {
unit: Option<Spanned<String>>,
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct IntoDuration;
@ -15,13 +48,17 @@ impl Command for IntoDuration {
Signature::build("into duration")
.input_output_types(vec![
(Type::Int, Type::Duration),
(Type::Float, Type::Duration),
(Type::String, Type::Duration),
(Type::Duration, Type::Duration),
// FIXME: https://github.com/nushell/nushell/issues/15485
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
(Type::record(), Type::Any),
(Type::record(), Type::record()),
(Type::record(), Type::Duration),
(Type::table(), Type::table()),
//todo: record<hour,minute,sign> | into duration -> Duration
//(Type::record(), Type::record()),
])
//.allow_variants_without_examples(true)
.allow_variants_without_examples(true)
.named(
"unit",
SyntaxShape::String,
@ -55,7 +92,35 @@ impl Command for IntoDuration {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
into_duration(engine_state, stack, call, input)
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let span = match input.span() {
Some(t) => t,
None => call.head,
};
let unit = match call.get_flag::<Spanned<String>>(engine_state, stack, "unit")? {
Some(spanned_unit) => {
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
.contains(&spanned_unit.item.as_str())
{
Some(spanned_unit)
} else {
return Err(ShellError::CantConvertToDuration {
details: spanned_unit.item,
dst_span: span,
src_span: span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk"
.to_string(),
),
});
}
}
None => None,
};
let args = Arguments { unit, cell_paths };
operate(action, args, input, call.head, engine_state.signals())
}
fn examples(&self) -> Vec<Example> {
@ -109,67 +174,23 @@ impl Command for IntoDuration {
example: "1_234 | into duration --unit ms",
result: Some(Value::test_duration(1_234 * 1_000_000)),
},
Example {
description: "Convert a floating point number of an arbitrary unit to duration",
example: "1.234 | into duration --unit sec",
result: Some(Value::test_duration(1_234 * 1_000_000)),
},
Example {
description: "Convert a record to a duration",
example: "{day: 10, hour: 2, minute: 6, second: 50, sign: '+'} | into duration",
result: Some(Value::duration(
10 * NS_PER_DAY + 2 * NS_PER_HOUR + 6 * NS_PER_MINUTE + 50 * NS_PER_SEC,
Span::test_data(),
)),
},
]
}
}
fn into_duration(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = match input.span() {
Some(t) => t,
None => call.head,
};
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let unit = match call.get_flag::<String>(engine_state, stack, "unit")? {
Some(sep) => {
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
.iter()
.any(|d| d == &sep)
{
sep
} else {
return Err(ShellError::CantConvertToDuration {
details: sep,
dst_span: span,
src_span: span,
help: Some(
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string(),
),
});
}
}
None => "ns".to_string(),
};
input.map(
move |v| {
if column_paths.is_empty() {
action(&v, &unit.clone(), span)
} else {
let unitclone = &unit.clone();
let mut ret = v;
for path in &column_paths {
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, unitclone, span)),
);
if let Err(error) = r {
return Value::error(error, span);
}
}
ret
}
},
engine_state.signals(),
)
}
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
s.split_whitespace().map(move |sub| {
// Gets the offset of the `sub` substring inside the string `s`.
@ -232,27 +253,51 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
})
}
fn action(input: &Value, unit: &str, span: Span) -> Value {
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let value_span = input.span();
let unit_option = &args.unit;
if let Value::Record { .. } | Value::Duration { .. } = input {
if let Some(unit) = unit_option {
return Value::error(
ShellError::IncompatibleParameters {
left_message: "got a record as input".into(),
left_span: head,
right_message: "the units should be included in the record".into(),
right_span: unit.span,
},
head,
);
}
}
let unit: &str = match unit_option {
Some(unit) => &unit.item,
None => "ns",
};
match input {
Value::Duration { .. } => input.clone(),
Value::String { val, .. } => match compound_to_duration(val, value_span) {
Ok(val) => Value::duration(val, span),
Err(error) => Value::error(error, span),
},
Value::Record { val, .. } => {
merge_record(val, head, value_span).unwrap_or_else(|err| Value::error(err, value_span))
}
Value::String { val, .. } => {
if let Ok(num) = val.parse::<f64>() {
let ns = unit_to_ns_factor(unit);
return Value::duration((num * (ns as f64)) as i64, head);
}
match compound_to_duration(val, value_span) {
Ok(val) => Value::duration(val, head),
Err(error) => Value::error(error, head),
}
}
Value::Float { val, .. } => {
let ns = unit_to_ns_factor(unit);
Value::duration((*val * (ns as f64)) as i64, head)
}
Value::Int { val, .. } => {
let ns = match unit {
"ns" => 1,
"us" | "µs" => 1_000,
"ms" => 1_000_000,
"sec" => NS_PER_SEC,
"min" => NS_PER_SEC * 60,
"hr" => NS_PER_SEC * 60 * 60,
"day" => NS_PER_SEC * 60 * 60 * 24,
"wk" => NS_PER_SEC * 60 * 60 * 24 * 7,
_ => 0,
};
Value::duration(*val * ns, span)
let ns = unit_to_ns_factor(unit);
Value::duration(*val * ns, head)
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
@ -260,14 +305,134 @@ fn action(input: &Value, unit: &str, span: Span) -> Value {
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or duration".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
dst_span: head,
src_span: other.span(),
},
span,
head,
),
}
}
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
if let Some(invalid_col) = record
.columns()
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
{
let allowed_cols = ALLOWED_COLUMNS.join(", ");
return Err(ShellError::UnsupportedInput {
msg: format!(
"Column '{invalid_col}' is not valid for a structured duration. Allowed columns are: {allowed_cols}"
),
input: "value originates from here".into(),
msg_span: head,
input_span: span
}
);
};
let mut duration: i64 = 0;
if let Some(col_val) = record.get("week") {
let week = parse_number_from_record(col_val, &head)?;
duration += week * NS_PER_WEEK;
};
if let Some(col_val) = record.get("day") {
let day = parse_number_from_record(col_val, &head)?;
duration += day * NS_PER_DAY;
};
if let Some(col_val) = record.get("hour") {
let hour = parse_number_from_record(col_val, &head)?;
duration += hour * NS_PER_HOUR;
};
if let Some(col_val) = record.get("minute") {
let minute = parse_number_from_record(col_val, &head)?;
duration += minute * NS_PER_MINUTE;
};
if let Some(col_val) = record.get("second") {
let second = parse_number_from_record(col_val, &head)?;
duration += second * NS_PER_SEC;
};
if let Some(col_val) = record.get("millisecond") {
let millisecond = parse_number_from_record(col_val, &head)?;
duration += millisecond * NS_PER_MS;
};
if let Some(col_val) = record.get("microsecond") {
let microsecond = parse_number_from_record(col_val, &head)?;
duration += microsecond * NS_PER_US;
};
if let Some(col_val) = record.get("nanosecond") {
let nanosecond = parse_number_from_record(col_val, &head)?;
duration += nanosecond;
};
if let Some(sign) = record.get("sign") {
match sign {
Value::String { val, .. } => {
if !ALLOWED_SIGNS.contains(&val.as_str()) {
let allowed_signs = ALLOWED_SIGNS.join(", ");
return Err(ShellError::IncorrectValue {
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
.to_string(),
val_span: sign.span(),
call_span: head,
});
}
if val == "-" {
duration = -duration;
}
}
other => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "int".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
});
}
}
};
Ok(Value::duration(duration, span))
}
fn parse_number_from_record(col_val: &Value, head: &Span) -> Result<i64, ShellError> {
let value = match col_val {
Value::Int { val, .. } => {
if *val < 0 {
return Err(ShellError::IncorrectValue {
msg: "number should be positive".to_string(),
val_span: col_val.span(),
call_span: *head,
});
}
*val
}
other => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "int".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: *head,
src_span: other.span(),
});
}
};
Ok(value)
}
fn unit_to_ns_factor(unit: &str) -> i64 {
match unit {
"ns" => 1,
"us" | "µs" => NS_PER_US,
"ms" => NS_PER_MS,
"sec" => NS_PER_SEC,
"min" => NS_PER_MINUTE,
"hr" => NS_PER_HOUR,
"day" => NS_PER_DAY,
"wk" => NS_PER_WEEK,
_ => 0,
}
}
#[cfg(test)]
mod test {
use super::*;
@ -284,24 +449,27 @@ mod test {
#[rstest]
#[case("3ns", 3)]
#[case("4us", 4*1000)]
#[case("4\u{00B5}s", 4*1000)] // micro sign
#[case("4\u{03BC}s", 4*1000)] // mu symbol
#[case("5ms", 5 * 1000 * 1000)]
#[case("4us", 4 * NS_PER_US)]
#[case("4\u{00B5}s", 4 * NS_PER_US)] // micro sign
#[case("4\u{03BC}s", 4 * NS_PER_US)] // mu symbol
#[case("5ms", 5 * NS_PER_MS)]
#[case("1sec", NS_PER_SEC)]
#[case("7min", 7 * 60 * NS_PER_SEC)]
#[case("42hr", 42 * 60 * 60 * NS_PER_SEC)]
#[case("123day", 123 * 24 * 60 * 60 * NS_PER_SEC)]
#[case("3wk", 3 * 7 * 24 * 60 * 60 * NS_PER_SEC)]
#[case("7min", 7 * NS_PER_MINUTE)]
#[case("42hr", 42 * NS_PER_HOUR)]
#[case("123day", 123 * NS_PER_DAY)]
#[case("3wk", 3 * NS_PER_WEEK)]
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
#[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order
#[case("14ns 3hr 17sec", 14 + 3 * NS_PER_HOUR + 17 * NS_PER_SEC)] // compound string with units in random order
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
let actual = action(
&Value::test_string(phrase),
"ns",
Span::new(0, phrase.len()),
);
let args = Arguments {
unit: Some(Spanned {
item: "ns".to_string(),
span: Span::test_data(),
}),
cell_paths: None,
};
let actual = action(&Value::test_string(phrase), &args, Span::test_data());
match actual {
Value::Duration {
val: observed_val, ..

View File

@ -208,7 +208,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
}
} else if DATETIME_DMY_RE.is_match(&val_str).unwrap_or(false) {
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
to_type: "date".to_string(),
to_type: "datetime".to_string(),
from_type: "string".to_string(),
span,
help: Some(format!(
@ -219,7 +219,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
Ok(Value::date(dt, span))
} else if DATETIME_YMD_RE.is_match(&val_str).unwrap_or(false) {
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
to_type: "date".to_string(),
to_type: "datetime".to_string(),
from_type: "string".to_string(),
span,
help: Some(format!(
@ -230,7 +230,7 @@ fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Va
Ok(Value::date(dt, span))
} else if DATETIME_YMDZ_RE.is_match(&val_str).unwrap_or(false) {
let dt = parse_date_from_string(&val_str, span).map_err(|_| ShellError::CantConvert {
to_type: "date".to_string(),
to_type: "datetime".to_string(),
from_type: "string".to_string(),
span,
help: Some(format!(

View File

@ -40,6 +40,7 @@ impl Command for SplitCellPath {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let input_type = input.get_type();
let src_span = match input {
// Early return on correct type and empty pipeline
@ -54,8 +55,9 @@ impl Command for SplitCellPath {
PipelineData::ListStream(stream, ..) => stream.span(),
PipelineData::ByteStream(stream, ..) => stream.span(),
};
Err(ShellError::PipelineMismatch {
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "cell-path".into(),
wrong_type: input_type.to_string(),
dst_span: head,
src_span,
})

View File

@ -0,0 +1,261 @@
use chrono::{Local, TimeZone};
use human_date_parser::{from_human_time, ParseResult};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct DateFromHuman;
impl Command for DateFromHuman {
fn name(&self) -> &str {
"date from-human"
}
fn signature(&self) -> Signature {
Signature::build("date from-human")
.input_output_types(vec![
(Type::String, Type::Date),
(Type::Nothing, Type::table()),
])
.allow_variants_without_examples(true)
.switch(
"list",
"Show human-readable datetime parsing examples",
Some('l'),
)
.category(Category::Date)
}
fn description(&self) -> &str {
"Convert a human readable datetime string to a datetime."
}
fn search_terms(&self) -> Vec<&str> {
vec![
"relative",
"now",
"today",
"tomorrow",
"yesterday",
"weekday",
"weekday_name",
"timezone",
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "list")? {
return Ok(list_human_readable_examples(call.head).into_pipeline_data());
}
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
input.map(move |value| helper(value, head), engine_state.signals())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Parsing human readable datetime",
example: "'Today at 18:30' | date from-human",
result: None,
},
Example {
description: "Parsing human readable datetime",
example: "'Last Friday at 19:45' | date from-human",
result: None,
},
Example {
description: "Parsing human readable datetime",
example: "'In 5 minutes and 30 seconds' | date from-human",
result: None,
},
Example {
description: "PShow human-readable datetime parsing examples",
example: "date from-human --list",
result: None,
},
]
}
}
fn helper(value: Value, head: Span) -> Value {
let span = value.span();
let input_val = match value {
Value::String { val, .. } => val,
other => {
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: span,
},
span,
)
}
};
let now = Local::now();
if let Ok(date) = from_human_time(&input_val, now.naive_local()) {
match date {
ParseResult::Date(date) => {
let time = now.time();
let combined = date.and_time(time);
let local_offset = *now.offset();
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
return Value::date(dt_fixed, span);
}
ParseResult::DateTime(date) => {
let local_offset = *now.offset();
let dt_fixed = match local_offset.from_local_datetime(&date) {
chrono::LocalResult::Single(dt) => dt,
chrono::LocalResult::Ambiguous(_, _) => {
return Value::error(
ShellError::DatetimeParseError {
msg: "Ambiguous datetime".to_string(),
span,
},
span,
);
}
chrono::LocalResult::None => {
return Value::error(
ShellError::DatetimeParseError {
msg: "Invalid datetime".to_string(),
span,
},
span,
);
}
};
return Value::date(dt_fixed, span);
}
ParseResult::Time(time) => {
let date = now.date_naive();
let combined = date.and_time(time);
let local_offset = *now.offset();
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
return Value::date(dt_fixed, span);
}
}
}
match from_human_time(&input_val, now.naive_local()) {
Ok(date) => match date {
ParseResult::Date(date) => {
let time = now.time();
let combined = date.and_time(time);
let local_offset = *now.offset();
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
Value::date(dt_fixed, span)
}
ParseResult::DateTime(date) => {
let local_offset = *now.offset();
let dt_fixed = match local_offset.from_local_datetime(&date) {
chrono::LocalResult::Single(dt) => dt,
chrono::LocalResult::Ambiguous(_, _) => {
return Value::error(
ShellError::DatetimeParseError {
msg: "Ambiguous datetime".to_string(),
span,
},
span,
);
}
chrono::LocalResult::None => {
return Value::error(
ShellError::DatetimeParseError {
msg: "Invalid datetime".to_string(),
span,
},
span,
);
}
};
Value::date(dt_fixed, span)
}
ParseResult::Time(time) => {
let date = now.date_naive();
let combined = date.and_time(time);
let local_offset = *now.offset();
let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined)
.single()
.unwrap_or_default();
Value::date(dt_fixed, span)
}
},
Err(_) => Value::error(
ShellError::IncorrectValue {
msg: "Cannot parse as humanized date".to_string(),
val_span: head,
call_span: span,
},
span,
),
}
}
fn list_human_readable_examples(span: Span) -> Value {
let examples: Vec<String> = vec![
"Today 18:30".into(),
"2022-11-07 13:25:30".into(),
"15:20 Friday".into(),
"This Friday 17:00".into(),
"13:25, Next Tuesday".into(),
"Last Friday at 19:45".into(),
"In 3 days".into(),
"In 2 hours".into(),
"10 hours and 5 minutes ago".into(),
"1 years ago".into(),
"A year ago".into(),
"A month ago".into(),
"A week ago".into(),
"A day ago".into(),
"An hour ago".into(),
"A minute ago".into(),
"A second ago".into(),
"Now".into(),
];
let records = examples
.iter()
.map(|s| {
Value::record(
record! {
"parseable human datetime examples" => Value::test_string(s.to_string()),
"result" => helper(Value::test_string(s.to_string()), span),
},
span,
)
})
.collect::<Vec<Value>>();
Value::list(records, span)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(DateFromHuman {})
}
}

View File

@ -1,4 +1,5 @@
mod date_;
mod from_human;
mod humanize;
mod list_timezone;
mod now;
@ -7,6 +8,7 @@ mod to_timezone;
mod utils;
pub use date_::Date;
pub use from_human::DateFromHuman;
pub use humanize::DateHumanize;
pub use list_timezone::DateListTimezones;
pub use now::DateNow;

View File

@ -38,10 +38,16 @@ impl Command for DateNow {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current date and display it in a given format string.",
description: "Get the current date and format it in a given format string.",
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
result: None,
},
Example {
description:
"Get the current date and format it according to the RFC 3339 standard.",
example: r#"date now | format date "%+""#,
result: None,
},
Example {
description: "Get the time duration since 2019-04-30.",
example: r#"(date now) - 2019-05-01"#,
@ -53,7 +59,8 @@ impl Command for DateNow {
result: None,
},
Example {
description: "Get current time in full RFC 3339 format with time zone.",
description:
"Get current time and format it in the debug format (RFC 2822 with timezone)",
example: r#"date now | debug"#,
result: None,
},

View File

@ -9,7 +9,11 @@ pub(crate) fn parse_date_from_string(
Ok((native_dt, fixed_offset)) => {
let offset = match fixed_offset {
Some(offset) => offset,
None => *(Local::now().offset()),
None => *Local
.from_local_datetime(&native_dt)
.single()
.unwrap_or_default()
.offset(),
};
match offset.from_local_datetime(&native_dt) {
LocalResult::Single(d) => Ok(d),

View File

@ -23,6 +23,11 @@ impl Command for Debug {
])
.category(Category::Debug)
.switch("raw", "Prints the raw value representation", Some('r'))
.switch(
"raw-value",
"Prints the raw value representation but not the nushell value part",
Some('v'),
)
}
fn run(
@ -35,6 +40,7 @@ impl Command for Debug {
let head = call.head;
let config = stack.get_config(engine_state);
let raw = call.has_flag(engine_state, stack, "raw")?;
let raw_value = call.has_flag(engine_state, stack, "raw-value")?;
// Should PipelineData::Empty result in an error here?
@ -42,6 +48,11 @@ impl Command for Debug {
move |x| {
if raw {
Value::string(x.to_debug_string(), head)
} else if raw_value {
match x.coerce_into_string_all() {
Ok(s) => Value::string(format!("{s:#?}"), head),
Err(e) => Value::error(e, head),
}
} else {
Value::string(x.to_expanded_string(", ", &config), head)
}
@ -78,10 +89,75 @@ impl Command for Debug {
Span::test_data(),
)),
},
Example {
description: "Debug print an ansi escape encoded string and get the raw value",
example: "$'(ansi red)nushell(ansi reset)' | debug -v",
result: Some(Value::test_string("\"\\u{1b}[31mnushell\\u{1b}[0m\"")),
},
]
}
}
// This is just a local Value Extension trait to avoid having to
// put another *_to_string() converter in nu_protocol
trait ValueExt {
fn coerce_into_string_all(&self) -> Result<String, ShellError>;
fn cant_convert_to<T>(&self, typ: &str) -> Result<T, ShellError>;
}
impl ValueExt for Value {
fn cant_convert_to<T>(&self, typ: &str) -> Result<T, ShellError> {
Err(ShellError::CantConvert {
to_type: typ.into(),
from_type: self.get_type().to_string(),
span: self.span(),
help: None,
})
}
fn coerce_into_string_all(&self) -> Result<String, ShellError> {
let span = self.span();
match self {
Value::Bool { val, .. } => Ok(val.to_string()),
Value::Int { val, .. } => Ok(val.to_string()),
Value::Float { val, .. } => Ok(val.to_string()),
Value::String { val, .. } => Ok(val.to_string()),
Value::Glob { val, .. } => Ok(val.to_string()),
Value::Filesize { val, .. } => Ok(val.get().to_string()),
Value::Duration { val, .. } => Ok(val.to_string()),
Value::Date { val, .. } => Ok(val.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)),
Value::Range { val, .. } => Ok(val.to_string()),
Value::Record { val, .. } => Ok(format!(
"{{{}}}",
val.iter()
.map(|(x, y)| match y.coerce_into_string_all() {
Ok(value) => format!("{x}: {value}"),
Err(err) => format!("Error: {err}"),
})
.collect::<Vec<_>>()
.join(", ")
)),
Value::List { vals, .. } => Ok(format!(
"[{}]",
vals.iter()
.map(|x| match x.coerce_into_string_all() {
Ok(value) => value,
Err(err) => format!("Error: {err}"),
})
.collect::<Vec<_>>()
.join(", ")
)),
Value::Binary { val, .. } => match String::from_utf8(val.to_vec()) {
Ok(s) => Ok(s),
Err(err) => Value::binary(err.into_bytes(), span).cant_convert_to("string"),
},
Value::CellPath { val, .. } => Ok(val.to_string()),
Value::Nothing { .. } => Ok("nothing".to_string()),
val => val.cant_convert_to("string"),
}
}
}
#[cfg(test)]
mod test {
#[test]

View File

@ -118,7 +118,7 @@ fn increase_string_width(text: &mut String, total: usize) {
let rest = total - width;
if rest > 0 {
text.extend(std::iter::repeat(' ').take(rest));
text.extend(std::iter::repeat_n(' ', rest));
}
}

View File

@ -272,6 +272,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
// Date
bind_command! {
Date,
DateFromHuman,
DateHumanize,
DateListTimezones,
DateNow,
@ -451,9 +452,18 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
JobSpawn,
JobList,
JobKill,
JobId,
JobTag,
Job,
};
#[cfg(not(target_family = "wasm"))]
bind_command! {
JobSend,
JobRecv,
JobFlush,
}
#[cfg(all(unix, feature = "os"))]
bind_command! {
JobUnfreeze,

View File

@ -1,7 +1,11 @@
use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
use nu_protocol::shell_error::io::IoError;
use nu_system::ForegroundChild;
#[cfg(feature = "os")]
use nu_protocol::process::PostWaitCallback;
#[derive(Clone)]
pub struct ConfigMeta;
@ -61,7 +65,6 @@ pub(super) fn start_editor(
) -> Result<PipelineData, ShellError> {
// Find the editor executable.
use nu_protocol::shell_error::io::IoError;
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
@ -119,8 +122,17 @@ pub(super) fn start_editor(
)
})?;
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
// Wrap the output into a `PipelineData::ByteStream`.
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head, None)?;
let child = nu_protocol::process::ChildProcess::new(
child,
None,
false,
call.head,
Some(post_wait_callback),
)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,

View File

@ -10,7 +10,7 @@ impl Command for Job {
fn signature(&self) -> Signature {
Signature::build("job")
.category(Category::Strings)
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::String)])
}

View File

@ -0,0 +1,58 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct JobFlush;
impl Command for JobFlush {
fn name(&self) -> &str {
"job flush"
}
fn description(&self) -> &str {
"Clear this job's mailbox."
}
fn extra_description(&self) -> &str {
r#"
This command removes all messages in the mailbox of the current job.
If a message is received while this command is executing, it may also be discarded.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job flush")
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec![]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut mailbox = engine_state
.current_job
.mailbox
.lock()
.expect("failed to acquire lock");
mailbox.clear();
Ok(Value::nothing(call.head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "job flush",
description: "Clear the mailbox of the current job.",
result: None,
}]
}
}

View File

@ -0,0 +1,50 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct JobId;
impl Command for JobId {
fn name(&self) -> &str {
"job id"
}
fn description(&self) -> &str {
"Get id of current job."
}
fn extra_description(&self) -> &str {
"This command returns the job id for the current background job.
The special id 0 indicates that this command was not called from a background job thread, and
was instead spawned by main nushell execution thread."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job id")
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::Int)])
}
fn search_terms(&self) -> Vec<&str> {
vec!["self", "this", "my-id", "this-id"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
Ok(Value::int(engine_state.current_job.id.get() as i64, head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "job id",
description: "Get id of current job",
result: None,
}]
}
}

View File

@ -37,7 +37,7 @@ impl Command for JobList {
let values = jobs
.iter()
.map(|(id, job)| {
let record = record! {
let mut record = record! {
"id" => Value::int(id.get() as i64, head),
"type" => match job {
Job::Thread(_) => Value::string("thread", head),
@ -52,12 +52,16 @@ impl Command for JobList {
head,
),
Job::Frozen(FrozenJob { unfreeze }) => {
Job::Frozen(FrozenJob { unfreeze, .. }) => {
Value::list(vec![ Value::int(unfreeze.pid() as i64, head) ], head)
}
}
},
};
if let Some(tag) = job.tag() {
record.push("tag", Value::string(tag, head));
}
Value::record(record, head)
})
.collect::<Vec<Value>>();

View File

@ -0,0 +1,181 @@
use std::{
sync::mpsc::{RecvTimeoutError, TryRecvError},
time::{Duration, Instant},
};
use nu_engine::command_prelude::*;
use nu_protocol::{
engine::{FilterTag, Mailbox},
Signals,
};
#[derive(Clone)]
pub struct JobRecv;
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
impl Command for JobRecv {
fn name(&self) -> &str {
"job recv"
}
fn description(&self) -> &str {
"Read a message from the mailbox."
}
fn extra_description(&self) -> &str {
r#"When messages are sent to the current process, they get stored in what is called the "mailbox".
This commands reads and returns a message from the mailbox, in a first-in-first-out fashion.
Messages may have numeric flags attached to them. This commands supports filtering out messages that do not satisfy a given tag, by using the `tag` flag.
If no tag is specified, this command will accept any message.
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
If a timeout duration of zero is specified, it will succeed only if there already is a message in the mailbox.
Note: When using par-each, only one thread at a time can utilize this command.
In the case of two or more threads running this command, they will wait until other threads are done using it,
in no particular order, regardless of the specified timeout parameter.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job recv")
.category(Category::Experimental)
.named("tag", SyntaxShape::Int, "A tag for the message", None)
.named(
"timeout",
SyntaxShape::Duration,
"The maximum time duration to wait for.",
None,
)
.input_output_types(vec![(Type::Nothing, Type::Any)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec!["receive"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
if let Some(tag) = tag_arg {
if tag.item < 0 {
return Err(ShellError::NeedsPositiveValue { span: tag.span });
}
}
let tag = tag_arg.map(|it| it.item as FilterTag);
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
let mut mailbox = engine_state
.current_job
.mailbox
.lock()
.expect("failed to acquire lock");
if let Some(timeout) = timeout {
if timeout == Duration::ZERO {
recv_instantly(&mut mailbox, tag, head)
} else {
recv_with_time_limit(&mut mailbox, tag, engine_state.signals(), head, timeout)
}
} else {
recv_without_time_limit(&mut mailbox, tag, engine_state.signals(), head)
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "job recv",
description: "Block the current thread while no message arrives",
result: None,
},
Example {
example: "job recv --timeout 10sec",
description: "Receive a message, wait for at most 10 seconds.",
result: None,
},
Example {
example: "job recv --timeout 0sec",
description: "Get a message or fail if no message is available immediately",
result: None,
},
]
}
}
fn recv_without_time_limit(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
signals: &Signals,
span: Span,
) -> Result<PipelineData, ShellError> {
loop {
if signals.interrupted() {
return Err(ShellError::Interrupted { span });
}
match mailbox.recv_timeout(tag, CTRL_C_CHECK_INTERVAL) {
Ok(value) => return Ok(value),
Err(RecvTimeoutError::Timeout) => {} // try again
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
}
}
}
fn recv_instantly(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
span: Span,
) -> Result<PipelineData, ShellError> {
match mailbox.try_recv(tag) {
Ok(value) => Ok(value),
Err(TryRecvError::Empty) => Err(ShellError::RecvTimeout { span }),
Err(TryRecvError::Disconnected) => Err(ShellError::Interrupted { span }),
}
}
fn recv_with_time_limit(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
signals: &Signals,
span: Span,
timeout: Duration,
) -> Result<PipelineData, ShellError> {
let deadline = Instant::now() + timeout;
loop {
if signals.interrupted() {
return Err(ShellError::Interrupted { span });
}
let time_until_deadline = deadline.saturating_duration_since(Instant::now());
let time_to_sleep = time_until_deadline.min(CTRL_C_CHECK_INTERVAL);
match mailbox.recv_timeout(tag, time_to_sleep) {
Ok(value) => return Ok(value),
Err(RecvTimeoutError::Timeout) => {} // try again
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
}
if time_until_deadline.is_zero() {
return Err(ShellError::RecvTimeout { span });
}
}
}

View File

@ -0,0 +1,112 @@
use nu_engine::command_prelude::*;
use nu_protocol::{engine::FilterTag, JobId};
#[derive(Clone)]
pub struct JobSend;
impl Command for JobSend {
fn name(&self) -> &str {
"job send"
}
fn description(&self) -> &str {
"Send a message to the mailbox of a job."
}
fn extra_description(&self) -> &str {
r#"
This command sends a message to a background job, which can then read sent messages
in a first-in-first-out fashion with `job recv`. When it does so, it may additionally specify a numeric filter tag,
in which case it will only read messages sent with the exact same filter tag.
In particular, the id 0 refers to the main/initial nushell thread.
A message can be any nushell value, and streams are always collected before being sent.
This command never blocks.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job send")
.category(Category::Experimental)
.required(
"id",
SyntaxShape::Int,
"The id of the job to send the message to.",
)
.named("tag", SyntaxShape::Int, "A tag for the message", None)
.input_output_types(vec![(Type::Any, Type::Nothing)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec![]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
let id = id_arg.item;
if id < 0 {
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
}
if let Some(tag) = tag_arg {
if tag.item < 0 {
return Err(ShellError::NeedsPositiveValue { span: tag.span });
}
}
let tag = tag_arg.map(|it| it.item as FilterTag);
if id == 0 {
engine_state
.root_job_sender
.send((tag, input))
.expect("this should NEVER happen.");
} else {
let jobs = engine_state.jobs.lock().expect("failed to acquire lock");
if let Some(job) = jobs.lookup(JobId::new(id as usize)) {
match job {
nu_protocol::engine::Job::Thread(thread_job) => {
// it is ok to send this value while holding the lock, because
// mail channels are always unbounded, so this send never blocks
let _ = thread_job.sender.send((tag, input));
}
nu_protocol::engine::Job::Frozen(_) => {
return Err(ShellError::JobIsFrozen {
id: id as usize,
span: id_arg.span,
});
}
}
} else {
return Err(ShellError::JobNotFound {
id: id as usize,
span: id_arg.span,
});
}
}
Ok(Value::nothing(head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
description: "Send a message to a newly spawned job",
result: None,
}]
}
}

View File

@ -1,15 +1,15 @@
use std::{
sync::{
atomic::{AtomicBool, AtomicU32},
Arc,
mpsc, Arc, Mutex,
},
thread,
};
use nu_engine::{command_prelude::*, ClosureEvalOnce};
use nu_protocol::{
engine::{Closure, Job, ThreadJob},
report_shell_error, Signals,
engine::{Closure, CurrentJob, Job, Mailbox, Redirection, ThreadJob},
report_shell_error, OutDest, Signals,
};
#[derive(Clone)]
@ -28,6 +28,12 @@ impl Command for JobSpawn {
Signature::build("job spawn")
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::Int)])
.named(
"tag",
SyntaxShape::String,
"An optional description tag for this job",
Some('t'),
)
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
@ -50,11 +56,12 @@ impl Command for JobSpawn {
let closure: Closure = call.req(engine_state, stack, 0)?;
let tag: Option<String> = call.get_flag(engine_state, stack, "tag")?;
let job_stack = stack.clone();
let mut job_state = engine_state.clone();
job_state.is_interactive = false;
let job_stack = stack.clone();
// the new job should have its ctrl-c independent of foreground
let job_signals = Signals::new(Arc::new(AtomicBool::new(false)));
job_state.set_signals(job_signals.clone());
@ -67,24 +74,37 @@ impl Command for JobSpawn {
let jobs = job_state.jobs.clone();
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
let (send, recv) = mpsc::channel();
let id = {
let thread_job = ThreadJob::new(job_signals);
job_state.current_thread_job = Some(thread_job.clone());
jobs.add_job(Job::Thread(thread_job))
let thread_job = ThreadJob::new(job_signals, tag, send);
let id = jobs.add_job(Job::Thread(thread_job.clone()));
job_state.current_job = CurrentJob {
id,
background_thread_job: Some(thread_job),
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
};
id
};
let result = thread::Builder::new()
.name(format!("background job {}", id.get()))
.spawn(move || {
ClosureEvalOnce::new(&job_state, &job_stack, closure)
let mut stack = job_stack.reset_pipes();
let stack = stack.push_redirection(
Some(Redirection::Pipe(OutDest::Null)),
Some(Redirection::Pipe(OutDest::Null)),
);
ClosureEvalOnce::new_preserve_out_dest(&job_state, &stack, closure)
.run_with_input(Value::nothing(head).into_pipeline_data())
.and_then(|data| data.into_value(head))
.and_then(|data| data.drain())
.unwrap_or_else(|err| {
if !job_state.signals().interrupted() {
report_shell_error(&job_state, &err);
}
Value::nothing(head)
});
{

View File

@ -0,0 +1,81 @@
use nu_engine::command_prelude::*;
use nu_protocol::JobId;
#[derive(Clone)]
pub struct JobTag;
impl Command for JobTag {
fn name(&self) -> &str {
"job tag"
}
fn description(&self) -> &str {
"Add a description tag to a background job."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job tag")
.category(Category::Experimental)
.required("id", SyntaxShape::Int, "The id of the job to tag.")
.required(
"tag",
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
"The tag to assign to the job.",
)
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
}
fn search_terms(&self) -> Vec<&str> {
vec!["describe", "desc"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
if id_arg.item < 0 {
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
}
let id: JobId = JobId::new(id_arg.item as usize);
let tag: Option<String> = call.req(engine_state, stack, 1)?;
let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!");
match jobs.lookup_mut(id) {
None => {
return Err(ShellError::JobNotFound {
id: id.get(),
span: head,
});
}
Some(job) => job.assign_tag(tag),
}
Ok(Value::nothing(head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "let id = job spawn { sleep 10sec }; job tag $id abc ",
description: "Tag a newly spawned job",
result: None,
},
Example {
example: "let id = job spawn { sleep 10sec }; job tag $id abc; job tag $id null",
description: "Remove the tag of a job",
result: None,
},
]
}
}

View File

@ -112,10 +112,13 @@ fn unfreeze_job(
span,
}),
Job::Frozen(FrozenJob { unfreeze: handle }) => {
Job::Frozen(FrozenJob {
unfreeze: handle,
tag,
}) => {
let pid = handle.pid();
if let Some(thread_job) = &state.current_thread_job {
if let Some(thread_job) = &state.current_thread_job() {
if !thread_job.try_add_pid(pid) {
kill_by_pid(pid.into()).map_err(|err| {
ShellError::Io(IoError::new_internal(
@ -133,7 +136,7 @@ fn unfreeze_job(
.then(|| state.pipeline_externals_state.clone()),
);
if let Some(thread_job) = &state.current_thread_job {
if let Some(thread_job) = &state.current_thread_job() {
thread_job.remove_pid(pid);
}
@ -141,8 +144,14 @@ fn unfreeze_job(
Ok(ForegroundWaitStatus::Frozen(handle)) => {
let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!");
jobs.add_job_with_id(old_id, Job::Frozen(FrozenJob { unfreeze: handle }))
.expect("job was supposed to be removed");
jobs.add_job_with_id(
old_id,
Job::Frozen(FrozenJob {
unfreeze: handle,
tag,
}),
)
.expect("job was supposed to be removed");
if state.is_interactive {
println!("\nJob {} is re-frozen", old_id.get());

View File

@ -1,18 +1,35 @@
mod is_admin;
mod job;
mod job_id;
mod job_kill;
mod job_list;
mod job_spawn;
mod job_tag;
#[cfg(all(unix, feature = "os"))]
mod job_unfreeze;
#[cfg(not(target_family = "wasm"))]
mod job_flush;
#[cfg(not(target_family = "wasm"))]
mod job_recv;
#[cfg(not(target_family = "wasm"))]
mod job_send;
pub use is_admin::IsAdmin;
pub use job::Job;
pub use job_id::JobId;
pub use job_kill::JobKill;
pub use job_list::JobList;
pub use job_spawn::JobSpawn;
pub use job_tag::JobTag;
#[cfg(not(target_family = "wasm"))]
pub use job_flush::JobFlush;
#[cfg(not(target_family = "wasm"))]
pub use job_recv::JobRecv;
#[cfg(not(target_family = "wasm"))]
pub use job_send::JobSend;
#[cfg(all(unix, feature = "os"))]
pub use job_unfreeze::JobUnfreeze;

View File

@ -132,7 +132,7 @@ impl Command for Cd {
stack.set_cwd(path)?;
Ok(PipelineData::empty())
}
PermissionResult::PermissionDenied(_) => {
PermissionResult::PermissionDenied => {
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
}
}

View File

@ -35,6 +35,11 @@ impl Command for Glob {
"Whether to filter out symlinks from the returned paths",
Some('S'),
)
.switch(
"follow-symlinks",
"Whether to follow symbolic links to their targets",
Some('l'),
)
.named(
"exclude",
SyntaxShape::List(Box::new(SyntaxShape::String)),
@ -111,6 +116,11 @@ impl Command for Glob {
example: r#"glob **/* --exclude [**/target/** **/.git/** */]"#,
result: None,
},
Example {
description: "Search for files following symbolic links to their targets",
example: r#"glob "**/*.txt" --follow-symlinks"#,
result: None,
},
]
}
@ -132,6 +142,7 @@ impl Command for Glob {
let no_dirs = call.has_flag(engine_state, stack, "no-dir")?;
let no_files = call.has_flag(engine_state, stack, "no-file")?;
let no_symlinks = call.has_flag(engine_state, stack, "no-symlink")?;
let follow_symlinks = call.has_flag(engine_state, stack, "follow-symlinks")?;
let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
@ -213,6 +224,11 @@ impl Command for Glob {
}
};
let link_behavior = match follow_symlinks {
true => wax::LinkBehavior::ReadTarget,
false => wax::LinkBehavior::ReadFile,
};
let result = if !not_patterns.is_empty() {
let np: Vec<&str> = not_patterns.iter().map(|s| s as &str).collect();
let glob_results = glob
@ -220,7 +236,7 @@ impl Command for Glob {
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
link: link_behavior,
},
)
.into_owned()
@ -247,7 +263,7 @@ impl Command for Glob {
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
link: link_behavior,
},
)
.into_owned()

View File

@ -378,10 +378,7 @@ fn ls_for_one_pattern(
.par_bridge()
.filter_map(move |x| match x {
Ok(path) => {
let metadata = match std::fs::symlink_metadata(&path) {
Ok(metadata) => Some(metadata),
Err(_) => None,
};
let metadata = std::fs::symlink_metadata(&path).ok();
let hidden_dir_clone = Arc::clone(&hidden_dirs);
let mut hidden_dir_mutex = hidden_dir_clone
.lock()

View File

@ -1,11 +1,15 @@
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
use nu_engine::{command_prelude::*, current_dir, eval_call};
use nu_protocol::{
ast,
debugger::{WithDebug, WithoutDebug},
shell_error::{self, io::IoError},
DataSource, NuGlob, PipelineMetadata,
};
use std::path::{Path, PathBuf};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
#[cfg(feature = "sqlite")]
use crate::database::SQLiteDatabase;
@ -30,7 +34,14 @@ impl Command for Open {
}
fn search_terms(&self) -> Vec<&str> {
vec!["load", "read", "load_file", "read_file"]
vec![
"load",
"read",
"load_file",
"read_file",
"cat",
"get-content",
]
}
fn signature(&self) -> nu_protocol::Signature {
@ -63,7 +74,6 @@ impl Command for Open {
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let eval_block = get_eval_block(engine_state);
if paths.is_empty() && !call.has_positional_args(stack, 0) {
// try to use path from pipeline input if there were no positional or spread args
@ -192,13 +202,16 @@ impl Command for Open {
match converter {
Some((converter_id, ext)) => {
let decl = engine_state.get_decl(converter_id);
let command_output = if let Some(block_id) = decl.block_id() {
let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, stream)
let open_call = ast::Call {
decl_id: converter_id,
head: call_span,
arguments: vec![],
parser_info: HashMap::new(),
};
let command_output = if engine_state.is_debugging() {
eval_call::<WithDebug>(engine_state, stack, &open_call, stream)
} else {
let call = ast::Call::new(call_span);
decl.run(engine_state, stack, &(&call).into(), stream)
eval_call::<WithoutDebug>(engine_state, stack, &open_call, stream)
};
output.push(command_output.map_err(|inner| {
ShellError::GenericError{
@ -268,6 +281,16 @@ impl Command for Open {
example: r#"def "from ndjson" [] { from json -o }; open myfile.ndjson"#,
result: None,
},
Example {
description: "Show the extensions for which the `open` command will automatically parse",
example: r#"scope commands
| where name starts-with "from "
| insert extension { get name | str replace -r "^from " "" | $"*.($in)" }
| select extension name
| rename extension command
"#,
result: None,
}
]
}
}

View File

@ -10,11 +10,7 @@ use nu_protocol::{
};
#[cfg(unix)]
use std::os::unix::prelude::FileTypeExt;
use std::{
collections::HashMap,
io::{Error, ErrorKind},
path::PathBuf,
};
use std::{collections::HashMap, io::Error, path::PathBuf};
const TRASH_SUPPORTED: bool = cfg!(all(
feature = "trash-support",
@ -379,7 +375,7 @@ fn rm(
);
let result = if let Err(e) = interaction {
Err(Error::new(ErrorKind::Other, &*e.to_string()))
Err(Error::other(&*e.to_string()))
} else if interactive && !confirmed {
Ok(())
} else if TRASH_SUPPORTED && (trash || (rm_always_trash && !permanent)) {
@ -389,7 +385,7 @@ fn rm(
))]
{
trash::delete(&f).map_err(|e: trash::Error| {
Error::new(ErrorKind::Other, format!("{e:?}\nTry '--permanent' flag"))
Error::other(format!("{e:?}\nTry '--permanent' flag"))
})
}

View File

@ -263,6 +263,17 @@ impl Command for Save {
example: r#"do -i {} | save foo.txt --stderr bar.txt"#,
result: None,
},
Example {
description:
"Show the extensions for which the `save` command will automatically serialize",
example: r#"scope commands
| where name starts-with "to "
| insert extension { get name | str replace -r "^to " "" | $"*.($in)" }
| select extension name
| rename extension command
"#,
result: None,
},
]
}

View File

@ -158,17 +158,15 @@ impl Command for UTouch {
continue;
}
let mut expanded_globs = glob(
&file_path.to_string_lossy(),
Some(engine_state.signals().clone()),
)
.unwrap_or_else(|_| {
panic!(
"Failed to process file path: {}",
&file_path.to_string_lossy()
)
})
.peekable();
let mut expanded_globs =
glob(&file_path.to_string_lossy(), engine_state.signals().clone())
.unwrap_or_else(|_| {
panic!(
"Failed to process file path: {}",
&file_path.to_string_lossy()
)
})
.peekable();
if expanded_globs.peek().is_none() {
let file_name = file_path.file_name().unwrap_or_else(|| {

View File

@ -30,6 +30,11 @@ impl Command for All {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Check if a list contains only true values",
example: "[false true true false] | all {}",
result: Some(Value::test_bool(false)),
},
Example {
description: "Check if each row's status is the string 'UP'",
example: "[[status]; [UP] [UP]] | all {|el| $el.status == UP }",

View File

@ -30,6 +30,11 @@ impl Command for Any {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Check if a list contains any true values",
example: "[false true true false] | any {}",
result: Some(Value::test_bool(true)),
},
Example {
description: "Check if any row's status is the string 'DOWN'",
example: "[[status]; [UP] [DOWN] [UP]] | any {|el| $el.status == DOWN }",

View File

@ -243,7 +243,7 @@ mod test {
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
assert_eq!(
chunks,
[s[..4].as_bytes(), s[4..8].as_bytes(), s[8..].as_bytes()]
[&s.as_bytes()[..4], &s.as_bytes()[4..8], &s.as_bytes()[8..]]
);
}
@ -260,7 +260,7 @@ mod test {
let chunks = chunk_read.map(|e| e.unwrap()).collect::<Vec<_>>();
assert_eq!(
chunks,
[s[..4].as_bytes(), s[4..8].as_bytes(), s[8..].as_bytes()]
[&s.as_bytes()[..4], &s.as_bytes()[4..8], &s.as_bytes()[8..]]
);
}

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::{ListStream, Signals};
#[derive(Clone)]
pub struct Default;
@ -146,6 +147,20 @@ fn default(
&& matches!(input, PipelineData::Value(ref value, _) if value.is_empty()))
{
Ok(value.into_pipeline_data())
} else if default_when_empty && matches!(input, PipelineData::ListStream(..)) {
let PipelineData::ListStream(ls, metadata) = input else {
unreachable!()
};
let span = ls.span();
let mut stream = ls.into_inner().peekable();
if stream.peek().is_none() {
return Ok(value.into_pipeline_data());
}
// stream's internal state already preserves the original signals config, so if this
// Signals::empty list stream gets interrupted it will be caught by the underlying iterator
let ls = ListStream::new(stream, span, Signals::empty());
Ok(PipelineData::ListStream(ls, metadata))
} else {
Ok(input)
}

View File

@ -15,17 +15,8 @@ pub fn empty(
if !columns.is_empty() {
for val in input {
for column in &columns {
let val = val.clone();
match val.follow_cell_path(&column.members, false) {
Ok(Value::Nothing { .. }) => {}
Ok(_) => {
if negate {
return Ok(Value::bool(true, head).into_pipeline_data());
} else {
return Ok(Value::bool(false, head).into_pipeline_data());
}
}
Err(err) => return Err(err),
if !val.follow_cell_path(&column.members, false)?.is_nothing() {
return Ok(Value::bool(negate, head).into_pipeline_data());
}
}
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::command_prelude::*;
use nu_protocol::{ast::PathMember, Signals};
@ -188,9 +190,11 @@ fn action(
let input = input.into_value(span)?;
for path in paths {
let val = input.clone().follow_cell_path(&path.members, !sensitive);
output.push(val?);
output.push(
input
.follow_cell_path(&path.members, !sensitive)?
.into_owned(),
);
}
Ok(output.into_iter().into_pipeline_data(span, signals))
@ -223,10 +227,10 @@ pub fn follow_cell_path_into_stream(
.map(move |value| {
let span = value.span();
match value.follow_cell_path(&cell_path, insensitive) {
Ok(v) => v,
Err(error) => Value::error(error, span),
}
value
.follow_cell_path(&cell_path, insensitive)
.map(Cow::into_owned)
.unwrap_or_else(|error| Value::error(error, span))
})
.into_pipeline_data(head, signals);

View File

@ -322,11 +322,9 @@ fn group_cell_path(
let mut groups = IndexMap::<_, Vec<_>>::new();
for value in values.into_iter() {
let key = value
.clone()
.follow_cell_path(&column_name.members, false)?;
let key = value.follow_cell_path(&column_name.members, false)?;
if matches!(key, Value::Nothing { .. }) {
if key.is_nothing() {
continue; // likely the result of a failed optional access, ignore this value
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::ast::PathMember;
@ -299,8 +301,8 @@ fn insert_value_by_closure(
) -> Result<(), ShellError> {
let value_at_path = if first_path_member_int {
value
.clone()
.follow_cell_path(cell_path, false)
.map(Cow::into_owned)
.unwrap_or(Value::nothing(span))
} else {
value.clone()
@ -319,8 +321,8 @@ fn insert_single_value_by_closure(
) -> Result<(), ShellError> {
let value_at_path = if first_path_member_int {
value
.clone()
.follow_cell_path(cell_path, false)
.map(Cow::into_owned)
.unwrap_or(Value::nothing(span))
} else {
value.clone()

View File

@ -255,6 +255,16 @@ fn join_rows(
config: &Config,
span: Span,
) {
if !this
.iter()
.any(|this_record| match this_record.as_record() {
Ok(record) => record.contains(this_join_key),
Err(_) => false,
})
{
// `this` table does not contain the join column; do nothing
return;
}
for this_row in this {
if let Value::Record {
val: this_record, ..
@ -281,39 +291,40 @@ fn join_rows(
result.push(Value::record(record, span))
}
}
} else if !matches!(join_type, JoinType::Inner) {
// `other` table did not contain any rows matching
// `this` row on the join column; emit a single joined
// row with null values for columns not present,
let other_record = other_keys
.iter()
.map(|&key| {
let val = if Some(key.as_ref()) == shared_join_key {
this_record
.get(key)
.cloned()
.unwrap_or_else(|| Value::nothing(span))
} else {
Value::nothing(span)
};
(key.clone(), val)
})
.collect();
let record = match join_type {
JoinType::Inner | JoinType::Right => {
merge_records(&other_record, this_record, shared_join_key)
}
JoinType::Left => {
merge_records(this_record, &other_record, shared_join_key)
}
_ => panic!("not implemented"),
};
result.push(Value::record(record, span))
continue;
}
} // else { a row is missing a value for the join column }
}
if !matches!(join_type, JoinType::Inner) {
// Either `this` row is missing a value for the join column or
// `other` table did not contain any rows matching
// `this` row on the join column; emit a single joined
// row with null values for columns not present
let other_record = other_keys
.iter()
.map(|&key| {
let val = if Some(key.as_ref()) == shared_join_key {
this_record
.get(key)
.cloned()
.unwrap_or_else(|| Value::nothing(span))
} else {
Value::nothing(span)
};
(key.clone(), val)
})
.collect();
let record = match join_type {
JoinType::Inner | JoinType::Right => {
merge_records(&other_record, this_record, shared_join_key)
}
JoinType::Left => merge_records(this_record, &other_record, shared_join_key),
_ => panic!("not implemented"),
};
result.push(Value::record(record, span))
}
};
}
}

View File

@ -42,8 +42,9 @@ pub(crate) fn typecheck_merge(lhs: &Value, rhs: &Value, head: Span) -> Result<()
match (lhs.get_type(), rhs.get_type()) {
(Type::Record { .. }, Type::Record { .. }) => Ok(()),
(_, _) if is_list_of_records(lhs) && is_list_of_records(rhs) => Ok(()),
_ => Err(ShellError::PipelineMismatch {
other => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "input and argument to be both record or both table".to_string(),
wrong_type: format!("{} and {}", other.0, other.1).to_string(),
dst_span: head,
src_span: lhs.span(),
}),

View File

@ -174,8 +174,9 @@ impl Command for Move {
PipelineData::Value(Value::Record { val, .. }, ..) => {
Ok(move_record_columns(&val, &columns, &location, head)?.into_pipeline_data())
}
_ => Err(ShellError::PipelineMismatch {
other => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record or table".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: Span::new(head.start, head.start),
}),

View File

@ -229,45 +229,37 @@ fn select(
match v {
Value::List {
vals: input_vals, ..
} => {
Ok(input_vals
.into_iter()
.map(move |input_val| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher);
}
Err(e) => return Value::error(e, call_span),
} => Ok(input_vals
.into_iter()
.map(move |input_val| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
match input_val.follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher.into_owned());
}
Err(e) => return Value::error(e, call_span),
}
Value::record(record, span)
} else {
input_val.clone()
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
Value::record(record, span)
} else {
input_val.clone()
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
)),
_ => {
if !columns.is_empty() {
let mut record = Record::new();
for cell_path in columns {
// FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) {
Ok(result) => {
record.push(cell_path.to_column_name(), result);
}
Err(e) => return Err(e),
}
let result = v.follow_cell_path(&cell_path.members, false)?;
record.push(cell_path.to_column_name(), result.into_owned());
}
Ok(Value::record(record, call_span)
@ -278,31 +270,24 @@ fn select(
}
}
}
PipelineData::ListStream(stream, metadata, ..) => {
Ok(stream
.map(move |x| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value);
}
Err(e) => return Value::error(e, call_span),
PipelineData::ListStream(stream, metadata, ..) => Ok(stream
.map(move |x| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
match x.follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value.into_owned());
}
Err(e) => return Value::error(e, call_span),
}
Value::record(record, call_span)
} else {
x
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
Value::record(record, call_span)
} else {
x
}
})
.into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)),
_ => Ok(PipelineData::empty()),
}
}

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use rand::{prelude::SliceRandom, thread_rng};
use rand::{prelude::SliceRandom, rng};
#[derive(Clone)]
pub struct Shuffle;
@ -31,7 +31,7 @@ impl Command for Shuffle {
) -> Result<PipelineData, ShellError> {
let metadata = input.metadata();
let mut values = input.into_iter_strict(call.head)?.collect::<Vec<_>>();
values.shuffle(&mut thread_rng());
values.shuffle(&mut rng());
let iter = values.into_iter();
Ok(iter.into_pipeline_data_with_metadata(
call.head,

View File

@ -184,9 +184,10 @@ impl Command for Sort {
dst_span: value.span(),
})
}
_ => {
return Err(ShellError::PipelineMismatch {
ref other => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record or list".to_string(),
wrong_type: other.get_type().to_string(),
dst_span: call.head,
src_span: value.span(),
})

View File

@ -394,7 +394,7 @@ impl<R: Read> Read for IoTee<R> {
if let Some(thread) = self.thread.take() {
if thread.is_finished() {
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
return Err(io::Error::new(io::ErrorKind::Other, err));
return Err(io::Error::other(err));
}
} else {
self.thread = Some(thread)
@ -405,7 +405,7 @@ impl<R: Read> Read for IoTee<R> {
self.sender = None;
if let Some(thread) = self.thread.take() {
if let Err(err) = thread.join().unwrap_or_else(|_| Err(panic_error())) {
return Err(io::Error::new(io::ErrorKind::Other, err));
return Err(io::Error::other(err));
}
}
} else if let Some(sender) = self.sender.as_mut() {

View File

@ -243,17 +243,17 @@ fn update_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
let value_at_path = value.follow_cell_path(cell_path, false)?;
let arg = if first_path_member_int {
&value_at_path
value_at_path.as_ref()
} else {
&*value
};
let new_value = closure
.add_arg(arg.clone())
.run_with_input(value_at_path.into_pipeline_data())?
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
.into_value(span)?;
value.update_data_at_cell_path(cell_path, new_value)
@ -266,17 +266,17 @@ fn update_single_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
let value_at_path = value.follow_cell_path(cell_path, false)?;
let arg = if first_path_member_int {
&value_at_path
value_at_path.as_ref()
} else {
&*value
};
let new_value = closure
.add_arg(arg.clone())
.run_with_input(value_at_path.into_pipeline_data())?
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
.into_value(span)?;
value.update_data_at_cell_path(cell_path, new_value)

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::ast::PathMember;
@ -319,15 +321,19 @@ fn upsert_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false);
let value_at_path = value.follow_cell_path(cell_path, false);
let arg = if first_path_member_int {
value_at_path.clone().unwrap_or(Value::nothing(span))
value_at_path
.as_deref()
.cloned()
.unwrap_or(Value::nothing(span))
} else {
value.clone()
};
let input = value_at_path
.map(Cow::into_owned)
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);
@ -346,15 +352,19 @@ fn upsert_single_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false);
let value_at_path = value.follow_cell_path(cell_path, false);
let arg = if first_path_member_int {
value_at_path.clone().unwrap_or(Value::nothing(span))
value_at_path
.as_deref()
.cloned()
.unwrap_or(Value::nothing(span))
} else {
value.clone()
};
let input = value_at_path
.map(Cow::into_owned)
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);

View File

@ -36,13 +36,13 @@ impl Command for ToMd {
Example {
description: "Outputs an MD string representing the contents of this table",
example: "[[foo bar]; [1 2]] | to md",
result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|\n")),
result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|")),
},
Example {
description: "Optionally, output a formatted markdown string",
example: "[[foo bar]; [1 2]] | to md --pretty",
result: Some(Value::test_string(
"| foo | bar |\n| --- | --- |\n| 1 | 2 |\n",
"| foo | bar |\n| --- | --- |\n| 1 | 2 |",
)),
},
Example {
@ -57,6 +57,13 @@ impl Command for ToMd {
example: "[0 1 2] | to md --pretty",
result: Some(Value::test_string("0\n1\n2")),
},
Example {
description: "Separate list into markdown tables",
example: "[ {foo: 1, bar: 2} {foo: 3, bar: 4} {foo: 5}] | to md --per-element",
result: Some(Value::test_string(
"|foo|bar|\n|-|-|\n|1|2|\n|3|4|\n|foo|\n|-|\n|5|",
)),
},
]
}
@ -94,11 +101,14 @@ fn to_md(
grouped_input
.into_iter()
.map(move |val| match val {
Value::List { .. } => table(val.into_pipeline_data(), pretty, config),
Value::List { .. } => {
format!("{}\n", table(val.into_pipeline_data(), pretty, config))
}
other => fragment(other, pretty, config),
})
.collect::<Vec<String>>()
.join(""),
.join("")
.trim(),
head,
)
.into_pipeline_data_with_metadata(Some(metadata)));
@ -152,7 +162,13 @@ fn collect_headers(headers: &[String]) -> (Vec<String>, Vec<usize>) {
}
fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
let vec_of_values = input.into_iter().collect::<Vec<Value>>();
let vec_of_values = input
.into_iter()
.flat_map(|val| match val {
Value::List { vals, .. } => vals,
other => vec![other],
})
.collect::<Vec<Value>>();
let mut headers = merge_descriptors(&vec_of_values);
let mut empty_header_index = 0;
@ -464,6 +480,39 @@ mod tests {
);
}
#[test]
fn test_empty_row_value() {
let value = Value::test_list(vec![
Value::test_record(record! {
"foo" => Value::test_string("1"),
"bar" => Value::test_string("2"),
}),
Value::test_record(record! {
"foo" => Value::test_string("3"),
"bar" => Value::test_string("4"),
}),
Value::test_record(record! {
"foo" => Value::test_string("5"),
"bar" => Value::test_string(""),
}),
]);
assert_eq!(
table(
value.clone().into_pipeline_data(),
false,
&Config::default()
),
one(r#"
|foo|bar|
|-|-|
|1|2|
|3|4|
|5||
"#)
);
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());

View File

@ -1,3 +1,4 @@
use chrono::Datelike;
use chrono_humanize::HumanTime;
use nu_engine::command_prelude::*;
use nu_protocol::{format_duration, shell_error::io::IoError, ByteStream, PipelineMetadata};
@ -167,7 +168,17 @@ fn local_into_string(
Value::Filesize { val, .. } => val.to_string(),
Value::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => {
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
format!(
"{} ({})",
{
if val.year() >= 0 {
val.to_rfc2822()
} else {
val.to_rfc3339()
}
},
HumanTime::from(val)
)
}
Value::Range { val, .. } => val.to_string(),
Value::String { val, .. } => val,

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
#[derive(Clone)]
@ -21,6 +22,7 @@ impl Command for MathAbs {
Type::List(Box::new(Type::Duration)),
Type::List(Box::new(Type::Duration)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.category(Category::Math)
@ -46,6 +48,16 @@ impl Command for MathAbs {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(move |value| abs_helper(value, head), engine_state.signals())
}
@ -56,6 +68,16 @@ impl Command for MathAbs {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| abs_helper(value, head),
working_set.permanent().signals(),

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
#[derive(Clone)]
@ -16,6 +17,7 @@ impl Command for MathCeil {
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Int)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.category(Category::Math)
@ -45,6 +47,16 @@ impl Command for MathCeil {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(move |value| operate(value, head), engine_state.signals())
}
@ -59,6 +71,16 @@ impl Command for MathCeil {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| operate(value, head),
working_set.permanent().signals(),

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
#[derive(Clone)]
@ -16,6 +17,7 @@ impl Command for MathFloor {
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Int)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.category(Category::Math)
@ -45,6 +47,16 @@ impl Command for MathFloor {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(move |value| operate(value, head), engine_state.signals())
}
@ -59,6 +71,16 @@ impl Command for MathFloor {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| operate(value, head),
working_set.permanent().signals(),

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
use nu_protocol::Signals;
@ -22,6 +23,7 @@ impl Command for MathLog {
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.category(Category::Math)
@ -46,7 +48,18 @@ impl Command for MathLog {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let base: Spanned<f64> = call.req(engine_state, stack, 0)?;
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
log(base, call.head, input, engine_state.signals())
}
@ -56,7 +69,18 @@ impl Command for MathLog {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let base: Spanned<f64> = call.req_const(working_set, 0)?;
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
log(base, call.head, input, working_set.permanent().signals())
}

View File

@ -110,7 +110,7 @@ impl Command for MathMode {
}
}
pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellError> {
pub fn mode(values: &[Value], span: Span, head: Span) -> Result<Value, ShellError> {
//In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside
// But f64 doesn't implement Hash, so we get the binary representation to use as
// key in the HashMap
@ -130,11 +130,11 @@ pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellErr
NumberTypes::Filesize,
)),
Value::Error { error, .. } => Err(*error.clone()),
other => Err(ShellError::UnsupportedInput {
_ => Err(ShellError::UnsupportedInput {
msg: "Unable to give a result with this input".to_string(),
input: "value originates from here".into(),
msg_span: head,
input_span: other.span(),
input_span: span,
}),
})
.collect::<Result<Vec<HashableType>, ShellError>>()?;

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
#[derive(Clone)]
@ -16,6 +17,7 @@ impl Command for MathRound {
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Number)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.named(
@ -52,6 +54,16 @@ impl Command for MathRound {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| operate(value, head, precision_param),
engine_state.signals(),
@ -70,6 +82,16 @@ impl Command for MathRound {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| operate(value, head, precision_param),
working_set.permanent().signals(),

View File

@ -1,3 +1,4 @@
use crate::math::utils::ensure_bounded;
use nu_engine::command_prelude::*;
#[derive(Clone)]
@ -16,6 +17,7 @@ impl Command for MathSqrt {
Type::List(Box::new(Type::Number)),
Type::List(Box::new(Type::Float)),
),
(Type::Range, Type::List(Box::new(Type::Number))),
])
.allow_variants_without_examples(true)
.category(Category::Math)
@ -45,6 +47,16 @@ impl Command for MathSqrt {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(move |value| operate(value, head), engine_state.signals())
}
@ -59,6 +71,16 @@ impl Command for MathSqrt {
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });
}
if let PipelineData::Value(
Value::Range {
ref val,
internal_span,
},
..,
) = input
{
ensure_bounded(val.as_ref(), internal_span, head)?;
}
input.map(
move |value| operate(value, head),
working_set.permanent().signals(),

View File

@ -14,6 +14,7 @@ impl Command for MathStddev {
Signature::build("math stddev")
.input_output_types(vec![
(Type::List(Box::new(Type::Number)), Type::Number),
(Type::Range, Type::Number),
(Type::table(), Type::record()),
(Type::record(), Type::record()),
])
@ -53,6 +54,18 @@ impl Command for MathStddev {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let sample = call.has_flag(engine_state, stack, "sample")?;
let name = call.head;
let span = input.span().unwrap_or(name);
let input: PipelineData = match input.try_expand_range() {
Err(_) => {
return Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span: span,
call_span: name,
});
}
Ok(val) => val,
};
run_with_function(call, input, compute_stddev(sample))
}
@ -63,6 +76,18 @@ impl Command for MathStddev {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let sample = call.has_flag_const(working_set, "sample")?;
let name = call.head;
let span = input.span().unwrap_or(name);
let input: PipelineData = match input.try_expand_range() {
Err(_) => {
return Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span: span,
call_span: name,
});
}
Ok(val) => val,
};
run_with_function(call, input, compute_stddev(sample))
}

View File

@ -1,4 +1,4 @@
use core::{ops::Bound, slice};
use core::slice;
use indexmap::IndexMap;
use nu_protocol::{
engine::Call, IntoPipelineData, PipelineData, Range, ShellError, Signals, Span, Value,
@ -93,10 +93,7 @@ pub fn calculate(
Ok(Value::record(record, span))
}
PipelineData::Value(Value::Range { val, .. }, ..) => {
match *val {
Range::IntRange(range) => ensure_bounded(range.end(), span, name)?,
Range::FloatRange(range) => ensure_bounded(range.end(), span, name)?,
}
ensure_bounded(val.as_ref(), span, name)?;
let new_vals: Result<Vec<Value>, ShellError> = val
.into_range_iter(span, Signals::empty())
.map(|val| mf(&[val], span, name))
@ -105,7 +102,7 @@ pub fn calculate(
mf(&new_vals?, span, name)
}
PipelineData::Value(val, ..) => mf(&[val], span, name),
PipelineData::Empty { .. } => Err(ShellError::PipelineEmpty { dst_span: name }),
PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: name }),
val => Err(ShellError::UnsupportedInput {
msg: "Only ints, floats, lists, records, or ranges are supported".into(),
input: "value originates from here".into(),
@ -117,17 +114,13 @@ pub fn calculate(
}
}
pub fn ensure_bounded<T>(
bound: Bound<T>,
val_span: Span,
call_span: Span,
) -> Result<(), ShellError> {
match bound {
Bound::<T>::Unbounded => Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span,
call_span,
}),
_ => Ok(()),
pub fn ensure_bounded(range: &Range, val_span: Span, call_span: Span) -> Result<(), ShellError> {
if range.is_bounded() {
return Ok(());
}
Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span,
call_span,
})
}

View File

@ -13,6 +13,7 @@ impl Command for MathVariance {
Signature::build("math variance")
.input_output_types(vec![
(Type::List(Box::new(Type::Number)), Type::Number),
(Type::Range, Type::Number),
(Type::table(), Type::record()),
(Type::record(), Type::record()),
])
@ -45,6 +46,18 @@ impl Command for MathVariance {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let sample = call.has_flag(engine_state, stack, "sample")?;
let name = call.head;
let span = input.span().unwrap_or(name);
let input: PipelineData = match input.try_expand_range() {
Err(_) => {
return Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span: span,
call_span: name,
});
}
Ok(val) => val,
};
run_with_function(call, input, compute_variance(sample))
}
@ -55,6 +68,18 @@ impl Command for MathVariance {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let sample = call.has_flag_const(working_set, "sample")?;
let name = call.head;
let span = input.span().unwrap_or(name);
let input: PipelineData = match input.try_expand_range() {
Err(_) => {
return Err(ShellError::IncorrectValue {
msg: "Range must be bounded".to_string(),
val_span: span,
call_span: name,
});
}
Ok(val) => val,
};
run_with_function(call, input, compute_variance(sample))
}

View File

@ -723,7 +723,7 @@ fn transform_response_using_content_type(
)
})?
.path_segments()
.and_then(|segments| segments.last())
.and_then(|mut segments| segments.next_back())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.and_then(|name| {
PathBuf::from(name)

View File

@ -117,8 +117,13 @@ impl Command for HttpDelete {
result: None,
},
Example {
description: "http delete from example.com, with custom header",
example: "http delete --headers [my-header-key my-header-value] https://www.example.com",
description: "http delete from example.com, with custom header using a record",
example: "http delete --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "http delete from example.com, with custom header using a list",
example: "http delete --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
Example {

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