Compare commits

...

159 Commits

Author SHA1 Message Date
98521100ed Inline actions-rust-lang/setup-rust-toolchain with main branch 2025-05-23 21:44:17 +08:00
985211a924 Use nushell's fork for winget-pkgs publishing (#15808)
<!--
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.
-->

Try to use nushell's fork for winget-pkgs publishing
2025-05-23 21:09:42 +08:00
a3388b00ae Fix Windows arm64 release binaries and winget related issues (#15690)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
Publishing Nushell to winget has always been a challenge for us, and to
this day, many issues remain unresolved—and some seem almost impossible
to fix. The road to solving these problems may be winding and long, but
it's time for us to set out on this journey.

This PR try to fix the Windows arm64 release binaries and some `winget`
related issues:

- [x] Fixes https://github.com/nushell/nushell/issues/14815: build
Windows arm64 binaries by Windows arm64 runner
- [x] Upgrade WiX Toolset to latest 6.0 version: WiX 3 we used currently
doesn't support arm64 arch and [WiX v4 Security Fixes End Date is
2025/02/05](https://docs.firegiant.com/wix/)
- [x] Update the **nightly** workflow to make it work for all future
releases
- [x] Update the **release** workflow to make it work for all future
releases
- [x] Fixes https://github.com/nushell/nushell/issues/15698
- [x] Fixes https://github.com/nushell/nushell/issues/13719 so that
Nushell should be possible to be installed via winget with both user and
machine scope
- [x] Fixes https://github.com/nushell/nushell/issues/5927
- [x] Try to fix https://github.com/nushell/nushell/issues/14786
- [x] Fixes https://github.com/nushell/nushell/issues/9537

- Related https://github.com/nushell/nushell/issues/13017
- Related https://github.com/nushell/nushell/issues/8053

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

- Nushell should be possible to be installed via winget with both user
and machine scope and The default should be user scope
  - User scope install by winget: `winget install Nushell.Nushell`
- User scope install by msiexec: `msiexec /i
nu-0.104.1-x86_64-pc-windows-msvc.msi /quiet /qn`
- Machine scope install by winget: `winget install Nushell.Nushell
--override 'ALLUSERS=1'`
- Machine scope install by msiexec: `msiexec /i
nu-0.104.1-x86_64-pc-windows-msvc.msi ALLUSERS=1`
- Note that `--scope` flag for `winget install` does not work use
`--override` instead
- Default user scope install dir:
`$'($nu.home-path)\AppData\Local\Programs\nu\'`
  - Default machine scope install dir: `C:\Program Files\nu\`
- When a standard user runs the installer and selects "Install for
everyone" (per-machine installation), Windows will automatically trigger
a UAC prompt, the user can enter admin credentials and the installation
will proceed with elevated privileges
- [hustcer/setup-nu](https://github.com/hustcer/setup-nu) should work
for `windows-11-arm` runners

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

The latest MSI builds are available here:
https://github.com/nushell/nightly/releases/tag/v0.104.1
Actually all the nightly releases were built with latest changes
included: https://github.com/nushell/nightly/releases

`winget` and `msiexec` install tests goes here:
https://github.com/nushell/integrations/pull/49

https://github.com/nushell/integrations/actions/runs/14974621061
https://github.com/nushell/integrations/actions/runs/14974621054

- git clone git@github.com:nushell/integrations.git
- User scope install: `winget install -m
manifests\n\Nushell\Nushell\0.104.1\`
- Run: `use tests\common.nu *; check-user-install`
- Machine scope install: `winget install -m
manifests\n\Nushell\Nushell\0.104.1\ --override 'ALLUSERS=1'`
- Run: `use tests\common.nu *; check-local-machine-install`

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

@fdncred I suggest releasing a patch version after merging this PR (only
the changes of this PR will be included) to ensure that the winget
release process works properly. This way, we can be more confident when
releasing version 0.105.0.

References:

-
https://learn.microsoft.com/en-us/windows/win32/msi/single-package-authoring
-
https://learn.microsoft.com/en-us/windows/package-manager/winget/source#add
-
https://github.com/microsoft/winget-pkgs/blob/master/doc/tools/SandboxTest.md
- https://docs.firegiant.com/quick-start/
-
https://docs.firegiant.com/wix3/tutorial/getting-started/putting-it-to-use/#_top
-
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#set-public-properties
2025-05-22 23:08:16 +08:00
eadb8da9f7 Bump to 0.104.1 dev version (#15669)
Marks development or hotfix
2025-04-29 23:33:10 -04:00
cda15d91dd Bump version for 0.104.0 release (#15664) 2025-04-29 19:31:45 -04:00
651a8716fb Pin reedline to 0.40 for 0.104 release (#15663) 2025-04-29 16:32:18 -04:00
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
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
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
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
12ccaf5e33 Update Nu to 0.103.0 for release workflow and improve Windows OS checks (#15625) 2025-04-27 17:44:16 +02:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
7b57f132bb Bump crossbeam-channel (#15541)
Resolves https://rustsec.org/advisories/RUSTSEC-2025-0024.html
2025-04-10 17:07:42 +02:00
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
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
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
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
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
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
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
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
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
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
ecb9799b6a Fix future clippy lints (#15519)
- suggestions for tersity using helpers
2025-04-08 08:51:12 +08:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
0cd90e2388 Fix #15394 for table -e wrapping issue (#15407)
close #15394
cc @fdncred
2025-04-05 08:26:50 -05:00
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
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
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
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
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
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
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
d7f26b177a build(deps): bump crate-ci/typos from 1.31.0 to 1.31.1 (#15469) 2025-04-02 08:59:12 +08:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
1979b61a92 build(deps): bump tokio from 1.43.0 to 1.44.1 (#15419) 2025-03-26 14:12:42 +00:00
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
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
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
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
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
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
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
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
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
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
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
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
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
b241e9edd5 build(deps): bump mockito from 1.6.1 to 1.7.0 (#15343) 2025-03-20 15:51:22 +00:00
946cef77f1 build(deps): bump uuid from 1.12.0 to 1.16.0 (#15346) 2025-03-20 15:46:25 +00:00
c99c8119fe build(deps): bump indexmap from 2.7.0 to 2.8.0 (#15345) 2025-03-20 15:45:58 +00:00
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
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
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
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
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
968eb45fb2 Don't collect job output (#15365)
# Description

Fixes #15359.

# User-Facing Changes

Bug fix.
2025-03-20 09:49:12 -04:00
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
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
2c7ab6e898 Bump to 0.103.1 dev version (#15347)
# Description

Marks development or hotfix
2025-03-19 00:12:01 -04:00
325 changed files with 13014 additions and 4652 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

@ -4,6 +4,7 @@
# 2. https://github.com/JasonEtco/create-an-issue
# 3. https://docs.github.com/en/actions/learn-github-actions/variables
# 4. https://github.com/actions/github-script
# 5. https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
#
name: Nightly Build
@ -14,6 +15,7 @@ on:
# This schedule will run only from the default branch
schedule:
- cron: '15 0 * * *' # run at 00:15 AM UTC
workflow_dispatch:
defaults:
run:
@ -25,6 +27,11 @@ jobs:
runs-on: ubuntu-latest
# This job is required by the release job, so we should make it run both from Nushell repo and nightly repo
# if: github.repository == 'nushell/nightly'
# Map a step output to a job output
outputs:
skip: ${{ steps.vars.outputs.skip }}
build_date: ${{ steps.vars.outputs.build_date }}
nightly_tag: ${{ steps.vars.outputs.nightly_tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
@ -39,7 +46,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
@ -57,16 +64,53 @@ jobs:
# All the changes will be overwritten by the upstream main branch
git reset --hard src/main
git push origin main -f
let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..7)
let tag_name = $'nightly-($sha_short)'
if (git ls-remote --tags origin $tag_name | is-empty) {
git tag -a $tag_name -m $'Nightly build from ($sha_short)'
- name: Create Tag and Output Tag Name
if: github.repository == 'nushell/nightly'
id: vars
shell: nu {0}
run: |
let date = date now | format date %m%d
let version = open Cargo.toml | get package.version
let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..6)
let latest_meta = http get https://api.github.com/repos/nushell/nightly/releases
| sort-by -r created_at
| where tag_name =~ nightly
| get tag_name?.0? | default ''
| parse '{version}-nightly.{build}+{hash}'
if ($latest_meta.0?.hash? | default '') == $sha_short {
print $'(ansi g)Latest nightly build is up-to-date, skip rebuilding.(ansi reset)'
$'skip=true(char nl)' o>> $env.GITHUB_OUTPUT
exit 0
}
let prev_ver = $latest_meta.0?.version? | default '0.0.0'
let build = if ($latest_meta | is-empty) or ($version != $prev_ver) { 1 } else {
($latest_meta | get build?.0? | default 0 | into int) + 1
}
let nightly_tag = $'($version)-nightly.($build)+($sha_short)'
$'build_date=($date)(char nl)' o>> $env.GITHUB_OUTPUT
$'nightly_tag=($nightly_tag)(char nl)' o>> $env.GITHUB_OUTPUT
if (git ls-remote --tags origin $nightly_tag | is-empty) {
ls **/Cargo.toml | each {|file|
open --raw $file.name
| str replace --all $'version = "($version)"' $'version = "($version)-nightly.($build)"'
| save --force $file.name
}
# Disable the following two workflows for the automatic committed changes
rm .github/workflows/ci.yml
rm .github/workflows/audit.yml
git add .
git commit -m $'Update version to ($version)-nightly.($build)'
git tag -a $nightly_tag -m $'Nightly build from ($sha_short)'
git push origin --tags
git push origin main -f
}
standard:
release:
name: Nu
needs: prepare
if: needs.prepare.outputs.skip != 'true'
strategy:
fail-fast: false
matrix:
@ -83,24 +127,15 @@ jobs:
- armv7-unknown-linux-musleabihf
- riscv64gc-unknown-linux-gnu
- loongarch64-unknown-linux-gnu
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
os: windows-11-arm
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
@ -119,40 +154,64 @@ jobs:
os: ubuntu-22.04
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Install Wix Toolset 6 for Windows
shell: pwsh
if: ${{ startsWith(matrix.os, 'windows') }}
run: |
dotnet tool install --global wix --version 6.0.0
dotnet workload install wix
$wixPath = "$env:USERPROFILE\.dotnet\tools"
echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
$env:PATH = "$wixPath;$env:PATH"
wix --version
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3
if: ${{ matrix.os != 'windows-11-arm' }}
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu
if: ${{ matrix.os != 'windows-11-arm' }}
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
- name: Build Nu for Windows ARM64
id: nu0
shell: pwsh
if: ${{ matrix.os == 'windows-11-arm' }}
run: |
$env:OS = 'windows'
$env:REF = '${{ github.ref }}'
$env:TARGET = '${{ matrix.target }}'
cargo build --release --all --target aarch64-pc-windows-msvc
cp ./target/${{ matrix.target }}/release/nu.exe .
./nu.exe -c 'version'
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
- name: Create an Issue for Release Failure
if: ${{ failure() }}
uses: JasonEtco/create-an-issue@v2.9.2
uses: JasonEtco/create-an-issue@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -160,13 +219,6 @@ jobs:
search_existing: open
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
- name: Set Outputs of Short SHA
id: vars
run: |
echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
sha_short=$(git rev-parse --short HEAD)
echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT
# REF: https://github.com/marketplace/actions/gh-release
# Create a release only in nushell/nightly repo
- name: Publish Archive
@ -174,9 +226,39 @@ jobs:
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
with:
prerelease: true
files: ${{ steps.nu.outputs.archive }}
tag_name: nightly-${{ steps.vars.outputs.sha_short }}
name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }}
files: |
${{ steps.nu.outputs.msi }}
${{ steps.nu0.outputs.msi }}
${{ steps.nu.outputs.archive }}
${{ steps.nu0.outputs.archive }}
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sha256sum:
needs: [prepare, release]
name: Create Sha256sum
runs-on: ubuntu-latest
if: github.repository == 'nushell/nightly'
steps:
- name: Download Release Archives
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
gh release download ${{ needs.prepare.outputs.nightly_tag }}
--repo ${{ github.repository }}
--pattern '*'
--dir release
- name: Create Checksums
run: cd release && shasum -a 256 * > ../SHA256SUMS
- name: Publish Checksums
uses: softprops/action-gh-release@v2.0.9
with:
draft: false
prerelease: true
files: SHA256SUMS
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -184,12 +266,9 @@ jobs:
name: Cleanup
# Should only run in nushell/nightly repo
if: github.repository == 'nushell/nightly'
needs: [release, sha256sum]
runs-on: ubuntu-latest
steps:
# Sleep for 30 minutes, waiting for the release to be published
- name: Waiting for Release
run: sleep 1800
- uses: actions/checkout@v4
with:
ref: main
@ -197,14 +276,14 @@ 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
shell: nu {0}
run: |
let KEEP_COUNT = 10
let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | range $KEEP_COUNT..)
let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | slice $KEEP_COUNT..)
for release in $deprecated {
print $'Deleting tag ($release.tag_name)'
git push origin --delete $release.tag_name

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)'
@ -175,53 +175,60 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
tar -czf $archive $dest
print $'archive: ---> ($archive)'; ls $archive
# 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
echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT
} else if $os == 'windows-latest' {
} else if $os =~ 'windows' {
let releaseStem = $'($bin)-($version)-($target)'
let arch = if $nu.os-info.arch =~ 'x86_64' { 'x64' } else { 'arm64' }
fetch-less $arch
print $'(char nl)Download less related stuffs...'; hr-line
# todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it?
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
# the below was renamed because it was failing to download for darren. it should work but it wasn't
# todo: maybe we should get rid of this aria2c dependency and just use http get?
#aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt
# Create Windows msi release package
if (get-env _EXTRA_) == 'msi' {
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
print $'(char nl)Start creating Windows msi package with the following contents...'
cd $src; hr-line
# Wix need the binaries be stored in target/release/
cp -r ($'($dist)/*' | into glob) target/release/
ls target/release/* | print
cargo install cargo-wix --version 0.3.8
cargo wix --no-build --nocapture --package nu --output $wixRelease
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print
let archive = $'($dist)/($releaseStem).zip'
7z a $archive ...(glob *)
let pkg = (ls -f $archive | get name)
if not ($pkg | is-empty) {
# Workaround for https://github.com/softprops/action-gh-release/issues/280
let archive = ($wixRelease | str replace --all '\' '/')
print $'archive: ---> ($archive)';
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
let archive = ($pkg | get 0 | str replace --all '\' '/')
print $'archive: ---> ($archive)'
echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT
}
} else {
# Create extra Windows msi release package if dotnet and wix are available
let installed = [dotnet wix] | all { (which $in | length) > 0 }
if $installed and (wix --version | split row . | first | into int) >= 6 {
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print
let archive = $'($dist)/($releaseStem).zip'
7z a $archive ...(glob *)
let pkg = (ls -f $archive | get name)
if not ($pkg | is-empty) {
# Workaround for https://github.com/softprops/action-gh-release/issues/280
let archive = ($pkg | get 0 | str replace --all '\' '/')
print $'archive: ---> ($archive)'
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
}
print $'(char nl)Start creating Windows msi package with the following contents...'
cd $src; cd wix; hr-line; mkdir nu
# Wix need the binaries be stored in nu folder
cp -r ($'($dist)/*' | into glob) nu/
cp $'($dist)/README.txt' .
ls -f nu/* | print
./nu/nu.exe -c $'NU_RELEASE_VERSION=($version) dotnet build -c Release -p:Platform=($arch)'
glob **/*.msi | print
# Workaround for https://github.com/softprops/action-gh-release/issues/280
let wixRelease = (glob **/*.msi | where $it =~ bin | get 0 | str replace --all '\' '/')
let msi = $'($wixRelease | path dirname)/nu-($version)-($target).msi'
mv $wixRelease $msi
print $'MSI archive: ---> ($msi)';
echo $"msi=($msi)(char nl)" o>> $env.GITHUB_OUTPUT
}
}
def fetch-less [
arch: string = 'x64' # The architecture to fetch
] {
let less_zip = $'less-($arch).zip'
print $'Fetching less archive: (ansi g)($less_zip)(ansi reset)'
let url = $'https://github.com/jftuga/less-Windows/releases/download/less-v668/($less_zip)'
http get https://github.com/jftuga/less-Windows/blob/master/LICENSE | save -rf LICENSE-for-less.txt
http get $url | save -rf $less_zip
unzip $less_zip
rm $less_zip lesskey.exe
}
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

@ -35,24 +35,15 @@ jobs:
- armv7-unknown-linux-musleabihf
- riscv64gc-unknown-linux-gnu
- loongarch64-unknown-linux-gnu
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
- target: aarch64-pc-windows-msvc
extra: 'bin'
os: windows-latest
- target: aarch64-pc-windows-msvc
extra: msi
os: windows-latest
os: windows-11-arm
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
@ -75,12 +66,23 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Wix Toolset 6 for Windows
shell: pwsh
if: ${{ startsWith(matrix.os, 'windows') }}
run: |
dotnet tool install --global wix --version 6.0.0
dotnet workload install wix
$wixPath = "$env:USERPROFILE\.dotnet\tools"
echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
$env:PATH = "$wixPath;$env:PATH"
wix --version
- name: Update Rust Toolchain Target
run: |
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
@ -88,17 +90,31 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
if: ${{ matrix.os != 'windows-11-arm' }}
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu
if: ${{ matrix.os != 'windows-11-arm' }}
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
- name: Build Nu for Windows ARM64
id: nu0
shell: pwsh
if: ${{ matrix.os == 'windows-11-arm' }}
run: |
$env:OS = 'windows'
$env:REF = '${{ github.ref }}'
$env:TARGET = '${{ matrix.target }}'
cargo build --release --all --target aarch64-pc-windows-msvc
cp ./target/${{ matrix.target }}/release/nu.exe .
./nu.exe -c 'version'
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
# WARN: Don't upgrade this action due to the release per asset issue.
# See: https://github.com/softprops/action-gh-release/issues/445
@ -107,7 +123,11 @@ jobs:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with:
draft: true
files: ${{ steps.nu.outputs.archive }}
files: |
${{ steps.nu.outputs.msi }}
${{ steps.nu0.outputs.msi }}
${{ steps.nu.outputs.archive }}
${{ steps.nu0.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

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

View File

@ -26,4 +26,4 @@ jobs:
version: ${{ inputs.tag_name || github.event.release.tag_name }}
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
token: ${{ secrets.NUSHELL_PAT }}
fork-user: fdncred
fork-user: nushell

503
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
@ -70,8 +70,8 @@ bracoxide = "0.1.5"
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

@ -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

@ -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

@ -17,14 +17,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<'_> {
@ -108,14 +108,26 @@ 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

@ -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.
j
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{

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

@ -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

@ -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

@ -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

@ -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

@ -175,7 +175,7 @@ fn run(call: &Call, args: &Arguments, input: PipelineData) -> Result<PipelineDat
handle_value(stream.into_value(), args, head),
metadata,
)),
PipelineData::Empty { .. } => Err(ShellError::PipelineEmpty { dst_span: head }),
PipelineData::Empty => Err(ShellError::PipelineEmpty { dst_span: head }),
_ => Err(ShellError::UnsupportedInput {
msg: "Input value cannot be joined".to_string(),
input: "value originates from here".into(),
@ -221,14 +221,21 @@ fn join_list(parts: &[Value], head: Span, span: Span, args: &Arguments) -> Value
Value::list(vals, span)
}
Err(_) => Value::error(
ShellError::PipelineMismatch {
Err(ShellError::CantConvert { from_type, .. }) => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or record".into(),
wrong_type: from_type,
dst_span: head,
src_span: span,
},
span,
),
Err(_) => Value::error(
ShellError::NushellFailed {
msg: "failed to join path".into(),
},
span,
),
}
}
}

View File

@ -51,21 +51,11 @@ fn handle_invalid_values(rest: Value, name: Span) -> Value {
fn err_from_value(rest: &Value, name: Span) -> ShellError {
match rest {
Value::Error { error, .. } => *error.clone(),
_ => {
if rest.is_nothing() {
ShellError::OnlySupportsThisInputType {
exp_input_type: "string, record or list".into(),
wrong_type: "nothing".into(),
dst_span: name,
src_span: rest.span(),
}
} else {
ShellError::PipelineMismatch {
exp_input_type: "string, row or list".into(),
dst_span: name,
src_span: rest.span(),
}
}
}
_ => ShellError::OnlySupportsThisInputType {
exp_input_type: "string, record or list".into(),
wrong_type: rest.get_type().to_string(),
dst_span: name,
src_span: rest.span(),
},
}
}

View File

@ -18,12 +18,11 @@ impl Command for Kill {
let signature = Signature::build("kill")
.input_output_types(vec![(Type::Nothing, Type::Any)])
.allow_variants_without_examples(true)
.required(
.rest(
"pid",
SyntaxShape::Int,
"Process id of process that is to be killed.",
"Process ids of processes that are to be killed.",
)
.rest("rest", SyntaxShape::Int, "Rest of processes to kill.")
.switch("force", "forcefully kill the process", Some('f'))
.switch("quiet", "won't print anything to the console", Some('q'))
.category(Category::Platform);
@ -51,12 +50,18 @@ impl Command for Kill {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pid: i64 = call.req(engine_state, stack, 0)?;
let rest: Vec<i64> = call.rest(engine_state, stack, 1)?;
let pids: Vec<i64> = call.rest(engine_state, stack, 0)?;
let force: bool = call.has_flag(engine_state, stack, "force")?;
let signal: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "signal")?;
let quiet: bool = call.has_flag(engine_state, stack, "quiet")?;
if pids.is_empty() {
return Err(ShellError::MissingParameter {
param_name: "pid".to_string(),
span: call.arguments_span(),
});
}
if cfg!(unix) {
if let (
true,
@ -83,7 +88,7 @@ impl Command for Kill {
let mut cmd = build_kill_command(
force,
std::iter::once(pid).chain(rest),
pids.iter().copied(),
signal.map(|spanned| spanned.item as u32),
);

View File

@ -1,5 +1,5 @@
use super::byte_stream::{random_byte_stream, RandomDistribution};
use nu_engine::command_prelude::*;
use rand::{thread_rng, RngCore};
#[derive(Clone)]
pub struct RandomBinary;
@ -57,12 +57,12 @@ impl Command for RandomBinary {
}),
}?;
let mut rng = thread_rng();
let mut out = vec![0u8; length];
rng.fill_bytes(&mut out);
Ok(Value::binary(out, call.head).into_pipeline_data())
Ok(random_byte_stream(
RandomDistribution::Binary,
length,
call.head,
engine_state.signals().clone(),
))
}
fn examples(&self) -> Vec<Example> {

View File

@ -1,6 +1,5 @@
use nu_engine::command_prelude::*;
use rand::prelude::{thread_rng, Rng};
use rand::random_bool;
#[derive(Clone)]
pub struct RandomBool;
@ -77,8 +76,7 @@ fn bool(
}
}
let mut rng = thread_rng();
let bool_result: bool = rng.gen_bool(probability);
let bool_result: bool = random_bool(probability);
Ok(PipelineData::Value(Value::bool(bool_result, span), None))
}

View File

@ -0,0 +1,49 @@
use nu_engine::command_prelude::*;
use nu_protocol::Signals;
use rand::{
distr::{Alphanumeric, StandardUniform},
rng, Rng,
};
pub(super) enum RandomDistribution {
Binary,
Alphanumeric,
}
pub(super) fn random_byte_stream(
distribution: RandomDistribution,
length: usize,
span: Span,
signals: Signals,
) -> PipelineData {
let stream_type = match distribution {
RandomDistribution::Binary => ByteStreamType::Binary,
RandomDistribution::Alphanumeric => ByteStreamType::String,
};
const OUTPUT_CHUNK_SIZE: usize = 8192;
let mut remaining_bytes = length;
PipelineData::ByteStream(
ByteStream::from_fn(span, signals.clone(), stream_type, move |out| {
if remaining_bytes == 0 || signals.interrupted() {
return Ok(false);
}
let bytes_to_write = std::cmp::min(remaining_bytes, OUTPUT_CHUNK_SIZE);
let rng = rng();
let byte_iter: Box<dyn Iterator<Item = u8>> = match distribution {
RandomDistribution::Binary => Box::new(rng.sample_iter(StandardUniform)),
RandomDistribution::Alphanumeric => Box::new(rng.sample_iter(Alphanumeric)),
};
out.extend(byte_iter.take(bytes_to_write));
remaining_bytes -= bytes_to_write;
Ok(true)
})
.with_known_size(Some(length as u64)),
None,
)
.with_span(span)
}

View File

@ -1,8 +1,5 @@
use super::byte_stream::{random_byte_stream, RandomDistribution};
use nu_engine::command_prelude::*;
use rand::{
distributions::{Alphanumeric, Distribution},
thread_rng,
};
const DEFAULT_CHARS_LENGTH: usize = 25;
@ -71,7 +68,6 @@ fn chars(
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let length: Option<Value> = call.get_flag(engine_state, stack, "length")?;
let length = if let Some(length_val) = length {
match length_val {
@ -97,17 +93,11 @@ fn chars(
DEFAULT_CHARS_LENGTH
};
let mut rng = thread_rng();
let random_string = Alphanumeric
.sample_iter(&mut rng)
.take(length)
.map(char::from)
.collect::<String>();
Ok(PipelineData::Value(
Value::string(random_string, span),
None,
Ok(random_byte_stream(
RandomDistribution::Alphanumeric,
length,
call.head,
engine_state.signals().clone(),
))
}

View File

@ -1,6 +1,6 @@
use nu_engine::command_prelude::*;
use nu_protocol::ListStream;
use rand::prelude::{thread_rng, Rng};
use rand::random_range;
#[derive(Clone)]
pub struct RandomDice;
@ -73,10 +73,7 @@ fn dice(
let dice: usize = call.get_flag(engine_state, stack, "dice")?.unwrap_or(1);
let sides: usize = call.get_flag(engine_state, stack, "sides")?.unwrap_or(6);
let iter = (0..dice).map(move |_| {
let mut thread_rng = thread_rng();
Value::int(thread_rng.gen_range(1..sides + 1) as i64, span)
});
let iter = (0..dice).map(move |_| Value::int(random_range(1..sides + 1) as i64, span));
Ok(ListStream::new(iter, span, engine_state.signals().clone()).into())
}

View File

@ -1,6 +1,6 @@
use nu_engine::command_prelude::*;
use nu_protocol::{FloatRange, Range};
use rand::prelude::{thread_rng, Rng};
use rand::random_range;
use std::ops::Bound;
#[derive(Clone)]
@ -71,8 +71,6 @@ fn float(
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let mut thread_rng = thread_rng();
match range {
Some(range) => {
let range_span = range.span;
@ -90,15 +88,15 @@ fn float(
}
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..f64::INFINITY),
Bound::Included(end) => random_range(range.start()..=end),
Bound::Excluded(end) => random_range(range.start()..end),
Bound::Unbounded => random_range(range.start()..f64::INFINITY),
};
Ok(PipelineData::Value(Value::float(value, span), None))
}
None => Ok(PipelineData::Value(
Value::float(thread_rng.gen_range(0.0..1.0), span),
Value::float(random_range(0.0..1.0), span),
None,
)),
}

View File

@ -1,6 +1,6 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use rand::prelude::{thread_rng, Rng};
use rand::random_range;
use std::ops::Bound;
#[derive(Clone)]
@ -75,8 +75,6 @@ fn integer(
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let mut thread_rng = thread_rng();
match range {
Some(range) => {
let range_span = range.span;
@ -94,9 +92,9 @@ fn integer(
}
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..=i64::MAX),
Bound::Included(end) => random_range(range.start()..=end),
Bound::Excluded(end) => random_range(range.start()..end),
Bound::Unbounded => random_range(range.start()..=i64::MAX),
};
Ok(PipelineData::Value(Value::int(value, span), None))
@ -110,7 +108,7 @@ fn integer(
}
}
None => Ok(PipelineData::Value(
Value::int(thread_rng.gen_range(0..=i64::MAX), span),
Value::int(random_range(0..=i64::MAX), span),
None,
)),
}

View File

@ -1,5 +1,6 @@
mod binary;
mod bool;
mod byte_stream;
mod chars;
mod dice;
mod float;

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