Compare commits

..

32 Commits

Author SHA1 Message Date
b52ec7f7b2 Bump quick-xml from 0.30.0 to 0.31.0
Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.30.0 to 0.31.0.
- [Release notes](https://github.com/tafia/quick-xml/releases)
- [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md)
- [Commits](https://github.com/tafia/quick-xml/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: quick-xml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 20:25:39 +00:00
8ad5d8bb6a Bump procfs to 0.16.0 (#11115)
Fix the breaking changes.
Get's rid of some outdated transitive dependencies.
Sadly we need to expose more of `procfs` to `nu-command` based on how
the features of `nu-system` are exposed right now.

Conditional compilation/dependencies from hell included

Supersedes #11101
2023-11-20 21:22:35 +01:00
869b01205c Bump uuid from 1.5.0 to 1.6.0 (#11104) 2023-11-20 19:38:41 +00:00
f5b2f5a9ee Bump winreg from 0.51.0 to 0.52.0 (#11102) 2023-11-20 19:35:21 +00:00
adfa4d00c0 Bump version to 0.87.2 (#11114)
Based on the hotfix
https://github.com/nushell/nushell/releases/tag/0.87.1 use this to
disambiguate
2023-11-20 20:31:10 +01:00
12effd9b4e Refactor Value cell path functions to fix bugs (#11066)
# Description
Slightly refactors the cell path functions (`insert_data_at_cell_path`,
etc.) for `Value` to fix a few bugs and ensure consistent behavior.
Namely, case (in)sensitivity now applies to lazy records just like it
does for regular `Records`. Also, the insert behavior of `insert` and
`upsert` now match, alongside fixing a few related bugs described below.
Otherwise, a few places were changed to use the `Record` API.

# Tests
Added tests for two bugs:
- `{a: {}} | insert a.b.c 0`: before this PR, doesn't create the
innermost record `c`.
- `{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2`: before
this PR, doesn't add the field `b: 2` to each row.
2023-11-19 21:46:41 +01:00
c26fca7419 Add Argument::span() and Call::arguments_span() (#10983)
# Description

These make it easy to make a Span that covers an entire argument and the
span of all arguments in a Call.

Call::arguments_span() is useful for errors where a command may accept
arguments or the pipeline, but not both.

Argument::span() is useful for errors where an arguments is incompatible
with one or more other arguments.

In particular, I wish to use this to create an error for an
implementation of #9563 that either allows arguments to set limits:

```nushell
limits set RLIMIT_NOFILE --soft 255 --hard 1024
```

Or pipeline:

```nushell
{name: RLIMIT_NOFILE, soft: 255} | limits set
```

But not both:

```
❯ [{name: RLIMIT_NOFILE, soft: 255, hard: 1024}] | limits set AS --soft 5 --hard 5
Error: nu:🐚:incompatible_parameters

  × Incompatible parameters.
   ╭─[source:1:1]
 1 │ [{name: RLIMIT_NOFILE, soft: 255, hard: 1024}] | limits set AS --soft 5 --hard 5
   · ───────────────────────┬──────────────────────              ──────────┬─────────
   ·                        │                                              ╰── or arguments, not both
   ·                        ╰── Supply either pipeline
   ╰────
```

# User-Facing Changes

Only nushell Command API changes
2023-11-19 21:43:56 +01:00
da59dfe7d2 Convert ShellError::NetworkFailure to named fields (#11093)
# Description

Part of #10700
2023-11-19 21:32:11 +01:00
08715e6308 Convert ShellError::CommandNotFound to named fields (#11094)
# Description

Part of #10700
2023-11-19 21:31:28 +01:00
07d7899a97 remove def-env and export def-env (#10999)
follow-up to
- https://github.com/nushell/nushell/pull/10715

> **Important**
> wait for between 0.87 and 0.88 to land this

# Description
it's time for removal again 😋 
this PR removes `def-env` and `export def-env` in favor of `def --env`

# User-Facing Changes
`def-env` and `export def-env` will not be found anymore.

# Tests + Formatting

# After Submitting
2023-11-19 23:25:09 +08:00
494a5a5286 Add mktemp command (#11005)
closes #10845 

I've opened this a little prematurely to get some questions answered
before I cleanup the code.

As I started trying to better understand GNUs `mktemp` I've realized its
kind of peculiar and we might want to change its behavior to introduce
it to nushell.

#### quiet and dry run

Does it make sense to keep the `quiet` and `dry_run` flags? I don't
think so. The GNU documentation says this about the dry run flag "Using
the output of this command to create a new file is inherently unsafe, as
there is a window of time between generating the name and using it where
another process can create an object by the same name." So yeah why keep
it? As far as quiet goes, does it make sense to silence the errors in
nushell?

#### other confusing flags

According to the [gnu
docs](https://www.gnu.org/software/coreutils/manual/html_node/mktemp-invocation.html),
the `-t` flag is deprecated and the `-p`/ `--tempdir` are the same flag
with the only difference being `--tempdir` takes an optional path, Given
that, I've broken the `-p` away from `--tempdir`. Now there is one
switch `--tmpdir`/`-t` and one named param `--tmpdir-path`/`-p`.

GNU mktemp
```
  -p DIR, --tmpdir[=DIR]  interpret TEMPLATE relative to DIR; if DIR is not
                        specified, use $TMPDIR if set, else /tmp.  With
                        this option, TEMPLATE must not be an absolute name;
                        unlike with -t, TEMPLATE may contain slashes, but
                        mktemp creates only the final component
  -t                  interpret TEMPLATE as a single file name component,
                        relative to a directory: $TMPDIR, if set; else the
                        directory specified via -p; else /tmp [deprecated]

```
to
nushell mktemp
```
  -p, --tmpdir-path <Filepath> # named param, must provide a path
  -t, --tmpdir                 # a switch
```

Is this a terrible idea?

What should I do?

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2023-11-17 19:30:53 -06:00
f41c93b2d3 Apply nightly clippy fixes (#11083)
<!--
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.
-->
Clippy fixes for rust 1.76.0-nightly

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-17 09:15:55 -06:00
5063e01c12 std: add cross platform null-device name (#11070)
# Description
We have meet a discord discussion about cross platform `null-device`:
https://discord.com/channels/601130461678272522/988303282931912704/1165966467095875624

I want the same feature too...And I think it's good enough to add it
into `nu-std`.

Different to https://github.com/nushell/nu_scripts/pull/649/files, I
think named it to `null-device`(the name comes from
[wiki](https://en.wikipedia.org/wiki/Null_device)) is better than
`null-stream`.
2023-11-17 14:49:07 +01:00
d1137cc700 Send only absolute paths to uu_cp (#11080)
# Description
Fixes https://github.com/nushell/nushell/issues/10832

Replaces: https://github.com/nushell/nushell/pull/10843
2023-11-17 07:30:57 +08:00
3966c0a9fd Fix rm path handling (#11064)
# Description
Fixes issue #11061 where `rm` fails to find a file after a `cd`. It
looks like the new glob functions do not return absolute file paths
which we forgot to account for.

# Tests
Added a test (fails on current main, but passes with this PR).

---------

Co-authored-by: Jakub Žádník <kubouch@gmail.com>
2023-11-17 07:30:15 +08:00
dbdb1f6600 remove the unfold command (#10773)
follow-up to:
- https://github.com/nushell/nushell/pull/10771

> **Important**
> wait for between 0.87 and 0.88 to land this

# Description
after deprecation comes the removal... this PR removes `unfold` in favor
of `generate` 🥳

# User-Facing Changes
users should use `generate` now, `unfold` will stop working.

# Tests + Formatting

# After Submitting
2023-11-17 06:50:20 +08:00
84cdc0d521 remove size command in favor of str stats (#10784)
follow-up to
- https://github.com/nushell/nushell/pull/10798

> **Important**
> wait for between 0.87 and 0.88 to land this

# Description
once again, after deprecation comes removal 😌 

# User-Facing Changes
`size` is now removed and `str size` should be used

# Tests + Formatting

# After Submitting
2023-11-17 06:49:19 +08:00
ab59dab129 remove --not from glob (#10839)
follow-up to
- https://github.com/nushell/nushell/pull/10827

> **Important**  
> wait for between 0.87 and 0.88 to land this

# Description
after deprecation comes removal... this PR removes `glob --not` in favor
of `glob --exclude`.

# User-Facing Changes
`glob --not` will stop working.

# Tests + Formatting

# After Submitting
i didn't find any use of `glob --not` in the `nu_scripts` so no update
required there 👍
2023-11-17 06:46:15 +08:00
e0c8a3d14c remove extern-wrapped and export extern-wrapped (#11000)
follow-up to
- https://github.com/nushell/nushell/pull/10716

> **Important**
> wait for between 0.87 and 0.88 to land this

# Description
it's time for removal again 😋 
this PR removes `extern-wrapped` and `export extern-wrapped` in favor of
`def --wrapped`

# User-Facing Changes
`extern-wrapped` and `export extern-wrapped` will not be found anymore.

# Tests + Formatting

# After Submitting
2023-11-17 06:44:28 +08:00
e93e51d672 bump rust-toolchain to 1.72.1 (#11079)
# Description

This PR follows our process of staying 2 releases behind rust. 1.74.0
was released today so we update to 1.72.1.

Reference https://releases.rs/

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-16 15:14:45 -06:00
4205edbc70 Fix the output type for 'view files' (#11077)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

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

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

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

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

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

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

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

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
2023-11-16 11:53:51 -06:00
80bee40807 optimize/clean up a few of the table changes (#11076)
# Description

@sholderbach pointed out some places that I could help improve the code
in the table command changes. This PR tries to implement those.

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-16 11:37:46 -06:00
461837773b correct table example syntax (#11074)
# Description

Correct an example that had old syntax.

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-16 08:20:52 -06:00
52d4259f58 add "default" table theme (#11072)
# Description

This PR fixes a minor bug that prevented this command from running.
```nushell
table --list | each {|r| print ($r); print (ls | first 3 | table --theme $r)}
```
Here's the output now of the first few themes.

![image](https://github.com/nushell/nushell/assets/343840/21bc8942-5106-4b6a-8905-e90d6cb9a153)

It prevented it from running because "default" wasn't a real table
theme. Now "default" is a synonym of rounded.

Also tweaked the error message when a bad theme name is provided.

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-16 06:14:18 -06:00
274a8366c6 tweak table example/parameter text (#11071)
# Description

This PR just tweaks the `table` example text and some parameter text.

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-16 05:35:55 -06:00
a1dfc35968 Fix #11047 (#11054)
close #11047
2023-11-16 05:28:54 -06:00
5886a74ccc into binary -c: return 0 as single byte (#11068)
# Description

The `into binary` command has a `-c` flag which strips any leading 0s in
the most significant digits to represent the minimal number of bytes,
rather than the system's complete in-memory representation of the input.

However, currently giving 0 as input results in eight 0 bytes even with
the `-c` flag, which is inconsistent with the purpose of the flag.

```nu
❯ : 345678 | into binary
Length: 8 (0x8) bytes | printable whitespace ascii_other non_ascii
00000000:   4e 46 05 00  00 00 00 00                             NF•00000

❯ : 345678 | into binary -c
Length: 3 (0x3) bytes | printable whitespace ascii_other non_ascii
00000000:   4e 46 05

❯ : 0 | into binary
Length: 8 (0x8) bytes | printable whitespace ascii_other non_ascii
00000000:   00 00 00 00  00 00 00 00                             00000000

❯ : 0 | into binary -c
Length: 8 (0x8) bytes | printable whitespace ascii_other non_ascii
00000000:   00 00 00 00  00 00 00 00                             00000000
```

This change fixes this behavior so that if the entire input results in
all 0 bytes, only a single 0 byte is returned.

```nu
❯ : ~/src/nushell/target/aarch64-linux-android/debug/nu -c '0 | into binar
y -c'
Length: 1 (0x1) bytes | printable whitespace ascii_other non_ascii
00000000:   00
```

# User-Facing Changes

Values which result in all null bytes will be truncated to a single byte
when `-c` is given. This could potentially be considered a breaking
change if this behavior was relied upon in some way.
2023-11-16 04:09:31 -06:00
4367aa9f58 allow parsing of human readable datetimes (#11051)
# Description

This PR adds the ability to parse human readable datetime strings as
part of the `into datetime` command. I added a new `-n`/`--list-human`
parameter that produces this list to give the user an idea of what is
supported.
```nushell
❯ into datetime --list-human 
╭#─┬parseable human datetime examples┬───result───╮
│0 │Today 18:30                      │in 8 hours  │
│1 │2022-11-07 13:25:30              │a year ago  │
│2 │15:20 Friday                     │in 3 days   │
│3 │This Friday 17:00                │in 3 days   │
│4 │13:25, Next Tuesday              │in a week   │
│5 │Last Friday at 19:45             │3 days 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         │
╰#─┴parseable human datetime examples┴───result───╯
```

Or with `$env.config.datetime_format.table` set.
```nushell
❯ into datetime --list-human 
╭#─┬parseable human datetime examples┬──────result───────╮
│0 │Today 18:30                      │11/14/23 06:30:00PM│
│1 │2022-11-07 13:25:30              │11/07/22 01:25:30PM│
│2 │15:20 Friday                     │11/17/23 03:20:00PM│
│3 │This Friday 17:00                │11/17/23 05:00:00PM│
│4 │13:25, Next Tuesday              │11/21/23 01:25:00PM│
│5 │Last Friday at 19:45             │11/10/23 07:45:00PM│
│6 │In 3 days                        │11/17/23 10:12:54AM│
│7 │In 2 hours                       │11/14/23 12:12:54PM│
│8 │10 hours and 5 minutes ago       │11/14/23 12:07:54AM│
│9 │1 years ago                      │11/13/22 10:12:54AM│
│10│A year ago                       │11/13/22 10:12:54AM│
│11│A month ago                      │10/15/23 11:12:54AM│
│12│A week ago                       │11/07/23 10:12:54AM│
│13│A day ago                        │11/13/23 10:12:54AM│
│14│An hour ago                      │11/14/23 09:12:54AM│
│15│A minute ago                     │11/14/23 10:11:54AM│
│16│A second ago                     │11/14/23 10:12:53AM│
│17│Now                              │11/14/23 10:12:54AM│
╰#─┴parseable human datetime examples┴──────result───────╯
```
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2023-11-15 17:43:37 -06:00
e9c298713e nu-table/ Add -t/theme argument && Replace -n/start-number with -i/index (#11058)
ref #11054

cc: @fdncred 

I've not figured out how to be able to have a flag option as `table -i`
:(

```nu
~/bin/nushell> [[a b, c]; [1 [2 3 3] 3] [4 5 [1 2 [1 2 3]]]] | table -e --width=80 --theme basic -i false

+---+-------+-----------+
| a |   b   |     c     |
+---+-------+-----------+
| 1 | +---+ |         3 |
|   | | 2 | |           |
|   | +---+ |           |
|   | | 3 | |           |
|   | +---+ |           |
|   | | 3 | |           |
|   | +---+ |           |
+---+-------+-----------+
| 4 |     5 | +-------+ |
|   |       | |     1 | |
|   |       | +-------+ |
|   |       | |     2 | |
|   |       | +-------+ |
|   |       | | +---+ | |
|   |       | | | 1 | | |
|   |       | | +---+ | |
|   |       | | | 2 | | |
|   |       | | +---+ | |
|   |       | | | 3 | | |
|   |       | | +---+ | |
|   |       | +-------+ |
+---+-------+-----------+
```

```nu
~/bin/nushell> [[a b, c]; [1 [2 3 3] 3] [4 5 [1 2 [1 2 3]]]] | table -e --width=80 --theme basic -i 100

+-----+---+-------------+-----------------------+
|   # | a |      b      |           c           |
+-----+---+-------------+-----------------------+
| 100 | 1 | +-----+---+ |                     3 |
|     |   | | 100 | 2 | |                       |
|     |   | +-----+---+ |                       |
|     |   | | 101 | 3 | |                       |
|     |   | +-----+---+ |                       |
|     |   | | 102 | 3 | |                       |
|     |   | +-----+---+ |                       |
+-----+---+-------------+-----------------------+
| 101 | 4 |           5 | +-----+-------------+ |
|     |   |             | | 100 |           1 | |
|     |   |             | +-----+-------------+ |
|     |   |             | | 101 |           2 | |
|     |   |             | +-----+-------------+ |
|     |   |             | | 102 | +-----+---+ | |
|     |   |             | |     | | 100 | 1 | | |
|     |   |             | |     | +-----+---+ | |
|     |   |             | |     | | 101 | 2 | | |
|     |   |             | |     | +-----+---+ | |
|     |   |             | |     | | 102 | 3 | | |
|     |   |             | |     | +-----+---+ | |
|     |   |             | +-----+-------------+ |
+-----+---+-------------+-----------------------+
```
2023-11-15 17:41:18 -06:00
c110ddff66 Implement LSP Text Document Synchronization (#10941) 2023-11-15 17:35:48 -06:00
a806717f35 Testing support tweaks: exit status in Outcome (#10692)
This PR makes a couple of tweaks to the testing support crate:

Add the `nu` invocation's exit status to the test output so that one
can assert that nu exited with a successful code.

This PR was split off of #10232.
2023-11-15 23:50:43 +01:00
2b5f1ee5b3 Bump version to 0.87.1 (#11056) 2023-11-15 23:50:11 +01:00
118 changed files with 2364 additions and 2331 deletions

855
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -47,27 +47,27 @@ members = [
]
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.87.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.87.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.87.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.87.1" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.87.1", features = ["dataframe"], optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.87.1", optional = true }
nu-command = { path = "./crates/nu-command", version = "0.87.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.87.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.87.1" }
nu-json = { path = "./crates/nu-json", version = "0.87.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.87.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.87.1" }
nu-path = { path = "./crates/nu-path", version = "0.87.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.87.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.87.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.87.1" }
nu-system = { path = "./crates/nu-system", version = "0.87.1" }
nu-table = { path = "./crates/nu-table", version = "0.87.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.87.1" }
nu-std = { path = "./crates/nu-std", version = "0.87.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.87.1" }
nu-cli = { path = "./crates/nu-cli", version = "0.87.2" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.87.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.87.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.87.2" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.87.2", features = ["dataframe"], optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.87.2", optional = true }
nu-command = { path = "./crates/nu-command", version = "0.87.2" }
nu-engine = { path = "./crates/nu-engine", version = "0.87.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.87.2" }
nu-json = { path = "./crates/nu-json", version = "0.87.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.87.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.87.2" }
nu-path = { path = "./crates/nu-path", version = "0.87.2" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.87.2" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.87.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.87.2" }
nu-system = { path = "./crates/nu-system", version = "0.87.2" }
nu-table = { path = "./crates/nu-table", version = "0.87.2" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.87.2" }
nu-std = { path = "./crates/nu-std", version = "0.87.2" }
nu-utils = { path = "./crates/nu-utils", version = "0.87.2" }
nu-ansi-term = "0.49.0"
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
@ -97,7 +97,7 @@ nix = { version = "0.27", default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.87.1" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.87.2" }
assert_cmd = "2.0"
criterion = "0.5"
pretty_assertions = "1.4"

View File

@ -5,25 +5,25 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
nu-command = { path = "../nu-command", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-command = { path = "../nu-command", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
rstest = { version = "0.18.1", default-features = false }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-ansi-term = "0.49.0"
reedline = { version = "0.26.0", features = ["bashisms", "sqlite"] }
@ -39,7 +39,7 @@ percent-encoding = "2"
pathdiff = "0.2"
sysinfo = "0.29"
unicode-segmentation = "1.10"
uuid = { version = "1.5.0", features = ["v4"] }
uuid = { version = "1.6.0", features = ["v4"] }
[features]
plugin = []

View File

@ -67,7 +67,7 @@ impl NuCompleter {
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
// Line
if let Some(pos_arg) = block.signature.required_positional.get(0) {
if let Some(pos_arg) = block.signature.required_positional.first() {
if let Some(var_id) = pos_arg.var_id {
callee_stack.add_var(
var_id,

View File

@ -111,7 +111,7 @@ fn gather_env_vars(
let name = if let Some(Token {
contents: TokenContents::Item,
span,
}) = parts.get(0)
}) = parts.first()
{
let mut working_set = StateWorkingSet::new(engine_state);
let bytes = working_set.get_span_contents(*span);

View File

@ -122,14 +122,14 @@ fn dotnu_completions() {
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
// Test use completion
let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
}
#[test]
@ -141,7 +141,7 @@ fn external_completer_trailing_space() {
let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("gh", suggestions.first().unwrap().value);
assert_eq!("alias", suggestions.get(1).unwrap().value);
assert_eq!("", suggestions.get(2).unwrap().value);
}
@ -153,7 +153,7 @@ fn external_completer_no_trailing_space() {
let suggestions = run_external_completion(block, &input);
assert_eq!(2, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("gh", suggestions.first().unwrap().value);
assert_eq!("alias", suggestions.get(1).unwrap().value);
}
@ -164,7 +164,7 @@ fn external_completer_pass_flags() {
let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len());
assert_eq!("gh", suggestions.get(0).unwrap().value);
assert_eq!("gh", suggestions.first().unwrap().value);
assert_eq!("api", suggestions.get(1).unwrap().value);
assert_eq!("--", suggestions.get(2).unwrap().value);
}

View File

@ -5,21 +5,21 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-glob = { path = "../nu-glob", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-glob = { path = "../nu-glob", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
indexmap = "2.1"
miette = "5.10.0"
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
rstest = "0.18.2"

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
version = "0.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,9 +13,9 @@ version = "0.87.1"
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
# Potential dependencies for extras
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
@ -66,5 +66,5 @@ dataframe = ["num", "polars", "polars-io", "sqlparser"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }

View File

@ -68,7 +68,7 @@ fn command(
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let new_df = col_string
.get(0)
.first()
.ok_or_else(|| {
ShellError::GenericError(
"Empty names list".into(),

View File

@ -29,7 +29,7 @@ impl SQLContext {
fn execute_select(&self, select_stmt: &Select) -> Result<LazyFrame, PolarsError> {
// Determine involved dataframe
// Implicit join require some more work in query parsers, Explicit join are preferred for now.
let tbl = select_stmt.from.get(0).ok_or_else(|| {
let tbl = select_stmt.from.first().ok_or_else(|| {
PolarsError::ComputeError(ErrString::from("No table found in select statement"))
})?;
let mut alias_map = HashMap::new();
@ -37,7 +37,7 @@ impl SQLContext {
TableFactor::Table { name, alias, .. } => {
let tbl_name = name
.0
.get(0)
.first()
.ok_or_else(|| {
PolarsError::ComputeError(ErrString::from(
"No table found in select statement",
@ -182,7 +182,7 @@ impl SQLContext {
))
} else {
let ast = ast
.get(0)
.first()
.ok_or_else(|| PolarsError::ComputeError(ErrString::from("No statement found")))?;
Ok(match ast {
Statement::Query(query) => {

View File

@ -325,7 +325,7 @@ impl NuDataFrame {
let series = self
.df
.get_columns()
.get(0)
.first()
.expect("We have already checked that the width is 1");
Ok(series.clone())

View File

@ -11,7 +11,7 @@ pub(crate) fn convert_columns(
) -> Result<(Vec<Spanned<String>>, Span), ShellError> {
// First column span
let mut col_span = columns
.get(0)
.first()
.ok_or_else(|| {
ShellError::GenericError(
"Empty column list".into(),
@ -54,7 +54,7 @@ pub(crate) fn convert_columns_string(
) -> Result<(Vec<String>, Span), ShellError> {
// First column span
let mut col_span = columns
.get(0)
.first()
.ok_or_else(|| {
ShellError::GenericError(
"Empty column list".into(),

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.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,11 +13,11 @@ version = "0.87.1"
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
# Potential dependencies for extras
heck = "0.4.1"
@ -27,8 +27,8 @@ nu-ansi-term = "0.49.0"
fancy-regex = "0.11.0"
rust-embed = "8.0.0"
serde = "1.0.164"
nu-pretty-hex = { version = "0.87.1", path = "../nu-pretty-hex" }
nu-json = { version = "0.87.1", path = "../nu-json" }
nu-pretty-hex = { version = "0.87.2", path = "../nu-pretty-hex" }
nu-json = { version = "0.87.2", path = "../nu-json" }
serde_urlencoded = "0.7.1"
htmlescape = "0.3.1"
@ -37,6 +37,6 @@ extra = ["default"]
default = []
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
nu-command = { path = "../nu-command", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-command = { path = "../nu-command", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }

View File

@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021"
license = "MIT"
name = "nu-cmd-lang"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-ansi-term = "0.49.0"
fancy-regex = "0.11"

View File

@ -1,65 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct DefEnv;
impl Command for DefEnv {
fn name(&self) -> &str {
"def-env"
}
fn usage(&self) -> &str {
"Define a custom command, which participates in the caller environment."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("def-env")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required("block", SyntaxShape::Block, "body of the definition")
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html
"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated command".into(),
"`def-env` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Use `def --env` instead".into()),
vec![],
),
);
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set environment variable by call a custom command",
example: r#"def-env foo [] { $env.BAR = "BAZ" }; foo; $env.BAR"#,
result: Some(Value::test_string("BAZ")),
}]
}
}

View File

@ -1,94 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct ExportDefEnv;
impl Command for ExportDefEnv {
fn name(&self) -> &str {
"export def-env"
}
fn usage(&self) -> &str {
"Define a custom command that participates in the environment and export it from a module."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export def-env")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required("block", SyntaxShape::Block, "body of the definition")
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated command".into(),
"`export def-env` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Use `export def --env` instead".into()),
vec![],
),
);
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Define a custom command that participates in the environment in a module and call it",
example: r#"module foo { export def-env bar [] { $env.FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
result: Some(Value::test_string("BAZ")),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["module"]
}
}

View File

@ -1,66 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
#[derive(Clone)]
pub struct ExportExternWrapped;
impl Command for ExportExternWrapped {
fn name(&self) -> &str {
"export extern-wrapped"
}
fn usage(&self) -> &str {
"Define an extern with a custom code block and export it from a module."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("export extern-wrapped")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required("body", SyntaxShape::Block, "wrapper code block")
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated command".into(),
"`export extern-wrapped` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Use `export def --wrapped` instead".into()),
vec![],
),
);
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Export the signature for an external command",
example: r#"export extern-wrapped my-echo [...rest] { echo $rest }"#,
result: None,
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["signature", "module", "declare"]
}
}

View File

@ -1,75 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct ExternWrapped;
impl Command for ExternWrapped {
fn name(&self) -> &str {
"extern-wrapped"
}
fn usage(&self) -> &str {
"Define a signature for an external command with a custom code block."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("extern-wrapped")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")
.required("body", SyntaxShape::Block, "wrapper code block")
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated command".into(),
"`extern-wrapped` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Use `def --wrapped` instead".into()),
vec![],
),
);
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Define a custom wrapper for an external command",
example: r#"extern-wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
result: Some(Value::test_list(vec![Value::test_string("spam")])),
}]
}
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::ExternWrapped;
use crate::test_examples;
test_examples(ExternWrapped {})
}
}

View File

@ -4,7 +4,6 @@ mod collect;
mod const_;
mod continue_;
mod def;
mod def_env;
mod describe;
mod do_;
mod echo;
@ -13,13 +12,10 @@ mod export;
mod export_alias;
mod export_const;
mod export_def;
mod export_def_env;
mod export_extern;
mod export_extern_wrapped;
mod export_module;
mod export_use;
mod extern_;
mod extern_wrapped;
mod for_;
mod hide;
mod hide_env;
@ -45,7 +41,6 @@ pub use collect::Collect;
pub use const_::Const;
pub use continue_::Continue;
pub use def::Def;
pub use def_env::DefEnv;
pub use describe::Describe;
pub use do_::Do;
pub use echo::Echo;
@ -54,13 +49,10 @@ pub use export::ExportCommand;
pub use export_alias::ExportAlias;
pub use export_const::ExportConst;
pub use export_def::ExportDef;
pub use export_def_env::ExportDefEnv;
pub use export_extern::ExportExtern;
pub use export_extern_wrapped::ExportExternWrapped;
pub use export_module::ExportModule;
pub use export_use::ExportUse;
pub use extern_::Extern;
pub use extern_wrapped::ExternWrapped;
pub use for_::For;
pub use hide::Hide;
pub use hide_env::HideEnv;

View File

@ -22,7 +22,6 @@ pub fn create_default_context() -> EngineState {
Const,
Continue,
Def,
DefEnv,
Describe,
Do,
Echo,
@ -31,13 +30,10 @@ pub fn create_default_context() -> EngineState {
ExportCommand,
ExportConst,
ExportDef,
ExportDefEnv,
ExportExtern,
ExportExternWrapped,
ExportUse,
ExportModule,
Extern,
ExternWrapped,
For,
Hide,
HideEnv,

View File

@ -14,8 +14,7 @@ mod test_examples {
check_example_input_and_output_types_match_command_signature,
};
use crate::{
Break, Collect, Def, DefEnv, Describe, Echo, ExportCommand, ExportDef, ExportDefEnv, If,
Let, Module, Mut, Use,
Break, Collect, Def, Describe, Echo, ExportCommand, ExportDef, If, Let, Module, Mut, Use,
};
use nu_protocol::{
engine::{Command, EngineState, StateWorkingSet},
@ -69,12 +68,10 @@ mod test_examples {
working_set.add_decl(Box::new(Break));
working_set.add_decl(Box::new(Collect));
working_set.add_decl(Box::new(Def));
working_set.add_decl(Box::new(DefEnv));
working_set.add_decl(Box::new(Describe));
working_set.add_decl(Box::new(Echo));
working_set.add_decl(Box::new(ExportCommand));
working_set.add_decl(Box::new(ExportDef));
working_set.add_decl(Box::new(ExportDefEnv));
working_set.add_decl(Box::new(If));
working_set.add_decl(Box::new(Let));
working_set.add_decl(Box::new(Module));

View File

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

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.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -14,19 +14,19 @@ bench = false
[dependencies]
nu-ansi-term = "0.49.0"
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-glob = { path = "../nu-glob", version = "0.87.1" }
nu-json = { path = "../nu-json", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-system = { path = "../nu-system", version = "0.87.1" }
nu-table = { path = "../nu-table", version = "0.87.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-glob = { path = "../nu-glob", version = "0.87.2" }
nu-json = { path = "../nu-json", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-system = { path = "../nu-system", version = "0.87.2" }
nu-table = { path = "../nu-table", version = "0.87.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
alphanumeric-sort = "1.5"
base64 = "0.21"
@ -47,6 +47,7 @@ filesize = "0.2"
filetime = "0.2"
fs_extra = "1.3"
htmlescape = "0.3"
human-date-parser = "0.1.1"
indexmap = "2.1"
indicatif = "0.17"
itertools = "0.11"
@ -67,7 +68,7 @@ os_pipe = "1.1"
pathdiff = "0.2"
percent-encoding = "2.3"
print-positions = "0.6"
quick-xml = "0.30"
quick-xml = "0.31"
rand = "0.8"
rayon = "1.8"
regex = "1.9.5"
@ -87,23 +88,27 @@ toml = "0.8"
unicode-segmentation = "1.10"
ureq = { version = "2.8", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
url = "2.2"
uu_cp = "0.0.22"
uu_whoami = "0.0.22"
uu_mkdir = "0.0.22"
uuid = { version = "1.5", features = ["v4"] }
uu_cp = "0.0.23"
uu_whoami = "0.0.23"
uu_mkdir = "0.0.23"
uu_mktemp = "0.0.23"
uuid = { version = "1.6", features = ["v4"] }
wax = { version = "0.6" }
which = { version = "5.0", optional = true }
bracoxide = "0.1.2"
chardetng = "0.1.17"
[target.'cfg(windows)'.dependencies]
winreg = "0.51"
winreg = "0.52"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
umask = "2.1"
nix = { version = "0.27", default-features = false, features = ["user"] }
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android"), not(target_os = "ios")))'.dependencies]
procfs = "0.16.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
optional = true
version = "3.1"
@ -126,8 +131,8 @@ trash-support = ["trash"]
which-support = ["which"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
dirs-next = "2.0"
mockito = { version = "1.2", default-features = false }

View File

@ -187,12 +187,14 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
let val = if cfg!(target_endian = "little") {
match val.iter().rposition(|&x| x != 0) {
Some(idx) => &val[..idx + 1],
None => &val,
// all 0s should just return a single 0 byte
None => &[0],
}
} else {
match val.iter().position(|&x| x != 0) {
Some(idx) => &val[idx..],
None => &val,
None => &[0],
}
};

View File

@ -1,13 +1,14 @@
use crate::{generate_strftime_list, parse_date_from_string};
use chrono::NaiveTime;
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
use human_date_parser::{from_human_time, ParseResult};
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
struct Arguments {
@ -95,6 +96,11 @@ impl Command for SubCommand {
"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,
@ -112,6 +118,8 @@ impl Command for SubCommand {
) -> Result<PipelineData, ShellError> {
if call.has_flag("list") {
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
} else if call.has_flag("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);
@ -225,6 +233,21 @@ impl Command for SubCommand {
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,
},
]
}
}
@ -241,7 +264,33 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
if let Ok(input_val) = input.as_spanned_string() {
match parse_date_from_string(&input_val.item, input_val.span) {
Ok(date) => return Value::date(date, input_val.span),
Err(err) => err,
Err(_) => {
if let Ok(date) = from_human_time(&input_val.item) {
match date {
ParseResult::Date(date) => {
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
let combined = date.and_time(time);
let dt_fixed = DateTime::from_naive_utc_and_offset(
combined,
*Local::now().offset(),
);
return Value::date(dt_fixed, input_val.span);
}
ParseResult::DateTime(date) => {
return Value::date(date.fixed_offset(), input_val.span)
}
ParseResult::Time(time) => {
let date = Local::now().date_naive();
let combined = date.and_time(time);
let dt_fixed = DateTime::from_naive_utc_and_offset(
combined,
*Local::now().offset(),
);
return Value::date(dt_fixed, input_val.span);
}
}
}
}
};
}
}
@ -362,6 +411,44 @@ 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(),
];
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>>();
Value::list(records, span)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -22,7 +22,15 @@ impl Command for ViewFiles {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("view files")
.input_output_types(vec![(Type::Nothing, Type::String)])
.input_output_types(vec![(
Type::Nothing,
Type::Table(vec![
("filename".into(), Type::String),
("start".into(), Type::Int),
("end".into(), Type::Int),
("size".into(), Type::Int),
]),
)])
.category(Category::Debug)
}
@ -51,10 +59,17 @@ impl Command for ViewFiles {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "View the files registered in nushell's EngineState memory",
example: r#"view files"#,
result: None,
}]
vec![
Example {
description: "View the files registered in Nushell's EngineState memory",
example: r#"view files"#,
result: None,
},
Example {
description: "View how Nushell was originally invoked",
example: r#"view files | get 0"#,
result: None,
},
]
}
}

View File

@ -169,7 +169,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
EncodeBase64,
DetectColumns,
Parse,
Size,
Split,
SplitChars,
SplitColumn,
@ -202,7 +201,8 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Cd,
Ls,
Mkdir,
UMkdir,
UMkdir,
Mktemp,
Mv,
Cp,
UCp,
@ -382,7 +382,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Seq,
SeqDate,
SeqChar,
Unfold, // deprecated
Generate,
};

View File

@ -43,12 +43,6 @@ impl Command for Glob {
"Whether to filter out symlinks from the returned paths",
Some('S'),
)
.named(
"not",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"DEPRECATED OPTION: Patterns to exclude from the results",
Some('n'),
)
.named(
"exclude",
SyntaxShape::List(Box::new(SyntaxShape::String)),
@ -147,35 +141,7 @@ impl Command for Glob {
let no_files = call.has_flag("no-file");
let no_symlinks = call.has_flag("no-symlink");
if call.has_flag("not") {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated option".into(),
"`glob --not {list<string>}` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Please use `glob --exclude {list<string>}` instead.".into()),
vec![],
),
);
}
let not_flag: Option<Value> = call.get_flag(engine_state, stack, "not")?;
let exclude_flag: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
let paths_to_exclude = match (not_flag, exclude_flag) {
(Some(not_flag), Some(exclude_flag)) => {
return Err(ShellError::IncompatibleParameters {
left_message: "Cannot pass --not".into(),
left_span: not_flag.span(),
right_message: "and --exclude".into(),
right_span: exclude_flag.span(),
})
}
(Some(not_flag), None) => Some(not_flag),
(None, Some(exclude_flag)) => Some(exclude_flag),
(None, None) => None,
};
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 {
None => (vec![], span),

View File

@ -0,0 +1,129 @@
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use std::path::PathBuf;
#[derive(Clone)]
pub struct Mktemp;
impl Command for Mktemp {
fn name(&self) -> &str {
"mktemp"
}
fn usage(&self) -> &str {
"Create temporary files or directories using uutils/coreutils mktemp."
}
fn search_terms(&self) -> Vec<&str> {
vec![
"coreutils",
"create",
"directory",
"file",
"folder",
"temporary",
]
}
fn signature(&self) -> Signature {
Signature::build("mktemp")
.input_output_types(vec![(Type::Nothing, Type::String)])
.optional(
"template",
SyntaxShape::String,
"Optional pattern from which the name of the file or directory is derived. Must contain at least three 'X's in last component.",
)
.named("suffix", SyntaxShape::String, "Append suffix to template; must not contain a slash.", None)
.named("tmpdir-path", SyntaxShape::Filepath, "Interpret TEMPLATE relative to tmpdir-path. If tmpdir-path is not set use $TMPDIR", Some('p'))
.switch("tmpdir", "Interpret TEMPLATE relative to the system temporary directory.", Some('t'))
.switch("directory", "Create a directory instead of a file.", Some('d'))
.category(Category::FileSystem)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Make a temporary file with the given suffix in the current working directory.",
example: "mktemp --suffix .txt",
result: Some(Value::test_string("<WORKING_DIR>/tmp.lekjbhelyx.txt")),
},
Example {
description: "Make a temporary file named testfile.XXX with the 'X's as random characters in the current working directory.",
example: "mktemp testfile.XXX",
result: Some(Value::test_string("<WORKING_DIR>/testfile.4kh")),
},
Example {
description: "Make a temporary file with a template in the system temp directory.",
example: "mktemp -t testfile.XXX",
result: Some(Value::test_string("/tmp/testfile.4kh")),
},
Example {
description: "Make a temporary directory with randomly generated name in the temporary directory.",
example: "mktemp -d",
result: Some(Value::test_string("/tmp/tmp.NMw9fJr8K0")),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let template = call
.rest(engine_state, stack, 0)?
.first()
.cloned()
.map(|i: Spanned<String>| i.item)
.unwrap_or("tmp.XXXXXXXXXX".to_string()); // same as default in coreutils
let directory = call.has_flag("directory");
let suffix = call.get_flag(engine_state, stack, "suffix")?;
let tmpdir = call.has_flag("tmpdir");
let tmpdir_path = call
.get_flag(engine_state, stack, "tmpdir-path")?
.map(|i: Spanned<PathBuf>| i.item);
let tmpdir = if tmpdir_path.is_some() {
tmpdir_path
} else if directory || tmpdir {
Some(std::env::temp_dir())
} else {
Some(current_dir(engine_state, stack)?)
};
let options = uu_mktemp::Options {
directory,
dry_run: false,
quiet: false,
suffix,
template,
tmpdir,
treat_as_template: true,
};
let res = match uu_mktemp::mktemp(&options) {
Ok(res) => res
.into_os_string()
.into_string()
.map_err(|e| ShellError::IOErrorSpanned(e.to_string_lossy().to_string(), span))?,
Err(e) => {
return Err(ShellError::GenericError(
format!("{}", e),
format!("{}", e),
None,
None,
Vec::new(),
));
}
};
Ok(PipelineData::Value(Value::string(res, span), None))
}
}

View File

@ -3,6 +3,7 @@ mod cp;
mod glob;
mod ls;
mod mkdir;
mod mktemp;
mod mv;
mod open;
mod rm;
@ -20,6 +21,7 @@ pub use cp::Cp;
pub use glob::Glob;
pub use ls::Ls;
pub use mkdir::Mkdir;
pub use mktemp::Mktemp;
pub use mv::Mv;
pub use rm::Rm;
pub use save::Save;

View File

@ -304,8 +304,9 @@ fn highlight_terms_in_record_with_search_columns(
let val_str = val.into_string("", config);
let Some(term_str) = term_strs
.iter()
.find(|term_str| contains_ignore_case(&val_str, term_str)) else {
continue;
.find(|term_str| contains_ignore_case(&val_str, term_str))
else {
continue;
};
let highlighted_str =

View File

@ -271,7 +271,7 @@ fn group_closure(
));
}
let value = match collection.get(0) {
let value = match collection.first() {
Some(Value::Error { .. }) | None => Value::string(error_key, span),
Some(return_value) => return_value.clone(),
};

View File

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

View File

@ -221,7 +221,7 @@ fn reject(
let mut new_rows = vec![];
for column in cell_paths {
let CellPath { ref members } = column;
match members.get(0) {
match members.first() {
Some(PathMember::Int { val, span, .. }) => {
if members.len() > 1 {
return Err(ShellError::GenericError(

View File

@ -212,7 +212,7 @@ fn select(
for column in columns {
let CellPath { ref members } = column;
match members.get(0) {
match members.first() {
Some(PathMember::Int { val, span, .. }) => {
if members.len() > 1 {
return Err(ShellError::GenericError(

View File

@ -190,7 +190,7 @@ pub fn transpose(
if args.header_row {
for i in input.iter() {
if let Some(desc) = descs.get(0) {
if let Some(desc) = descs.first() {
match &i.get_data_by_key(desc) {
Some(x) => {
if let Ok(s) = x.as_string() {

View File

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

View File

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

View File

@ -59,7 +59,7 @@ impl Command for FromNuon {
let mut block = nu_parser::parse(&mut working_set, None, string_input.as_bytes(), false);
if let Some(pipeline) = block.pipelines.get(1) {
if let Some(element) = pipeline.elements.get(0) {
if let Some(element) = pipeline.elements.first() {
return Err(ShellError::GenericError(
"error when loading nuon text".into(),
"could not load nuon text".into(),

View File

@ -3,11 +3,9 @@ mod generate;
mod seq;
mod seq_char;
mod seq_date;
mod unfold;
pub use cal::Cal;
pub use generate::Generate;
pub use seq::Seq;
pub use seq_char::SeqChar;
pub use seq_date::SeqDate;
pub use unfold::Unfold;

View File

@ -1,242 +0,0 @@
use itertools::unfold;
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct Unfold;
impl Command for Unfold {
fn name(&self) -> &str {
"unfold"
}
fn signature(&self) -> Signature {
Signature::build("unfold")
.input_output_types(vec![
(Type::Nothing, Type::List(Box::new(Type::Any))),
(
Type::List(Box::new(Type::Any)),
Type::List(Box::new(Type::Any)),
),
])
.required("initial", SyntaxShape::Any, "initial value")
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"generator function",
)
.allow_variants_without_examples(true)
.category(Category::Generators)
}
fn usage(&self) -> &str {
"Generate a list of values by successively invoking a closure."
}
fn extra_usage(&self) -> &str {
r#"The generator closure accepts a single argument and returns a record
containing two optional keys: 'out' and 'next'. Each invocation, the 'out'
value, if present, is added to the stream. If a 'next' key is present, it is
used as the next argument to the closure, otherwise generation stops.
"#
}
fn search_terms(&self) -> Vec<&str> {
vec!["generate", "stream"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "unfold 0 {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }}",
description: "Generate a sequence of numbers",
result: Some(Value::list(
vec![
Value::test_int(0),
Value::test_int(2),
Value::test_int(4),
Value::test_int(6),
Value::test_int(8),
Value::test_int(10),
],
Span::test_data(),
)),
},
Example {
example: "unfold [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } | first 10",
description: "Generate a stream of fibonacci numbers",
result: Some(Value::list(
vec![
Value::test_int(0),
Value::test_int(1),
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_int(5),
Value::test_int(8),
Value::test_int(13),
Value::test_int(21),
Value::test_int(34),
],
Span::test_data(),
)),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated option".into(),
"`unfold` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Please use `generate` instead.".into()),
vec![],
),
);
let initial: Value = call.req(engine_state, stack, 0)?;
let capture_block: Spanned<Closure> = call.req(engine_state, stack, 1)?;
let block_span = capture_block.span;
let block = engine_state.get_block(capture_block.item.block_id).clone();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let mut stack = stack.captures_to_stack(capture_block.item.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
// A type of Option<S> is used to represent state. Invocation
// will stop on None. Using Option<S> allows functions to output
// one final value before stopping.
let iter = unfold(Some(initial), move |state| {
let arg = match state {
Some(state) => state.clone(),
None => return None,
};
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.
// Hence, a 'cd' in the first loop won't affect the next loop.
stack.with_env(&orig_env_vars, &orig_env_hidden);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, arg.clone());
}
}
let (output, next_input) = match eval_block_with_early_return(
&engine_state,
&mut stack,
&block,
arg.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
// no data -> output nothing and stop.
Ok(PipelineData::Empty) => (None, None),
Ok(PipelineData::Value(value, ..)) => {
let span = value.span();
match value {
// {out: ..., next: ...} -> output and continue
Value::Record { val, .. } => {
let iter = val.into_iter();
let mut out = None;
let mut next = None;
let mut err = None;
for (k, v) in iter {
if k.eq_ignore_ascii_case("out") {
out = Some(v);
} else if k.eq_ignore_ascii_case("next") {
next = Some(v);
} else {
let error = ShellError::GenericError(
"Invalid block return".to_string(),
format!("Unexpected record key '{}'", k),
Some(span),
None,
Vec::new(),
);
err = Some(Value::error(error, block_span));
break;
}
}
if err.is_some() {
(err, None)
} else {
(out, next)
}
}
// some other value -> error and stop
_ => {
let error = ShellError::GenericError(
"Invalid block return".to_string(),
format!("Expected record, found {}", value.get_type()),
Some(span),
None,
Vec::new(),
);
(Some(Value::error(error, block_span)), None)
}
}
}
Ok(other) => {
let val = other.into_value(block_span);
let error = ShellError::GenericError(
"Invalid block return".to_string(),
format!("Expected record, found {}", val.get_type()),
Some(val.span()),
None,
Vec::new(),
);
(Some(Value::error(error, block_span)), None)
}
// error -> error and stop
Err(error) => (Some(Value::error(error, block_span)), None),
};
// We use `state` to control when to stop, not `output`. By wrapping
// it in a `Some`, we allow the generator to output `None` as a valid output
// value.
*state = next_input;
Some(output)
});
Ok(iter.flatten().into_pipeline_data(ctrlc))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Unfold {})
}
}

View File

@ -95,7 +95,7 @@ You can also learn more at https://www.nushell.sh/book/"#;
result
};
let result = if let Err(ShellError::CommandNotFound(_)) = result {
let result = if let Err(ShellError::CommandNotFound { .. }) = result {
help_modules(engine_state, stack, call)
} else {
result

View File

@ -114,10 +114,9 @@ pub fn help_commands(
.into_pipeline_data(),
)
} else {
Err(ShellError::CommandNotFound(span(&[
rest[0].span,
rest[rest.len() - 1].span,
])))
Err(ShellError::CommandNotFound {
span: span(&[rest[0].span, rest[rest.len() - 1].span]),
})
}
}
}

View File

@ -133,10 +133,9 @@ pub fn help_externs(
.into_pipeline_data(),
)
} else {
Err(ShellError::CommandNotFound(span(&[
rest[0].span,
rest[rest.len() - 1].span,
])))
Err(ShellError::CommandNotFound {
span: span(&[rest[0].span, rest[rest.len() - 1].span]),
})
}
}
}

View File

@ -79,7 +79,7 @@ pub fn min(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
}
pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let initial_value = data.get(0);
let initial_value = data.first();
let mut acc = match initial_value {
Some(v) => {
@ -124,7 +124,7 @@ pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
}
pub fn product(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let initial_value = data.get(0);
let initial_value = data.first();
let mut acc = match initial_value {
Some(v) => {

View File

@ -363,38 +363,26 @@ pub fn request_add_custom_headers(
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
match response_err {
Error::Status(301, _) => ShellError::NetworkFailure(
format!("Resource moved permanently (301): {requested_url:?}"),
span,
),
Error::Status(301, _) => ShellError::NetworkFailure { msg: format!("Resource moved permanently (301): {requested_url:?}"), span },
Error::Status(400, _) => {
ShellError::NetworkFailure(format!("Bad request (400) to {requested_url:?}"), span)
ShellError::NetworkFailure { msg: format!("Bad request (400) to {requested_url:?}"), span }
}
Error::Status(403, _) => {
ShellError::NetworkFailure(format!("Access forbidden (403) to {requested_url:?}"), span)
ShellError::NetworkFailure { msg: format!("Access forbidden (403) to {requested_url:?}"), span }
}
Error::Status(404, _) => ShellError::NetworkFailure(
format!("Requested file not found (404): {requested_url:?}"),
span,
),
Error::Status(404, _) => ShellError::NetworkFailure { msg: format!("Requested file not found (404): {requested_url:?}"), span },
Error::Status(408, _) => {
ShellError::NetworkFailure(format!("Request timeout (408): {requested_url:?}"), span)
ShellError::NetworkFailure { msg: format!("Request timeout (408): {requested_url:?}"), span }
}
Error::Status(_, _) => ShellError::NetworkFailure(
format!(
Error::Status(_, _) => ShellError::NetworkFailure { msg: format!(
"Cannot make request to {:?}. Error is {:?}",
requested_url,
response_err.to_string()
),
span,
),
), span },
Error::Transport(t) => match t {
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure(
format!("Cannot make request to {requested_url}, there was an error establishing a connection.",),
span,
),
t => ShellError::NetworkFailure(t.to_string(), span),
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure { msg: format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), span },
t => ShellError::NetworkFailure { msg: t.to_string(), span },
},
}
}

View File

@ -3,7 +3,6 @@ mod detect_columns;
mod encode_decode;
mod format;
mod parse;
mod size;
mod split;
mod str_;
@ -12,7 +11,6 @@ pub use detect_columns::*;
pub use encode_decode::*;
pub use format::*;
pub use parse::*;
pub use size::Size;
pub use split::*;
pub use str_::*;

View File

@ -1,381 +0,0 @@
use fancy_regex::Regex;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
};
use std::collections::BTreeMap;
use std::{fmt, str};
use unicode_segmentation::UnicodeSegmentation;
// borrowed liberally from here https://github.com/dead10ck/uwc
pub type Counted = BTreeMap<Counter, usize>;
#[derive(Clone)]
pub struct Size;
impl Command for Size {
fn name(&self) -> &str {
"size"
}
fn signature(&self) -> Signature {
Signature::build("size")
.category(Category::Strings)
.input_output_types(vec![(Type::String, Type::Record(vec![]))])
}
fn usage(&self) -> &str {
"Gather word count statistics on the text."
}
fn search_terms(&self) -> Vec<&str> {
vec!["count", "word", "character", "unicode", "wc"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
"Deprecated command".into(),
"`size` is deprecated and will be removed in 0.88.".into(),
Some(call.head),
Some("Use `str stats` instead".into()),
vec![],
),
);
size(engine_state, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Count the number of words in a string",
example: r#""There are seven words in this sentence" | size"#,
result: Some(Value::test_record(record! {
"lines" => Value::test_int(1),
"words" => Value::test_int(7),
"bytes" => Value::test_int(38),
"chars" => Value::test_int(38),
"graphemes" => Value::test_int(38),
})),
},
Example {
description: "Counts unicode characters",
example: r#"'今天天气真好' | size "#,
result: Some(Value::test_record(record! {
"lines" => Value::test_int(1),
"words" => Value::test_int(6),
"bytes" => Value::test_int(18),
"chars" => Value::test_int(6),
"graphemes" => Value::test_int(6),
})),
},
Example {
description: "Counts Unicode characters correctly in a string",
example: r#""Amélie Amelie" | size"#,
result: Some(Value::test_record(record! {
"lines" => Value::test_int(1),
"words" => Value::test_int(2),
"bytes" => Value::test_int(15),
"chars" => Value::test_int(14),
"graphemes" => Value::test_int(13),
})),
},
]
}
}
fn size(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: span });
}
input.map(
move |v| {
let value_span = v.span();
// First, obtain the span. If this fails, propagate the error that results.
if let Value::Error { error, .. } = v {
return Value::error(*error, span);
}
// Now, check if it's a string.
match v.as_string() {
Ok(s) => counter(&s, span),
Err(_) => Value::error(
ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: span,
src_span: value_span,
},
span,
),
}
},
engine_state.ctrlc.clone(),
)
}
fn counter(contents: &str, span: Span) -> Value {
let counts = uwc_count(&ALL_COUNTERS[..], contents);
fn get_count(counts: &BTreeMap<Counter, usize>, counter: Counter, span: Span) -> Value {
Value::int(counts.get(&counter).copied().unwrap_or(0) as i64, span)
}
let record = record! {
"lines" => get_count(&counts, Counter::Lines, span),
"words" => get_count(&counts, Counter::Words, span),
"bytes" => get_count(&counts, Counter::Bytes, span),
"chars" => get_count(&counts, Counter::CodePoints, span),
"graphemes" => get_count(&counts, Counter::GraphemeClusters, span),
};
Value::record(record, span)
}
/// Take all the counts in `other_counts` and sum them into `accum`.
// pub fn sum_counts(accum: &mut Counted, other_counts: &Counted) {
// for (counter, count) in other_counts {
// let entry = accum.entry(*counter).or_insert(0);
// *entry += count;
// }
// }
/// Sums all the `Counted` instances into a new one.
// pub fn sum_all_counts<'a, I>(counts: I) -> Counted
// where
// I: IntoIterator<Item = &'a Counted>,
// {
// let mut totals = BTreeMap::new();
// for counts in counts {
// sum_counts(&mut totals, counts);
// }
// totals
// }
/// Something that counts things in `&str`s.
pub trait Count {
/// Counts something in the given `&str`.
fn count(&self, s: &str) -> usize;
}
impl Count for Counter {
fn count(&self, s: &str) -> usize {
match *self {
Counter::GraphemeClusters => s.graphemes(true).count(),
Counter::Bytes => s.len(),
Counter::Lines => {
const LF: &str = "\n"; // 0xe0000a
const CR: &str = "\r"; // 0xe0000d
const CRLF: &str = "\r\n"; // 0xe00d0a
const NEL: &str = "\u{0085}"; // 0x00c285
const FF: &str = "\u{000C}"; // 0x00000c
const LS: &str = "\u{2028}"; // 0xe280a8
const PS: &str = "\u{2029}"; // 0xe280a9
// use regex here because it can search for CRLF first and not duplicate the count
let line_ending_types = [CRLF, LF, CR, NEL, FF, LS, PS];
let pattern = &line_ending_types.join("|");
let newline_pattern = Regex::new(pattern).expect("Unable to create regex");
let line_endings = newline_pattern
.find_iter(s)
.map(|f| match f {
Ok(mat) => mat.as_str().to_string(),
Err(_) => "".to_string(),
})
.collect::<Vec<String>>();
let has_line_ending_suffix =
line_ending_types.iter().any(|&suffix| s.ends_with(suffix));
// eprintln!("suffix = {}", has_line_ending_suffix);
if has_line_ending_suffix {
line_endings.len()
} else {
line_endings.len() + 1
}
}
Counter::Words => s.unicode_words().count(),
Counter::CodePoints => s.chars().count(),
}
}
}
/// Different types of counters.
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Counter {
/// Counts lines.
Lines,
/// Counts words.
Words,
/// Counts the total number of bytes.
Bytes,
/// Counts grapheme clusters. The input is required to be valid UTF-8.
GraphemeClusters,
/// Counts unicode code points
CodePoints,
}
/// A convenience array of all counter types.
pub const ALL_COUNTERS: [Counter; 5] = [
Counter::GraphemeClusters,
Counter::Bytes,
Counter::Lines,
Counter::Words,
Counter::CodePoints,
];
impl fmt::Display for Counter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match *self {
Counter::GraphemeClusters => "graphemes",
Counter::Bytes => "bytes",
Counter::Lines => "lines",
Counter::Words => "words",
Counter::CodePoints => "codepoints",
};
write!(f, "{s}")
}
}
/// Counts the given `Counter`s in the given `&str`.
pub fn uwc_count<'a, I>(counters: I, s: &str) -> Counted
where
I: IntoIterator<Item = &'a Counter>,
{
let mut counts: Counted = counters.into_iter().map(|c| (*c, c.count(s))).collect();
if let Some(lines) = counts.get_mut(&Counter::Lines) {
if s.is_empty() {
// If s is empty, indeed, the count is 0
*lines = 0;
} else if *lines == 0 && !s.is_empty() {
// If s is not empty and the count is 0, it means there
// is a line without a line ending, so let's make it 1
*lines = 1;
} else {
// no change, whatever the count is, is right
}
}
counts
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Size {})
}
}
#[test]
fn test_one_newline() {
let s = "\n".to_string();
let counts = uwc_count(&ALL_COUNTERS[..], &s);
let mut correct_counts = BTreeMap::new();
correct_counts.insert(Counter::Lines, 1);
correct_counts.insert(Counter::Words, 0);
correct_counts.insert(Counter::GraphemeClusters, 1);
correct_counts.insert(Counter::Bytes, 1);
correct_counts.insert(Counter::CodePoints, 1);
assert_eq!(correct_counts, counts);
}
#[test]
fn test_count_counts_lines() {
// const LF: &str = "\n"; // 0xe0000a
// const CR: &str = "\r"; // 0xe0000d
// const CRLF: &str = "\r\n"; // 0xe00d0a
const NEL: &str = "\u{0085}"; // 0x00c285
const FF: &str = "\u{000C}"; // 0x00000c
const LS: &str = "\u{2028}"; // 0xe280a8
const PS: &str = "\u{2029}"; // 0xe280a9
// * \r\n is a single grapheme cluster
// * trailing newlines are counted
// * NEL is 2 bytes
// * FF is 1 byte
// * LS is 3 bytes
// * PS is 3 bytes
let mut s = String::from("foo\r\nbar\n\nbaz");
s += NEL;
s += "quux";
s += FF;
s += LS;
s += "xi";
s += PS;
s += "\n";
let counts = uwc_count(&ALL_COUNTERS[..], &s);
let mut correct_counts = BTreeMap::new();
correct_counts.insert(Counter::Lines, 8);
correct_counts.insert(Counter::Words, 5);
correct_counts.insert(Counter::GraphemeClusters, 23);
correct_counts.insert(Counter::Bytes, 29);
// one more than grapheme clusters because of \r\n
correct_counts.insert(Counter::CodePoints, 24);
assert_eq!(correct_counts, counts);
}
#[test]
fn test_count_counts_words() {
let i_can_eat_glass = "Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα.";
let s = String::from(i_can_eat_glass);
let counts = uwc_count(&ALL_COUNTERS[..], &s);
let mut correct_counts = BTreeMap::new();
correct_counts.insert(Counter::GraphemeClusters, 50);
correct_counts.insert(Counter::Lines, 1);
correct_counts.insert(Counter::Bytes, i_can_eat_glass.len());
correct_counts.insert(Counter::Words, 9);
correct_counts.insert(Counter::CodePoints, 50);
assert_eq!(correct_counts, counts);
}
#[test]
fn test_count_counts_codepoints() {
// these are NOT the same! One is e + ́́ , and one is é, a single codepoint
let one = "é";
let two = "";
let counters = [Counter::CodePoints];
let counts = uwc_count(&counters[..], one);
let mut correct_counts = BTreeMap::new();
correct_counts.insert(Counter::CodePoints, 1);
assert_eq!(correct_counts, counts);
let counts = uwc_count(&counters[..], two);
let mut correct_counts = BTreeMap::new();
correct_counts.insert(Counter::CodePoints, 2);
assert_eq!(correct_counts, counts);
}

View File

@ -14,6 +14,14 @@ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Type, Value,
};
#[cfg(all(
unix,
not(target_os = "macos"),
not(target_os = "windows"),
not(target_os = "android"),
not(target_os = "ios")
))]
use procfs::WithCurrentSystemInfo;
use std::time::Duration;
@ -127,13 +135,11 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result<PipelineData, Shell
Vec::new(),
)
})?;
let proc_start = match proc_stat.starttime() {
Ok(t) => t,
Err(_) => {
// If we can't get the start time, just use the current time
chrono::Local::now()
}
};
// If we can't get the start time, just use the current time
let proc_start = proc_stat
.starttime()
.get()
.unwrap_or_else(|_| chrono::Local::now());
record.push("start_time", Value::date(proc_start.into(), span));
record.push("user_id", Value::int(proc.curr_proc.owner() as i64, span));
// These work and may be helpful, but it just seemed crowded

View File

@ -1,14 +1,18 @@
// todo: (refactoring) limit get_config() usage to 1 call
// overall reduce the redundant calls to StyleComputer etc.
// the goal is to configure it once...
use lscolors::{LsColors, Style};
use nu_color_config::color_from_hex;
use nu_color_config::{StyleComputer, TextStyle};
use nu_engine::{env::get_config, env_to_string, CallExt};
use nu_protocol::record;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use nu_protocol::{record, TableMode};
use nu_table::common::create_nu_table_config;
use nu_table::{
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
@ -16,6 +20,7 @@ use nu_table::{
};
use nu_utils::get_ls_colors;
use std::io::IsTerminal;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use std::{path::PathBuf, sync::atomic::AtomicBool};
@ -60,12 +65,17 @@ impl Command for Table {
.input_output_types(vec![(Type::Any, Type::Any)])
// TODO: make this more precise: what turns into string and what into raw stream
.named(
"start-number",
SyntaxShape::Int,
"row number to start viewing from",
Some('n'),
"theme",
SyntaxShape::String,
"set a table mode/theme",
Some('t'),
)
.named(
"index",
SyntaxShape::Any,
"enable (true) or disable (false) the #/index column or set the starting index",
Some('i'),
)
.switch("list", "list available table modes/themes", Some('l'))
.named(
"width",
SyntaxShape::Int,
@ -80,7 +90,7 @@ impl Command for Table {
.named(
"expand-deep",
SyntaxShape::Int,
"an expand limit of recursion which will take place",
"an expand limit of recursion which will take place, must be used with --expand",
Some('d'),
)
.switch("flatten", "Flatten simple arrays", None)
@ -101,6 +111,7 @@ impl Command for Table {
"abbreviate the data in the table by truncating the middle part and only showing amount provided on top and bottom",
Some('a'),
)
.switch("list", "list available table modes/themes", Some('l'))
.category(Category::Viewers)
}
@ -112,15 +123,15 @@ impl Command for Table {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let list_themes: bool = call.has_flag("list");
let cfg = parse_table_config(call, engine_state, stack)?;
let input = CmdInput::new(engine_state, stack, call, input);
// if list argument is present we just need to return a list of supported table themes
if list_themes {
let val = Value::list(supported_table_modes(), Span::test_data());
return Ok(val.into_pipeline_data());
}
let cfg = parse_table_config(call, engine_state, stack)?;
let input = CmdInput::new(engine_state, stack, call, input);
// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
{
@ -133,8 +144,8 @@ impl Command for Table {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "List the files in current directory, with indexes starting from 1.",
example: r#"ls | table --start-number 1"#,
description: "List the files in current directory, with indexes starting from 1",
example: r#"ls | table --index 1"#,
result: None,
},
Example {
@ -179,30 +190,54 @@ impl Command for Table {
}),
])),
},
Example {
description: "Change the table theme to the specified theme for a single run",
example: r#"[[a b]; [1 2] [2 [4 4]]] | table --theme basic"#,
result: None,
},
Example {
description: "Force showing of the #/index column for a single run",
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i true"#,
result: None,
},
Example {
description:
"Set the starting number of the #/index column to 100 for a single run",
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i 100"#,
result: None,
},
Example {
description: "Force hiding of the #/index column for a single run",
example: r#"[[a b]; [1 2] [2 [4 4]]] | table -i false"#,
result: None,
},
]
}
}
#[derive(Debug, Clone)]
struct TableConfig {
row_offset: usize,
index: Option<usize>,
table_view: TableView,
term_width: usize,
theme: TableMode,
abbreviation: Option<usize>,
}
impl TableConfig {
fn new(
row_offset: usize,
table_view: TableView,
term_width: usize,
theme: TableMode,
abbreviation: Option<usize>,
index: Option<usize>,
) -> Self {
Self {
row_offset,
index,
table_view,
term_width,
abbreviation,
theme,
}
}
}
@ -212,8 +247,6 @@ fn parse_table_config(
state: &EngineState,
stack: &mut Stack,
) -> Result<TableConfig, ShellError> {
let start_num: Option<i64> = call.get_flag(state, stack, "start-number")?;
let row_offset = start_num.unwrap_or_default() as usize;
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
let expand: bool = call.has_flag("expand");
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
@ -232,13 +265,74 @@ fn parse_table_config(
flatten_separator,
},
};
let theme =
get_theme_flag(call, state, stack)?.unwrap_or_else(|| get_config(state, stack).table_mode);
let index = get_index_flag(call, state, stack)?;
let term_width = get_width_param(width_param);
let cfg = TableConfig::new(row_offset, table_view, term_width, abbrivation);
let cfg = TableConfig::new(table_view, term_width, theme, abbrivation, index);
Ok(cfg)
}
fn get_index_flag(
call: &Call,
state: &EngineState,
stack: &mut Stack,
) -> Result<Option<usize>, ShellError> {
let index: Option<Value> = call.get_flag(state, stack, "index")?;
let value = match index {
Some(value) => value,
None => return Ok(Some(0)),
};
match value {
Value::Bool { val, .. } => {
if val {
Ok(Some(0))
} else {
Ok(None)
}
}
Value::Int { val, internal_span } => {
if val < 0 {
Err(ShellError::UnsupportedInput {
msg: String::from("got a negative integer"),
input: val.to_string(),
msg_span: call.span(),
input_span: internal_span,
})
} else {
Ok(Some(val as usize))
}
}
Value::Nothing { .. } => Ok(Some(0)),
_ => Err(ShellError::CantConvert {
to_type: String::from("index"),
from_type: String::new(),
span: call.span(),
help: Some(String::from("supported values: [bool, int, nothing]")),
}),
}
}
fn get_theme_flag(
call: &Call,
state: &EngineState,
stack: &mut Stack,
) -> Result<Option<TableMode>, ShellError> {
call.get_flag(state, stack, "theme")?
.map(|theme: String| {
TableMode::from_str(&theme).map_err(|err| ShellError::CantConvert {
to_type: String::from("theme"),
from_type: String::from("string"),
span: call.span(),
help: Some(format!("{}, but found '{}'.", err, theme)),
})
})
.transpose()
}
struct CmdInput<'a> {
engine_state: &'a EngineState,
stack: &'a mut Stack,
@ -366,7 +460,17 @@ fn handle_record(
}
let indent = (config.table_indent.left, config.table_indent.right);
let opts = TableOpts::new(&config, styles, ctrlc, span, 0, cfg.term_width, indent);
let opts = TableOpts::new(
&config,
styles,
ctrlc,
span,
cfg.term_width,
indent,
cfg.theme,
cfg.index.unwrap_or(0),
cfg.index.is_none(),
);
let result = build_table_kv(record, cfg.table_view, opts, span)?;
let result = match result {
@ -581,6 +685,7 @@ struct PagingTableCreator {
elements_displayed: usize,
reached_end: bool,
cfg: TableConfig,
row_offset: usize,
}
impl PagingTableCreator {
@ -601,6 +706,7 @@ impl PagingTableCreator {
cfg,
elements_displayed: 0,
reached_end: false,
row_offset: 0,
}
}
@ -657,9 +763,11 @@ impl PagingTableCreator {
style_comp,
self.ctrlc.clone(),
self.head,
self.cfg.row_offset,
self.cfg.term_width,
(cfg.table_indent.left, cfg.table_indent.right),
self.cfg.theme,
self.cfg.index.unwrap_or(0) + self.row_offset,
self.cfg.index.is_none(),
)
}
@ -770,7 +878,7 @@ impl Iterator for PagingTableCreator {
let table = self.build_table(batch);
self.cfg.row_offset += idx;
self.row_offset += idx;
let config = get_config(&self.engine_state, &self.stack);
convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width)
@ -858,7 +966,7 @@ fn create_empty_placeholder(
let out = TableOutput::new(table, false, false);
let style_computer = &StyleComputer::from_config(engine_state, stack);
let config = create_nu_table_config(&config, style_computer, &out, false);
let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());
out.table
.draw(config, termwidth)

View File

@ -269,10 +269,10 @@ fn cd_permission_denied_folder() {
sandbox.mkdir("banned");
let actual = nu!(
cwd: dirs.test(),
r#"
r"
icacls banned /deny BUILTIN\Administrators:F
cd banned
"#
"
);
assert!(actual.err.contains("Folder is not able to read"));
});

View File

@ -87,3 +87,17 @@ fn lazy_record_test_values() {
);
assert_eq!(actual.out, "3");
}
#[test]
fn deep_cell_path_creates_all_nested_records() {
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
assert_eq!(actual.out, "0");
}
#[test]
fn inserts_all_rows_in_table_in_record() {
let actual = nu!(
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
);
assert_eq!(actual.out, "[2, 2]");
}

View File

@ -0,0 +1,45 @@
use nu_test_support::nu;
use nu_test_support::playground::Playground;
use std::path::PathBuf;
#[test]
fn creates_temp_file() {
Playground::setup("mktemp_test_1", |dirs, _| {
let output = nu!(
cwd: dirs.test(),
"mktemp"
);
let loc = PathBuf::from(output.out.clone());
println!("{:?}", loc);
assert!(loc.exists());
})
}
#[test]
fn creates_temp_file_with_suffix() {
Playground::setup("mktemp_test_2", |dirs, _| {
let output = nu!(
cwd: dirs.test(),
"mktemp --suffix .txt tempfileXXX"
);
let loc = PathBuf::from(output.out.clone());
assert!(loc.exists());
assert!(loc.is_file());
assert!(output.out.ends_with(".txt"));
assert!(output.out.starts_with(dirs.test().to_str().unwrap()));
})
}
#[test]
fn creates_temp_directory() {
Playground::setup("mktemp_test_3", |dirs, _| {
let output = nu!(
cwd: dirs.test(),
"mktemp -d"
);
let loc = PathBuf::from(output.out);
assert!(loc.exists());
assert!(loc.is_dir());
})
}

View File

@ -57,6 +57,7 @@ mod match_;
mod math;
mod merge;
mod mkdir;
mod mktemp;
mod move_;
mod mut_;
mod network;

View File

@ -5,11 +5,11 @@ use nu_test_support::{nu, pipeline};
fn parses_single_path_prefix() {
let actual = nu!(
cwd: "tests", pipeline(
r#"
r"
echo 'C:\users\viking\spam.txt'
| path parse
| get prefix
"#
"
));
assert_eq!(actual.out, "C:");

View File

@ -3,7 +3,7 @@ use nu_test_support::nu;
#[test]
fn generates_chars_of_specified_length() {
let actual = nu!(r#"
random chars --length 15 | size | get chars
random chars --length 15 | str stats | get chars
"#);
let result = actual.out;

View File

@ -21,7 +21,7 @@ fn redirect_err() {
Playground::setup("redirect_err_test", |dirs, _sandbox| {
let output = nu!(
cwd: dirs.test(),
"vol missingdrive err> a; (open a | size).bytes >= 16"
"vol missingdrive err> a; (open a | str stats).bytes >= 16"
);
assert!(output.out.contains("true"));
@ -50,7 +50,7 @@ fn redirect_outerr() {
cwd: dirs.test(),
"vol missingdrive out+err> a"
);
let output = nu!(cwd: dirs.test(), "(open a | size).bytes >= 16");
let output = nu!(cwd: dirs.test(), "(open a | str stats).bytes >= 16");
assert!(output.out.contains("true"));
})

File diff suppressed because one or more lines are too long

View File

@ -1,102 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn unfold_no_next_break() {
let actual =
nu!("unfold 1 {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} | to nuon");
assert_eq!(actual.out, "[1, 2, 3]");
}
#[test]
fn unfold_null_break() {
let actual = nu!("unfold 1 {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} | to nuon");
assert_eq!(actual.out, "[1, 2, 3]");
}
#[test]
fn unfold_allows_empty_output() {
let actual = nu!(pipeline(
r#"
unfold 0 {|x|
if $x == 1 {
{next: ($x + 1)}
} else if $x < 3 {
{out: $x, next: ($x + 1)}
}
} | to nuon
"#
));
assert_eq!(actual.out, "[0, 2]");
}
#[test]
fn unfold_allows_no_output() {
let actual = nu!(pipeline(
r#"
unfold 0 {|x|
if $x < 3 {
{next: ($x + 1)}
}
} | to nuon
"#
));
assert_eq!(actual.out, "[]");
}
#[test]
fn unfold_allows_null_state() {
let actual = nu!(pipeline(
r#"
unfold 0 {|x|
if $x == null {
{out: "done"}
} else if $x < 1 {
{out: "going", next: ($x + 1)}
} else {
{out: "stopping", next: null}
}
} | to nuon
"#
));
assert_eq!(actual.out, "[going, stopping, done]");
}
#[test]
fn unfold_allows_null_output() {
let actual = nu!(pipeline(
r#"
unfold 0 {|x|
if $x == 3 {
{out: "done"}
} else {
{out: null, next: ($x + 1)}
}
} | to nuon
"#
));
assert_eq!(actual.out, "[null, null, null, done]");
}
#[test]
fn unfold_disallows_extra_keys() {
let actual = nu!("unfold 0 {|x| {foo: bar, out: $x}}");
assert!(actual.err.contains("Invalid block return"));
}
#[test]
fn unfold_disallows_list() {
let actual = nu!("unfold 0 {|x| [$x, ($x + 1)]}");
assert!(actual.err.contains("Invalid block return"));
}
#[test]
fn unfold_disallows_primitive() {
let actual = nu!("unfold 0 {|x| 1}");
assert!(actual.err.contains("Invalid block return"));
}

View File

@ -90,3 +90,17 @@ fn upsert_support_lazy_record() {
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
assert_eq!(actual.out, "10");
}
#[test]
fn deep_cell_path_creates_all_nested_records() {
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
assert_eq!(actual.out, "0");
}
#[test]
fn upserts_all_rows_in_table_in_record() {
let actual = nu!(
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
);
assert_eq!(actual.out, "[2, 2]");
}

View File

@ -183,6 +183,7 @@ fn from_csv_text_with_tab_separator_to_table() {
}
#[test]
#[allow(clippy::needless_raw_string_hashes)]
fn from_csv_text_with_comments_to_table() {
Playground::setup("filter_from_csv_test_5", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(

View File

@ -106,6 +106,7 @@ fn from_tsv_text_to_table() {
}
#[test]
#[allow(clippy::needless_raw_string_hashes)]
fn from_tsv_text_with_comments_to_table() {
Playground::setup("filter_from_tsv_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(

View File

@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
edition = "2021"
license = "MIT"
name = "nu-engine"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dependencies]
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-glob = { path = "../nu-glob", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-glob = { path = "../nu-glob", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
[features]
plugin = []

View File

@ -795,10 +795,10 @@ fn eval_element_with_input(
)
})
} else {
Err(ShellError::CommandNotFound(*span))
Err(ShellError::CommandNotFound { span: *span })
}
}
_ => Err(ShellError::CommandNotFound(*span)),
_ => Err(ShellError::CommandNotFound { span: *span }),
},
PipelineElement::SeparateRedirection {
out: (out_span, out_expr),
@ -842,14 +842,14 @@ fn eval_element_with_input(
)
})
} else {
Err(ShellError::CommandNotFound(*out_span))
Err(ShellError::CommandNotFound { span: *out_span })
}
}
(_out_other, err_other) => {
if let Expr::String(_) = err_other {
Err(ShellError::CommandNotFound(*out_span))
Err(ShellError::CommandNotFound { span: *out_span })
} else {
Err(ShellError::CommandNotFound(*err_span))
Err(ShellError::CommandNotFound { span: *err_span })
}
}
},

View File

@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
edition = "2021"
license = "MIT"
name = "nu-explore"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dependencies]
nu-ansi-term = "0.49.0"
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-table = { path = "../nu-table", version = "0.87.1" }
nu-json = { path = "../nu-json", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-table = { path = "../nu-table", version = "0.87.2" }
nu-json = { path = "../nu-json", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
terminal_size = "0.2"
strip-ansi-escapes = "0.2.0"

View File

@ -15,7 +15,7 @@ pub use nu_protocol::{Config as NuConfig, Span as NuSpan};
pub type NuText = (String, TextStyle);
pub type CtrlC = Option<Arc<AtomicBool>>;
pub use command::{is_ignored_command, run_command_with_value, run_nu_command};
pub use command::run_command_with_value;
pub use lscolor::{create_lscolors, lscolorize};
pub use string::{string_width, truncate_str};
pub use table::try_build_table;

View File

@ -38,9 +38,11 @@ fn try_build_map(
style_computer,
ctrlc,
Span::unknown(),
0,
usize::MAX,
(config.table_indent.left, config.table_indent.right),
config.table_mode,
0,
false,
);
let result = ExpandedTable::new(None, false, String::new()).build_map(&record, opts);
match result {
@ -63,10 +65,13 @@ fn try_build_list(
style_computer,
ctrlc,
Span::unknown(),
0,
usize::MAX,
(config.table_indent.left, config.table_indent.right),
config.table_mode,
0,
false,
);
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
match result {
Ok(Some(out)) => out,

View File

@ -721,7 +721,7 @@ fn build_table_as_record(v: &RecordView) -> Value {
let layer = v.get_layer_last();
let cols = layer.columns.to_vec();
let vals = layer.records.get(0).map_or(Vec::new(), |row| row.clone());
let vals = layer.records.first().map_or(Vec::new(), |row| row.clone());
Value::record(Record::from_raw_cols_vals(cols, vals), NuSpan::unknown())
}

View File

@ -1,6 +1,6 @@
[package]
name = "nu-glob"
version = "0.87.1"
version = "0.87.2"
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
license = "MIT/Apache-2.0"
description = """

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
edition = "2021"
license = "MIT"
name = "nu-json"
version = "0.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -22,5 +22,5 @@ num-traits = "0.2"
serde = "1.0"
[dev-dependencies]
# nu-path = { path="../nu-path", version = "0.87.1" }
# nu-path = { path="../nu-path", version = "0.87.2" }
# serde_json = "1.0"

View File

@ -63,7 +63,7 @@ fn main() {
// Extract the array
let array : &mut Vec<Value> = sample.get_mut("array").unwrap().as_array_mut().unwrap();
println!("first: {}", array.get(0).unwrap());
println!("first: {}", array.first().unwrap());
// Add a value
array.push(Value::String("tak".to_string()));

View File

@ -3,28 +3,29 @@ authors = ["The Nushell Project Developers"]
description = "Nushell's integrated LSP server"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
name = "nu-lsp"
version = "0.87.1"
version = "0.87.2"
edition = "2021"
license = "MIT"
[dependencies]
nu-cli = { path = "../nu-cli", version = "0.87.1" }
nu-parser = { path = "../nu-parser", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-cli = { path = "../nu-cli", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
reedline = { version = "0.26" }
crossbeam-channel = "0.5.8"
lsp-types = "0.94.1"
lsp-server = "0.7.4"
lsp-server = { version = "0.7.4", git = "https://github.com/schrieveslaach/rust-analyzer.git", branch = "cancelable-initialization" }
miette = "5.10"
ropey = "1.6.1"
serde = "1.0"
serde_json = "1.0"
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" }
nu-command = { path = "../nu-command", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-command = { path = "../nu-command", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
assert-json-diff = "2.0"
tempfile = "3.2"

View File

@ -0,0 +1,147 @@
use lsp_types::{
notification::{Notification, PublishDiagnostics},
Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Url,
};
use miette::{IntoDiagnostic, Result};
use nu_parser::parse;
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
eval_const::create_nu_constant,
Span, Value, NU_VARIABLE_ID,
};
use crate::LanguageServer;
impl LanguageServer {
pub(crate) fn publish_diagnostics_for_file(
&self,
uri: Url,
engine_state: &mut EngineState,
) -> Result<()> {
let cwd = std::env::current_dir().expect("Could not get current working directory.");
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let Ok(nu_const) = create_nu_constant(engine_state, Span::unknown()) else {
return Ok(());
};
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
let mut working_set = StateWorkingSet::new(engine_state);
let Some((rope_of_file, file_path)) = self.rope(&uri) else {
return Ok(());
};
let contents = rope_of_file.bytes().collect::<Vec<u8>>();
let offset = working_set.next_span_start();
parse(
&mut working_set,
Some(&file_path.to_string_lossy()),
&contents,
false,
);
let mut diagnostics = PublishDiagnosticsParams {
uri,
diagnostics: Vec::new(),
version: None,
};
for err in working_set.parse_errors.iter() {
let message = err.to_string();
diagnostics.diagnostics.push(Diagnostic {
range: Self::span_to_range(&err.span(), rope_of_file, offset),
severity: Some(DiagnosticSeverity::ERROR),
message,
..Default::default()
});
}
self.connection
.sender
.send(lsp_server::Message::Notification(
lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), diagnostics),
))
.into_diagnostic()
}
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use lsp_types::Url;
use nu_test_support::fs::fixtures;
use crate::tests::{initialize_language_server, open, update};
#[test]
fn publish_diagnostics_variable_does_not_exists() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("diagnostics");
script.push("var.nu");
let script = Url::from_file_path(script).unwrap();
let notification = open(&client_connection, script.clone());
assert_json_eq!(
notification,
serde_json::json!({
"method": "textDocument/publishDiagnostics",
"params": {
"uri": script,
"diagnostics": [{
"range": {
"start": { "line": 0, "character": 6 },
"end": { "line": 0, "character": 30 }
},
"message": "Variable not found.",
"severity": 1
}]
}
})
);
}
#[test]
fn publish_diagnostics_fixed_unknown_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("diagnostics");
script.push("var.nu");
let script = Url::from_file_path(script).unwrap();
open(&client_connection, script.clone());
let notification = update(
&client_connection,
script.clone(),
String::from("$env"),
Some(lsp_types::Range {
start: lsp_types::Position {
line: 0,
character: 6,
},
end: lsp_types::Position {
line: 0,
character: 30,
},
}),
);
assert_json_eq!(
notification,
serde_json::json!({
"method": "textDocument/publishDiagnostics",
"params": {
"uri": script,
"diagnostics": []
}
})
);
}
}

View File

@ -1,11 +1,19 @@
use std::{fs::File, io::Cursor, sync::Arc};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
use lsp_types::{
request::{Completion, GotoDefinition, HoverRequest, Request},
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
OneOf, Range, ServerCapabilities, TextEdit, Url,
OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url,
};
use miette::{IntoDiagnostic, Result};
use nu_cli::NuCompleter;
@ -17,6 +25,9 @@ use nu_protocol::{
use reedline::Completer;
use ropey::Rope;
mod diagnostics;
mod notification;
#[derive(Debug)]
enum Id {
Variable(VarId),
@ -27,6 +38,7 @@ enum Id {
pub struct LanguageServer {
connection: Connection,
io_threads: Option<IoThreads>,
ropes: BTreeMap<PathBuf, Rope>,
}
impl LanguageServer {
@ -42,11 +54,19 @@ impl LanguageServer {
Ok(Self {
connection,
io_threads,
ropes: BTreeMap::new(),
})
}
pub fn serve_requests(self, engine_state: EngineState) -> Result<()> {
let server_capabilities = serde_json::to_value(&ServerCapabilities {
pub fn serve_requests(
mut self,
engine_state: EngineState,
ctrlc: Arc<AtomicBool>,
) -> Result<()> {
let server_capabilities = serde_json::to_value(ServerCapabilities {
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
definition_provider: Some(OneOf::Left(true)),
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
completion_provider: Some(lsp_types::CompletionOptions::default()),
@ -56,10 +76,22 @@ impl LanguageServer {
let _initialization_params = self
.connection
.initialize(server_capabilities)
.initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst))
.into_diagnostic()?;
for msg in &self.connection.receiver {
while !ctrlc.load(Ordering::SeqCst) {
let msg = match self
.connection
.receiver
.recv_timeout(Duration::from_secs(1))
{
Ok(msg) => msg,
Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
continue;
}
Err(_) => break,
};
match msg {
Message::Request(request) => {
if self
@ -71,25 +103,39 @@ impl LanguageServer {
}
let mut engine_state = engine_state.clone();
match request.method.as_str() {
GotoDefinition::METHOD => {
self.handle_lsp_request(
&mut engine_state,
request,
Self::goto_definition,
)?;
let resp = match request.method.as_str() {
GotoDefinition::METHOD => Self::handle_lsp_request(
&mut engine_state,
request,
|engine_state, params| self.goto_definition(engine_state, params),
),
HoverRequest::METHOD => Self::handle_lsp_request(
&mut engine_state,
request,
|engine_state, params| self.hover(engine_state, params),
),
Completion::METHOD => Self::handle_lsp_request(
&mut engine_state,
request,
|engine_state, params| self.complete(engine_state, params),
),
_ => {
continue;
}
HoverRequest::METHOD => {
self.handle_lsp_request(&mut engine_state, request, Self::hover)?;
}
Completion::METHOD => {
self.handle_lsp_request(&mut engine_state, request, Self::complete)?;
}
_ => {}
}
};
self.connection
.sender
.send(Message::Response(resp))
.into_diagnostic()?;
}
Message::Response(_) => {}
Message::Notification(_) => {}
Message::Notification(notification) => {
if let Some(updated_file) = self.handle_lsp_notification(notification) {
let mut engine_state = engine_state.clone();
self.publish_diagnostics_for_file(updated_file, &mut engine_state)?;
}
}
}
}
@ -101,41 +147,36 @@ impl LanguageServer {
}
fn handle_lsp_request<P, H, R>(
&self,
engine_state: &mut EngineState,
req: lsp_server::Request,
param_handler: H,
) -> Result<()>
mut param_handler: H,
) -> Response
where
P: serde::de::DeserializeOwned,
H: Fn(&mut EngineState, &P) -> Option<R>,
H: FnMut(&mut EngineState, &P) -> Option<R>,
R: serde::ser::Serialize,
{
let resp = {
match serde_json::from_value::<P>(req.params) {
Ok(params) => Response {
id: req.id,
result: param_handler(engine_state, &params)
.and_then(|response| serde_json::to_value(response).ok()),
error: None,
},
match serde_json::from_value::<P>(req.params) {
Ok(params) => Response {
id: req.id,
result: Some(
param_handler(engine_state, &params)
.and_then(|response| serde_json::to_value(response).ok())
.unwrap_or(serde_json::Value::Null),
),
error: None,
},
Err(err) => Response {
id: req.id,
result: None,
error: Some(ResponseError {
code: 1,
message: err.to_string(),
data: None,
}),
},
}
};
self.connection
.sender
.send(Message::Response(resp))
.into_diagnostic()
Err(err) => Response {
id: req.id,
result: None,
error: Some(ResponseError {
code: 1,
message: err.to_string(),
data: None,
}),
},
}
}
fn span_to_range(span: &Span, rope_of_file: &Rope, offset: usize) -> lsp_types::Range {
@ -158,79 +199,84 @@ impl LanguageServer {
lsp_types::Range { start, end }
}
fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize {
pub fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize {
let line_idx = rope_of_file.line_to_char(position.line as usize);
line_idx + position.character as usize
}
fn find_id(
working_set: &mut StateWorkingSet,
file_path: &str,
file: &[u8],
path: &Path,
file: &Rope,
location: usize,
) -> Option<(Id, usize, Span)> {
let file_id = working_set.add_file(file_path.to_string(), file);
let offset = working_set.get_span_for_file(file_id).start;
let block = parse(working_set, Some(file_path), file, false);
let file_path = path.to_string_lossy();
// TODO: think about passing down the rope into the working_set
let contents = file.bytes().collect::<Vec<u8>>();
let block = parse(working_set, Some(&file_path), &contents, false);
let flattened = flatten_block(working_set, &block);
let offset = working_set.get_span_for_filename(&file_path)?.start;
let location = location + offset;
for item in flattened {
if location >= item.0.start && location < item.0.end {
match &item.1 {
for (span, shape) in flattened {
if location >= span.start && location < span.end {
match &shape {
FlatShape::Variable(var_id) | FlatShape::VarDecl(var_id) => {
return Some((Id::Variable(*var_id), offset, item.0));
return Some((Id::Variable(*var_id), offset, span));
}
FlatShape::InternalCall(decl_id) => {
return Some((Id::Declaration(*decl_id), offset, item.0));
return Some((Id::Declaration(*decl_id), offset, span));
}
_ => return Some((Id::Value(item.1), offset, item.0)),
_ => return Some((Id::Value(shape), offset, span)),
}
}
}
None
}
fn read_in_file<'a>(
engine_state: &'a mut EngineState,
file_path: &str,
) -> Result<(Vec<u8>, StateWorkingSet<'a>)> {
let file = std::fs::read(file_path).into_diagnostic()?;
fn rope<'a, 'b: 'a>(&'b self, file_url: &Url) -> Option<(&'a Rope, &'a PathBuf)> {
let file_path = file_url.to_file_path().ok()?;
engine_state.start_in_file(Some(file_path));
self.ropes
.get_key_value(&file_path)
.map(|(path, rope)| (rope, path))
}
fn read_in_file<'a>(
&mut self,
engine_state: &'a mut EngineState,
file_url: &Url,
) -> Option<(&Rope, &PathBuf, StateWorkingSet<'a>)> {
let (file, path) = self.rope(file_url)?;
// TODO: AsPath thingy
engine_state.start_in_file(Some(&path.to_string_lossy()));
let working_set = StateWorkingSet::new(engine_state);
Ok((file, working_set))
Some((file, path, working_set))
}
fn goto_definition(
&mut self,
engine_state: &mut EngineState,
params: &GotoDefinitionParams,
) -> Option<GotoDefinitionResponse> {
let cwd = std::env::current_dir().expect("Could not get current working directory.");
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params
.text_document_position_params
.text_document
.uri
.to_file_path()
.ok()?;
let file_path = file_path.to_string_lossy();
let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?;
let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?;
let (file, path, mut working_set) = self.read_in_file(
engine_state,
&params.text_document_position_params.text_document.uri,
)?;
let (id, _, _) = Self::find_id(
&mut working_set,
&file_path,
&file,
Self::lsp_position_to_location(
&params.text_document_position_params.position,
&rope_of_file,
),
path,
file,
Self::lsp_position_to_location(&params.text_document_position_params.position, file),
)?;
match id {
@ -242,7 +288,7 @@ impl LanguageServer {
if span.start >= *file_start && span.start < *file_end {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: Url::from_file_path(file_path).ok()?,
range: Self::span_to_range(span, &rope_of_file, *file_start),
range: Self::span_to_range(span, file, *file_start),
}));
}
}
@ -261,11 +307,7 @@ impl LanguageServer {
.text_document
.uri
.clone(),
range: Self::span_to_range(
&var.declaration_span,
&rope_of_file,
*file_start,
),
range: Self::span_to_range(&var.declaration_span, file, *file_start),
}));
}
}
@ -275,30 +317,20 @@ impl LanguageServer {
None
}
fn hover(engine_state: &mut EngineState, params: &HoverParams) -> Option<Hover> {
fn hover(&mut self, engine_state: &mut EngineState, params: &HoverParams) -> Option<Hover> {
let cwd = std::env::current_dir().expect("Could not get current working directory.");
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params
.text_document_position_params
.text_document
.uri
.to_file_path()
.ok()?;
let file_path = file_path.to_string_lossy();
let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?;
let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?;
let (file, path, mut working_set) = self.read_in_file(
engine_state,
&params.text_document_position_params.text_document.uri,
)?;
let (id, _, _) = Self::find_id(
&mut working_set,
&file_path,
&file,
Self::lsp_position_to_location(
&params.text_document_position_params.position,
&rope_of_file,
),
path,
file,
Self::lsp_position_to_location(&params.text_document_position_params.position, file),
)?;
match id {
@ -489,27 +521,23 @@ impl LanguageServer {
}
fn complete(
&mut self,
engine_state: &mut EngineState,
params: &CompletionParams,
) -> Option<CompletionResponse> {
let cwd = std::env::current_dir().expect("Could not get current working directory.");
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params
.text_document_position
.text_document
.uri
.to_file_path()
.ok()?;
let file_path = file_path.to_string_lossy();
let rope_of_file = Rope::from_reader(File::open(file_path.as_ref()).ok()?).ok()?;
let (rope_of_file, _, _) = self.read_in_file(
engine_state,
&params.text_document_position.text_document.uri,
)?;
let stack = Stack::new();
let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack);
let location =
Self::lsp_position_to_location(&params.text_document_position.position, &rope_of_file);
Self::lsp_position_to_location(&params.text_document_position.position, rope_of_file);
let results = completer.complete(&rope_of_file.to_string(), location);
if results.is_empty() {
None
@ -545,15 +573,18 @@ mod tests {
use super::*;
use assert_json_diff::assert_json_eq;
use lsp_types::{
notification::{Exit, Initialized, Notification},
notification::{
DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
},
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
CompletionParams, GotoDefinitionParams, InitializeParams, InitializedParams,
TextDocumentIdentifier, TextDocumentPositionParams, Url,
CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
GotoDefinitionParams, InitializeParams, InitializedParams, TextDocumentContentChangeEvent,
TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
};
use nu_test_support::fs::{fixtures, root};
use std::sync::mpsc::Receiver;
fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
pub fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
use std::sync::mpsc;
let (client_connection, server_connection) = Connection::memory();
let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap();
@ -562,7 +593,7 @@ mod tests {
std::thread::spawn(move || {
let engine_state = nu_cmd_lang::create_default_context();
let engine_state = nu_command::add_shell_command_context(engine_state);
send.send(lsp_server.serve_requests(engine_state))
send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false))))
});
client_connection
@ -651,16 +682,91 @@ mod tests {
.receiver
.recv_timeout(std::time::Duration::from_secs(2))
.unwrap();
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert!(matches!(
resp,
Message::Response(response) if response.result.is_none()
));
assert_json_eq!(result, serde_json::json!(null));
}
fn goto_definition(uri: Url, line: u32, character: u32) -> Message {
let (client_connection, _recv) = initialize_language_server();
pub fn open(client_connection: &Connection, uri: Url) -> lsp_server::Notification {
let text = std::fs::read_to_string(uri.to_file_path().unwrap()).unwrap();
client_connection
.sender
.send(Message::Notification(lsp_server::Notification {
method: DidOpenTextDocument::METHOD.to_string(),
params: serde_json::to_value(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri,
language_id: String::from("nu"),
version: 1,
text,
},
})
.unwrap(),
}))
.unwrap();
let notification = client_connection
.receiver
.recv_timeout(Duration::from_secs(2))
.unwrap();
if let Message::Notification(n) = notification {
n
} else {
panic!();
}
}
pub fn update(
client_connection: &Connection,
uri: Url,
text: String,
range: Option<Range>,
) -> lsp_server::Notification {
client_connection
.sender
.send(lsp_server::Message::Notification(
lsp_server::Notification {
method: DidChangeTextDocument::METHOD.to_string(),
params: serde_json::to_value(DidChangeTextDocumentParams {
text_document: lsp_types::VersionedTextDocumentIdentifier {
uri,
version: 2,
},
content_changes: vec![TextDocumentContentChangeEvent {
range,
range_length: None,
text,
}],
})
.unwrap(),
},
))
.unwrap();
let notification = client_connection
.receiver
.recv_timeout(Duration::from_secs(2))
.unwrap();
if let Message::Notification(n) = notification {
n
} else {
panic!();
}
}
fn goto_definition(
client_connection: &Connection,
uri: Url,
line: u32,
character: u32,
) -> Message {
client_connection
.sender
.send(Message::Request(lsp_server::Request {
@ -686,13 +792,17 @@ mod tests {
#[test]
fn goto_definition_of_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("goto");
script.push("var.nu");
let script = Url::from_file_path(script).unwrap();
let resp = goto_definition(script.clone(), 2, 12);
open(&client_connection, script.clone());
let resp = goto_definition(&client_connection, script.clone(), 2, 12);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -713,13 +823,17 @@ mod tests {
#[test]
fn goto_definition_of_command() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("goto");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
let resp = goto_definition(script.clone(), 4, 1);
open(&client_connection, script.clone());
let resp = goto_definition(&client_connection, script.clone(), 4, 1);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -740,13 +854,17 @@ mod tests {
#[test]
fn goto_definition_of_command_parameter() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("goto");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
let resp = goto_definition(script.clone(), 1, 14);
open(&client_connection, script.clone());
let resp = goto_definition(&client_connection, script.clone(), 1, 14);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -765,9 +883,7 @@ mod tests {
);
}
fn hover(uri: Url, line: u32, character: u32) -> Message {
let (client_connection, _recv) = initialize_language_server();
pub fn hover(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
client_connection
.sender
.send(Message::Request(lsp_server::Request {
@ -792,13 +908,17 @@ mod tests {
#[test]
fn hover_on_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("var.nu");
let script = Url::from_file_path(script).unwrap();
let resp = hover(script.clone(), 2, 0);
open(&client_connection, script.clone());
let resp = hover(&client_connection, script.clone(), 2, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -815,13 +935,17 @@ mod tests {
#[test]
fn hover_on_command() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
let resp = hover(script.clone(), 3, 0);
open(&client_connection, script.clone());
let resp = hover(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -839,9 +963,7 @@ mod tests {
);
}
fn complete(uri: Url, line: u32, character: u32) -> Message {
let (client_connection, _recv) = initialize_language_server();
fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
client_connection
.sender
.send(Message::Request(lsp_server::Request {
@ -868,13 +990,17 @@ mod tests {
#[test]
fn complete_on_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("completion");
script.push("var.nu");
let script = Url::from_file_path(script).unwrap();
let resp = complete(script, 2, 9);
open(&client_connection, script.clone());
let resp = complete(&client_connection, script, 2, 9);
let result = if let Message::Response(response) = resp {
response.result
} else {
@ -900,13 +1026,17 @@ mod tests {
#[test]
fn complete_command_with_space() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("completion");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
let resp = complete(script, 0, 8);
open(&client_connection, script.clone());
let resp = complete(&client_connection, script, 0, 8);
let result = if let Message::Response(response) = resp {
response.result
} else {

View File

@ -0,0 +1,185 @@
use lsp_types::{
notification::{
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification,
},
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Url,
};
use ropey::Rope;
use crate::LanguageServer;
impl LanguageServer {
pub(crate) fn handle_lsp_notification(
&mut self,
notification: lsp_server::Notification,
) -> Option<Url> {
match notification.method.as_str() {
DidOpenTextDocument::METHOD => Self::handle_notification_payload::<
DidOpenTextDocumentParams,
_,
>(notification, |param| {
if let Ok(file_path) = param.text_document.uri.to_file_path() {
let rope = Rope::from_str(&param.text_document.text);
self.ropes.insert(file_path, rope);
Some(param.text_document.uri)
} else {
None
}
}),
DidChangeTextDocument::METHOD => {
Self::handle_notification_payload::<DidChangeTextDocumentParams, _>(
notification,
|params| self.update_rope(params),
)
}
DidCloseTextDocument::METHOD => Self::handle_notification_payload::<
DidCloseTextDocumentParams,
_,
>(notification, |param| {
if let Ok(file_path) = param.text_document.uri.to_file_path() {
self.ropes.remove(&file_path);
}
None
}),
_ => None,
}
}
fn handle_notification_payload<P, H>(
notification: lsp_server::Notification,
mut param_handler: H,
) -> Option<Url>
where
P: serde::de::DeserializeOwned,
H: FnMut(P) -> Option<Url>,
{
if let Ok(params) = serde_json::from_value::<P>(notification.params) {
param_handler(params)
} else {
None
}
}
fn update_rope(&mut self, params: DidChangeTextDocumentParams) -> Option<Url> {
if let Ok(file_path) = params.text_document.uri.to_file_path() {
for content_change in params.content_changes.into_iter() {
let entry = self.ropes.entry(file_path.clone());
match (content_change.range, content_change.range) {
(Some(range), _) => {
entry.and_modify(|rope| {
let start = Self::lsp_position_to_location(&range.start, rope);
let end = Self::lsp_position_to_location(&range.end, rope);
rope.remove(start..end);
rope.insert(start, &content_change.text);
});
}
(None, None) => {
entry.and_modify(|r| *r = Rope::from_str(&content_change.text));
}
_ => {}
}
}
Some(params.text_document.uri)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use lsp_server::Message;
use lsp_types::{Range, Url};
use nu_test_support::fs::fixtures;
use crate::tests::{hover, initialize_language_server, open, update};
#[test]
fn hover_on_command_after_full_content_change() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
open(&client_connection, script.clone());
update(
&client_connection,
script.clone(),
String::from(
r#"# Renders some updated greeting message
def hello [] {}
hello"#,
),
None,
);
let resp = hover(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n"
}
})
);
}
#[test]
fn hover_on_command_after_partial_content_change() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("command.nu");
let script = Url::from_file_path(script).unwrap();
open(&client_connection, script.clone());
update(
&client_connection,
script.clone(),
String::from("# Renders some updated greeting message"),
Some(Range {
start: lsp_types::Position {
line: 0,
character: 0,
},
end: lsp_types::Position {
line: 0,
character: 31,
},
}),
);
let resp = hover(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n"
}
})
);
}
}

View File

@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
edition = "2021"
license = "MIT"
name = "nu-parser"
version = "0.87.1"
version = "0.87.2"
exclude = ["/fuzz"]
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
bytesize = "1.3"
chrono = { default-features = false, features = ['std'], version = "0.4" }

View File

@ -47,9 +47,7 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
b"export def",
b"for",
b"extern",
b"extern-wrapped",
b"export extern",
b"export extern-wrapped",
b"alias",
b"export alias",
b"export-env",
@ -72,13 +70,13 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
/// Check whether spans start with a parser keyword that can be aliased
pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
// try two words
if let (Some(span1), Some(span2)) = (spans.get(0), spans.get(1)) {
if let (Some(span1), Some(span2)) = (spans.first(), spans.get(1)) {
let cmd_name = working_set.get_span_contents(span(&[*span1, *span2]));
return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
}
// try one word
if let Some(span1) = spans.get(0) {
if let Some(span1) = spans.first() {
let cmd_name = working_set.get_span_contents(*span1);
UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
} else {
@ -151,11 +149,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
return;
};
if def_type_name != b"def"
&& def_type_name != b"def-env"
&& def_type_name != b"extern"
&& def_type_name != b"extern-wrapped"
{
if def_type_name != b"def" && def_type_name != b"extern" {
return;
}
@ -378,7 +372,7 @@ pub fn parse_def(
};
let def_call = working_set.get_span_contents(name_span).to_vec();
if def_call != b"def" && def_call != b"def-env" {
if def_call != b"def" {
working_set.error(ParseError::UnknownState(
"internal error: Wrong call name for def function".into(),
span(spans),
@ -575,7 +569,7 @@ pub fn parse_def(
let calls_itself = block_calls_itself(block, decl_id);
block.recursive = Some(calls_itself);
block.signature = signature;
block.redirect_env = def_call == b"def-env" || has_env;
block.redirect_env = has_env;
if block.signature.input_output_types.is_empty() {
block
@ -635,9 +629,9 @@ pub fn parse_extern(
};
let extern_call = working_set.get_span_contents(name_span).to_vec();
if extern_call != b"extern" && extern_call != b"extern-wrapped" {
if extern_call != b"extern" {
working_set.error(ParseError::UnknownState(
"internal error: Wrong call name for extern or extern-wrapped command".into(),
"internal error: Wrong call name for extern command".into(),
span(spans),
));
return garbage_pipeline(spans);
@ -659,7 +653,7 @@ pub fn parse_extern(
let (command_spans, rest_spans) = spans.split_at(split_id);
if let Some(name_span) = rest_spans.get(0) {
if let Some(name_span) = rest_spans.first() {
if let Some(err) = detect_params_in_name(
working_set,
*name_span,
@ -1055,8 +1049,9 @@ pub fn parse_export_in_block(
let full_name = if lite_command.parts.len() > 1 {
let sub = working_set.get_span_contents(lite_command.parts[1]);
match sub {
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module"
| b"const" => [b"export ", sub].concat(),
b"alias" | b"def" | b"extern" | b"use" | b"module" | b"const" => {
[b"export ", sub].concat()
}
_ => b"export".to_vec(),
}
} else {
@ -1113,7 +1108,7 @@ pub fn parse_export_in_block(
match full_name.as_slice() {
b"export alias" => parse_alias(working_set, lite_command, None),
b"export def" | b"export def-env" => parse_def(working_set, lite_command, None).0,
b"export def" => parse_def(working_set, lite_command, None).0,
b"export const" => parse_const(working_set, &lite_command.parts[1..]),
b"export use" => {
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
@ -1121,7 +1116,6 @@ pub fn parse_export_in_block(
}
b"export module" => parse_module(working_set, lite_command, None).0,
b"export extern" => parse_extern(working_set, lite_command, None),
b"export extern-wrapped" => parse_extern(working_set, lite_command, None),
_ => {
working_set.error(ParseError::UnexpectedKeyword(
String::from_utf8_lossy(&full_name).to_string(),
@ -1141,7 +1135,7 @@ pub fn parse_export_in_module(
) -> (Pipeline, Vec<Exportable>) {
let spans = &lite_command.parts[..];
let export_span = if let Some(sp) = spans.get(0) {
let export_span = if let Some(sp) = spans.first() {
if working_set.get_span_contents(*sp) != b"export" {
working_set.error(ParseError::UnknownState(
"expected export statement".into(),
@ -1215,7 +1209,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = def_call.clone();
@ -1230,67 +1224,7 @@ pub fn parse_export_in_module(
result
}
b"def-env" => {
let lite_command = LiteCommand {
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
};
let (pipeline, _) = parse_def(working_set, &lite_command, Some(module_name));
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def-env")
{
id
} else {
working_set.error(ParseError::InternalError(
"missing 'export def-env' command".into(),
export_span,
));
return (garbage_pipeline(spans), vec![]);
};
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(PipelineElement::Expression(
_,
Expression {
expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
{
call = def_call.clone();
call.head = span(&spans[0..=1]);
call.decl_id = export_def_decl_id;
} else {
working_set.error(ParseError::InternalError(
"unexpected output from parsing a definition".into(),
span(&spans[1..]),
));
};
let mut result = vec![];
let decl_name = match spans.get(2) {
Some(span) => working_set.get_span_contents(*span),
None => &[],
};
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name) {
result.push(Exportable::Decl {
name: decl_name.to_vec(),
id: decl_id,
});
} else {
working_set.error(ParseError::InternalError(
"failed to find added declaration".into(),
span(&spans[1..]),
));
}
result
}
b"extern" | b"extern-wrapped" => {
b"extern" => {
let lite_command = LiteCommand {
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
@ -1303,7 +1237,7 @@ pub fn parse_export_in_module(
id
} else {
working_set.error(ParseError::InternalError(
"missing 'export extern' or 'export extern-wrapped' command".into(),
"missing 'export extern' command".into(),
export_span,
));
return (garbage_pipeline(spans), vec![]);
@ -1316,7 +1250,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = def_call.clone();
@ -1376,7 +1310,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref alias_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = alias_call.clone();
@ -1435,7 +1369,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref use_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = use_call.clone();
@ -1472,7 +1406,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref module_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = module_call.clone();
@ -1529,7 +1463,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call),
..
},
)) = pipeline.elements.get(0)
)) = pipeline.elements.first()
{
call = def_call.clone();
@ -1569,7 +1503,7 @@ pub fn parse_export_in_module(
}
_ => {
working_set.error(ParseError::Expected(
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword",
"def, alias, use, module, const or extern keyword",
spans[1],
));
@ -1578,9 +1512,9 @@ pub fn parse_export_in_module(
}
} else {
working_set.error(ParseError::MissingPositional(
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword".to_string(),
"def, alias, use, module, const or extern keyword".to_string(),
Span::new(export_span.end, export_span.end),
"def, def-env, alias, use, module, const, extern or extern-wrapped keyword".to_string(),
"def, alias, use, module, const or extern keyword".to_string(),
));
vec![]
@ -1749,7 +1683,7 @@ pub fn parse_module_block(
let name = working_set.get_span_contents(command.parts[0]);
match name {
b"def" | b"def-env" => {
b"def" => {
block.pipelines.push(
parse_def(
working_set,
@ -1762,11 +1696,9 @@ pub fn parse_module_block(
b"const" => block
.pipelines
.push(parse_const(working_set, &command.parts)),
b"extern" | b"extern-wrapped" => {
block
.pipelines
.push(parse_extern(working_set, command, None))
}
b"extern" => block
.pipelines
.push(parse_extern(working_set, command, None)),
b"alias" => {
block.pipelines.push(parse_alias(
working_set,
@ -1906,7 +1838,7 @@ pub fn parse_module_block(
}
_ => {
working_set.error(ParseError::ExpectedKeyword(
"def, const, def-env, extern, extern-wrapped, alias, use, module, export or export-env keyword".into(),
"def, const, extern, alias, use, module, export or export-env keyword".into(),
command.parts[0],
));

View File

@ -1572,10 +1572,10 @@ pub fn parse_brace_expr(
let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
let second_token = tokens
.get(0)
.first()
.map(|token| working_set.get_span_contents(token.span));
let second_token_contents = tokens.get(0).map(|token| token.contents);
let second_token_contents = tokens.first().map(|token| token.contents);
let third_token = tokens
.get(1)
@ -2666,7 +2666,7 @@ pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Exp
}
pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
let Some(head_span) = spans.get(0) else {
let Some(head_span) = spans.first() else {
working_set.error(ParseError::WrongImportPattern(
"needs at least one component of import pattern".to_string(),
span(spans),
@ -4918,8 +4918,8 @@ pub fn parse_expression(
// For now, check for special parses of certain keywords
match bytes.as_slice() {
b"def" | b"extern" | b"extern-wrapped" | b"for" | b"module" | b"use" | b"source"
| b"alias" | b"export" | b"hide" => {
b"def" | b"extern" | b"for" | b"module" | b"use" | b"source" | b"alias" | b"export"
| b"hide" => {
working_set.error(ParseError::BuiltinCommandInPipeline(
String::from_utf8(bytes)
.expect("builtin commands bytes should be able to convert to string"),
@ -5085,8 +5085,8 @@ pub fn parse_builtin_commands(
let name = working_set.get_span_contents(lite_command.parts[0]);
match name {
b"def" | b"def-env" => parse_def(working_set, lite_command, None).0,
b"extern" | b"extern-wrapped" => parse_extern(working_set, lite_command, None),
b"def" => parse_def(working_set, lite_command, None).0,
b"extern" => parse_extern(working_set, lite_command, None),
b"let" => parse_let(working_set, &lite_command.parts),
b"const" => parse_const(working_set, &lite_command.parts),
b"mut" => parse_mut(working_set, &lite_command.parts),

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
edition = "2021"
license = "MIT"
name = "nu-path"
version = "0.87.1"
version = "0.87.2"
exclude = ["/fuzz"]
[lib]

View File

@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
edition = "2021"
license = "MIT"
name = "nu-plugin"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
bincode = "1.3"
rmp-serde = "1.1"

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
edition = "2021"
license = "MIT"
name = "nu-pretty-hex"
version = "0.87.1"
version = "0.87.2"
[lib]
doctest = false

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
edition = "2021"
license = "MIT"
name = "nu-protocol"
version = "0.87.1"
version = "0.87.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,9 +13,9 @@ version = "0.87.1"
bench = false
[dependencies]
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-path = { path = "../nu-path", version = "0.87.1" }
nu-system = { path = "../nu-system", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.2" }
nu-system = { path = "../nu-system", version = "0.87.2" }
byte-unit = "4.0"
chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false }
@ -37,5 +37,5 @@ plugin = ["serde_json"]
serde_json = "1.0"
strum = "0.25"
strum_macros = "0.25"
nu-test-support = { path = "../nu-test-support", version = "0.87.1" }
nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
rstest = "0.18"

View File

@ -12,6 +12,28 @@ pub enum Argument {
Unknown(Expression), // unknown argument used in "fall-through" signatures
}
impl Argument {
/// The span for an argument
pub fn span(&self) -> Span {
match self {
Argument::Positional(e) => e.span,
Argument::Named((named, short, expr)) => {
let start = named.span.start;
let end = if let Some(expr) = expr {
expr.span.end
} else if let Some(short) = short {
short.span.end
} else {
named.span.end
};
Span::new(start, end)
}
Argument::Unknown(e) => e.span,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Call {
/// identifier of the declaration to call
@ -36,6 +58,26 @@ impl Call {
}
}
/// The span encompassing the arguments
///
/// If there are no arguments the span covers where the first argument would exist
///
/// If there are one or more arguments the span encompasses the start of the first argument to
/// end of the last argument
pub fn arguments_span(&self) -> Span {
let past = self.head.past();
let start = self
.arguments
.first()
.map(|a| a.span())
.unwrap_or(past)
.start;
let end = self.arguments.last().map(|a| a.span()).unwrap_or(past).end;
Span::new(start, end)
}
pub fn named_iter(
&self,
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> {
@ -166,3 +208,64 @@ impl Call {
span
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn argument_span_named() {
let named = Spanned {
item: "named".to_string(),
span: Span::new(2, 3),
};
let short = Spanned {
item: "short".to_string(),
span: Span::new(5, 7),
};
let expr = Expression::garbage(Span::new(11, 13));
let arg = Argument::Named((named.clone(), None, None));
assert_eq!(Span::new(2, 3), arg.span());
let arg = Argument::Named((named.clone(), Some(short.clone()), None));
assert_eq!(Span::new(2, 7), arg.span());
let arg = Argument::Named((named.clone(), None, Some(expr.clone())));
assert_eq!(Span::new(2, 13), arg.span());
let arg = Argument::Named((named.clone(), Some(short.clone()), Some(expr.clone())));
assert_eq!(Span::new(2, 13), arg.span());
}
#[test]
fn argument_span_positional() {
let span = Span::new(2, 3);
let expr = Expression::garbage(span);
let arg = Argument::Positional(expr);
assert_eq!(span, arg.span());
}
#[test]
fn argument_span_unknown() {
let span = Span::new(2, 3);
let expr = Expression::garbage(span);
let arg = Argument::Unknown(expr);
assert_eq!(span, arg.span());
}
#[test]
fn call_arguments_span() {
let mut call = Call::new(Span::new(0, 1));
call.add_positional(Expression::garbage(Span::new(2, 3)));
call.add_positional(Expression::garbage(Span::new(5, 7)));
assert_eq!(Span::new(2, 7), call.arguments_span());
}
}

View File

@ -134,8 +134,8 @@ impl Expression {
return true;
}
if let Some(pipeline) = block.pipelines.get(0) {
match pipeline.elements.get(0) {
if let Some(pipeline) = block.pipelines.first() {
match pipeline.elements.first() {
Some(element) => element.has_in_variable(working_set),
None => false,
}
@ -150,8 +150,8 @@ impl Expression {
return true;
}
if let Some(pipeline) = block.pipelines.get(0) {
match pipeline.elements.get(0) {
if let Some(pipeline) = block.pipelines.first() {
match pipeline.elements.first() {
Some(element) => element.has_in_variable(working_set),
None => false,
}
@ -258,8 +258,8 @@ impl Expression {
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id);
if let Some(pipeline) = block.pipelines.get(0) {
if let Some(expr) = pipeline.elements.get(0) {
if let Some(pipeline) = block.pipelines.first() {
if let Some(expr) = pipeline.elements.first() {
expr.has_in_variable(working_set)
} else {
false
@ -304,8 +304,8 @@ impl Expression {
Expr::Block(block_id) => {
let block = working_set.get_block(*block_id);
let new_expr = if let Some(pipeline) = block.pipelines.get(0) {
if let Some(element) = pipeline.elements.get(0) {
let new_expr = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)
@ -335,8 +335,8 @@ impl Expression {
Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.get(0) {
if let Some(element) = pipeline.elements.get(0) {
let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)
@ -433,8 +433,8 @@ impl Expression {
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.get(0) {
if let Some(element) = pipeline.elements.get(0) {
let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)

View File

@ -3,7 +3,7 @@ use crate::{record, Config, ShellError, Span, Value};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
pub enum TableMode {
Basic,
Thin,
@ -35,6 +35,7 @@ impl FromStr for TableMode {
"compact" => Ok(Self::Compact),
"with_love" => Ok(Self::WithLove),
"compact_double" => Ok(Self::CompactDouble),
"default" => Ok(TableMode::default()),
"rounded" => Ok(Self::Rounded),
"reinforced" => Ok(Self::Reinforced),
"heavy" => Ok(Self::Heavy),

View File

@ -753,8 +753,7 @@ impl EngineState {
decls_map.extend(new_decls);
}
let mut decls: Vec<(Vec<u8>, DeclId)> =
decls_map.into_iter().map(|(v, k)| (v, k)).collect();
let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect();
decls.sort_by(|a, b| a.0.cmp(&b.0));
decls.into_iter()

View File

@ -317,6 +317,15 @@ impl<'a> StateWorkingSet<'a> {
self.num_virtual_paths() - 1
}
pub fn get_span_for_filename(&self, filename: &str) -> Option<Span> {
let (file_id, ..) = self
.files()
.enumerate()
.find(|(_, (fname, _, _))| fname == filename)?;
Some(self.get_span_for_file(file_id))
}
pub fn get_span_for_file(&self, file_id: usize) -> Span {
let result = self
.files()

View File

@ -676,7 +676,11 @@ pub enum ShellError {
/// It's always DNS.
#[error("Network failure")]
#[diagnostic(code(nu::shell::network_failure))]
NetworkFailure(String, #[label("{0}")] Span),
NetworkFailure {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Help text for this command could not be found.
///
@ -685,7 +689,10 @@ pub enum ShellError {
/// Check the spelling for the requested command and try again. Are you sure it's defined and your configurations are loading correctly? Can you execute it?
#[error("Command not found")]
#[diagnostic(code(nu::shell::command_not_found))]
CommandNotFound(#[label("command not found")] Span),
CommandNotFound {
#[label("command not found")]
span: Span,
},
/// This alias could not be found
///

View File

@ -894,26 +894,6 @@ impl Value {
}
}
/// Check if the content is empty
pub fn is_empty(&self) -> bool {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
}
}
pub fn is_nothing(&self) -> bool {
matches!(self, Value::Nothing { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Value::Error { .. })
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn follow_cell_path(
self,
@ -923,8 +903,6 @@ impl Value {
let mut current = self;
for member in cell_path {
// FIXME: this uses a few extra clones for simplicity, but there may be a way
// to traverse the path without them
match member {
PathMember::Int {
val: count,
@ -932,16 +910,22 @@ impl Value {
optional,
} => {
// Treat a numeric path member as `select <val>`
match &mut current {
Value::List { vals: val, .. } => {
if let Some(item) = val.get(*count) {
current = item.clone();
match current {
Value::List { mut vals, .. } => {
if *count < vals.len() {
// `vals` is owned and will be dropped right after this,
// so we can `swap_remove` the value at index `count`
// without worrying about preserving order.
current = vals.swap_remove(*count);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span })
} else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span });
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *origin_span,
});
}
}
Value::Binary { val, .. } => {
@ -950,19 +934,22 @@ impl Value {
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span })
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span });
return Err(ShellError::AccessBeyondEnd {
max_idx: val.len() - 1,
span: *origin_span,
});
}
}
Value::Range { val, .. } => {
if let Some(item) = val.clone().into_range_iter(None)?.nth(*count) {
current = item.clone();
if let Some(item) = val.into_range_iter(None)?.nth(*count) {
current = item;
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else {
return Err(ShellError::AccessBeyondEndOfStream {
span: *origin_span
span: *origin_span,
});
}
}
@ -988,11 +975,14 @@ impl Value {
return Err(ShellError::TypeMismatch {
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
span: *origin_span,
})
});
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess { type_name:format!("{}",x.get_type()), span: *origin_span })
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
}
}
}
@ -1003,7 +993,7 @@ impl Value {
} => {
let span = current.span();
match &mut current {
match current {
Value::Record { val, .. } => {
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
if let Some(found) = val.iter().rev().find(|x| {
@ -1013,7 +1003,7 @@ impl Value {
x.0 == column_name
}
}) {
current = found.1.clone();
current = found.1.clone(); // TODO: avoid clone here
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) =
@ -1022,7 +1012,7 @@ impl Value {
return Err(ShellError::DidYouMean(suggestion, *origin_span));
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
col_name: column_name.clone(),
span: *origin_span,
src_span: span,
});
@ -1031,15 +1021,21 @@ impl Value {
Value::LazyRecord { val, .. } => {
let columns = val.column_names();
if columns.contains(&column_name.as_str()) {
current = val.get_column_value(column_name)?;
if let Some(col) = columns.iter().rev().find(|&col| {
if insensitive {
col.eq_ignore_case(column_name)
} else {
col == column_name
}
}) {
current = val.get_column_value(col)?;
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) = did_you_mean(&columns, column_name) {
return Err(ShellError::DidYouMean(suggestion, *origin_span));
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
col_name: column_name.clone(),
span: *origin_span,
src_span: span,
});
@ -1049,39 +1045,50 @@ impl Value {
// Create a List which contains each matching value for contained
// records in the source list.
Value::List { vals, .. } => {
// TODO: this should stream instead of collecting
let mut output = vec![];
for val in vals {
// only look in records; this avoids unintentionally recursing into deeply nested tables
if matches!(val, Value::Record { .. }) {
if let Ok(result) = val.clone().follow_cell_path(
&[PathMember::String {
val: column_name.clone(),
let list = vals
.into_iter()
.map(|val| {
let val_span = val.span();
match val {
Value::Record { val, .. } => {
if let Some(found) = val.iter().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
Ok(found.1.clone()) // TODO: avoid clone here
} else if *optional {
Ok(Value::nothing(*origin_span))
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
Err(ShellError::DidYouMean(
suggestion,
*origin_span,
))
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: *origin_span,
src_span: val_span,
})
}
}
Value::Nothing { .. } if *optional => {
Ok(Value::nothing(*origin_span))
}
_ => Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: *origin_span,
optional: *optional,
}],
insensitive,
) {
output.push(result);
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
span: *origin_span,
src_span: val.span(),
});
src_span: val_span,
}),
}
} else if *optional && matches!(val, Value::Nothing { .. }) {
output.push(Value::nothing(*origin_span));
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
span: *origin_span,
src_span: val.span(),
});
}
}
})
.collect::<Result<_, _>>()?;
current = Value::list(output, span);
current = Value::list(list, span);
}
Value::CustomValue { val, .. } => {
current = val.follow_path_string(column_name.clone(), *origin_span)?;
@ -1089,12 +1096,12 @@ impl Value {
Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
})
});
}
}
}
@ -1130,8 +1137,8 @@ impl Value {
cell_path: &[PathMember],
new_val: Value,
) -> Result<(), ShellError> {
match cell_path.first() {
Some(path_member) => match path_member {
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
@ -1141,61 +1148,43 @@ impl Value {
for val in vals.iter_mut() {
match val {
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.upsert_data_at_cell_path(
&cell_path[1..],
new_val.clone(),
)?
}
}
if !found {
if cell_path.len() == 1 {
record.push(col_name, new_val);
break;
if let Some(val) = record.get_mut(col_name) {
val.upsert_data_at_cell_path(path, new_val.clone())?;
} else {
let new_col = if path.is_empty() {
new_val.clone()
} else {
let mut new_col =
Value::record(Record::new(), new_val.span());
new_col.upsert_data_at_cell_path(
&cell_path[1..],
new_val,
)?;
vals.push(new_col);
break;
}
new_col
.upsert_data_at_cell_path(path, new_val.clone())?;
new_col
};
record.push(col_name, new_col);
}
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
}
}
}
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
let new_col = if cell_path.len() == 1 {
if let Some(val) = record.get_mut(col_name) {
val.upsert_data_at_cell_path(path, new_val)?;
} else {
let new_col = if path.is_empty() {
new_val
} else {
let mut new_col = Value::record(Record::new(), new_val.span());
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
new_col.upsert_data_at_cell_path(path, new_val)?;
new_col
};
record.push(col_name, new_col);
}
}
@ -1203,15 +1192,15 @@ impl Value {
// convert to Record first.
let mut record = val.collect()?;
record.upsert_data_at_cell_path(cell_path, new_val)?;
*self = record
*self = record;
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
},
PathMember::Int {
@ -1219,8 +1208,8 @@ impl Value {
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.upsert_data_at_cell_path(&cell_path[1..], new_val)?
} else if vals.len() == *row_num && cell_path.len() == 1 {
v.upsert_data_at_cell_path(path, new_val)?;
} else if vals.len() == *row_num && path.is_empty() {
// If the upsert is at 1 + the end of the list, it's OK.
// Otherwise, it's prohibited.
vals.push(new_val);
@ -1231,18 +1220,17 @@ impl Value {
});
}
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
})
});
}
},
},
None => {
*self = new_val;
}
} else {
*self = new_val;
}
Ok(())
}
@ -1259,7 +1247,6 @@ impl Value {
match new_val {
Value::Error { error, .. } => Err(*error),
new_val => self.update_data_at_cell_path(cell_path, new_val),
}
}
@ -1270,9 +1257,8 @@ impl Value {
new_val: Value,
) -> Result<(), ShellError> {
let v_span = self.span();
match cell_path.first() {
Some(path_member) => match path_member {
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
@ -1283,47 +1269,33 @@ impl Value {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.update_data_at_cell_path(
&cell_path[1..],
new_val.clone(),
)?
}
}
if !found {
if let Some(val) = record.get_mut(col_name) {
val.update_data_at_cell_path(path, new_val.clone())?;
} else {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
}
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
}
}
}
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
if let Some(val) = record.get_mut(col_name) {
val.update_data_at_cell_path(path, new_val)?;
} else {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
@ -1333,15 +1305,15 @@ impl Value {
// convert to Record first.
let mut record = val.collect()?;
record.update_data_at_cell_path(cell_path, new_val)?;
*self = record
*self = record;
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
},
PathMember::Int {
@ -1349,7 +1321,7 @@ impl Value {
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.update_data_at_cell_path(&cell_path[1..], new_val)?
v.update_data_at_cell_path(path, new_val)?;
} else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *span });
} else {
@ -1359,29 +1331,27 @@ impl Value {
});
}
}
Value::Error { error, .. } => return Err(*error.to_owned()),
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
})
});
}
},
},
None => {
*self = new_val;
}
} else {
*self = new_val;
}
Ok(())
}
pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> {
match cell_path.len() {
0 => Ok(()),
1 => {
let path_member = cell_path.first().expect("there is a first");
match cell_path {
[] => Ok(()),
[member] => {
let v_span = self.span();
match path_member {
match member {
PathMember::String {
val: col_name,
span,
@ -1390,12 +1360,11 @@ impl Value {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
if record.remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
@ -1403,10 +1372,10 @@ impl Value {
}
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
}
}
@ -1415,7 +1384,7 @@ impl Value {
Value::Record { val: record, .. } => {
if record.remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
@ -1430,7 +1399,7 @@ impl Value {
Ok(())
}
v => Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
}),
@ -1462,10 +1431,9 @@ impl Value {
},
}
}
_ => {
let path_member = cell_path.first().expect("there is a first");
[member, path @ ..] => {
let v_span = self.span();
match path_member {
match member {
PathMember::String {
val: col_name,
span,
@ -1476,16 +1444,11 @@ impl Value {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
if let Some(val) = record.get_mut(col_name) {
val.remove_data_at_cell_path(path)?;
} else if !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
@ -1493,27 +1456,21 @@ impl Value {
}
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
})
});
}
}
}
Ok(())
}
Value::Record { val: record, .. } => {
let mut found = false;
for (col, val) in record.iter_mut() {
if col == col_name {
found = true;
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
if let Some(val) = record.get_mut(col_name) {
val.remove_data_at_cell_path(path)?;
} else if !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
@ -1528,7 +1485,7 @@ impl Value {
Ok(())
}
v => Err(ShellError::CantFindColumn {
col_name: col_name.to_string(),
col_name: col_name.clone(),
span: *span,
src_span: v.span(),
}),
@ -1540,7 +1497,7 @@ impl Value {
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.remove_data_at_cell_path(&cell_path[1..])
v.remove_data_at_cell_path(path)
} else if *optional {
Ok(())
} else if vals.is_empty() {
@ -1569,8 +1526,8 @@ impl Value {
head_span: Span,
) -> Result<(), ShellError> {
let v_span = self.span();
match cell_path.first() {
Some(path_member) => match path_member {
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
@ -1581,27 +1538,36 @@ impl Value {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
for (col, val) in record.iter_mut() {
if col == col_name {
if cell_path.len() == 1 {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.to_string(),
span: *span,
src_span: v_span,
});
} else {
return val.insert_data_at_cell_path(
&cell_path[1..],
new_val,
head_span,
);
}
if let Some(val) = record.get_mut(col_name) {
if path.is_empty() {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
} else {
val.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
}
} else {
let new_col = if path.is_empty() {
new_val.clone()
} else {
let mut new_col =
Value::record(Record::new(), new_val.span());
new_col.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
new_col
};
record.push(col_name, new_col);
}
record.push(col_name, new_val.clone());
}
// SIGH...
Value::Error { error, .. } => return Err(*error.clone()),
_ => {
return Err(ShellError::UnsupportedInput {
@ -1609,37 +1575,42 @@ impl Value {
input: format!("input type: {:?}", val.get_type()),
msg_span: head_span,
input_span: *span,
})
});
}
}
}
}
Value::Record { val: record, .. } => {
for (col, val) in record.iter_mut() {
if col == col_name {
if cell_path.len() == 1 {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.to_string(),
span: *span,
src_span: v_span,
});
} else {
return val.insert_data_at_cell_path(
&cell_path[1..],
new_val,
head_span,
);
}
if let Some(val) = record.get_mut(col_name) {
if path.is_empty() {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
} else {
val.insert_data_at_cell_path(path, new_val, head_span)?;
}
} else {
let new_col = if path.is_empty() {
new_val.clone()
} else {
let mut new_col = Value::record(Record::new(), new_val.span());
new_col.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
new_col
};
record.push(col_name, new_col);
}
record.push(col_name, new_val);
}
Value::LazyRecord { val, .. } => {
// convert to Record first.
let mut record = val.collect()?;
record.insert_data_at_cell_path(cell_path, new_val, v_span)?;
*self = record
*self = record;
}
other => {
return Err(ShellError::UnsupportedInput {
@ -1647,7 +1618,7 @@ impl Value {
input: format!("input type: {:?}", other.get_type()),
msg_span: head_span,
input_span: *span,
})
});
}
},
PathMember::Int {
@ -1655,8 +1626,8 @@ impl Value {
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.insert_data_at_cell_path(&cell_path[1..], new_val, head_span)?
} else if vals.len() == *row_num && cell_path.len() == 1 {
v.insert_data_at_cell_path(path, new_val, head_span)?;
} else if vals.len() == *row_num && path.is_empty() {
// If the insert is at 1 + the end of the list, it's OK.
// Otherwise, it's prohibited.
vals.push(new_val);
@ -1671,17 +1642,36 @@ impl Value {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
})
});
}
},
},
None => {
*self = new_val;
}
} else {
*self = new_val;
}
Ok(())
}
/// Check if the content is empty
pub fn is_empty(&self) -> bool {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
}
}
pub fn is_nothing(&self) -> bool {
matches!(self, Value::Nothing { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Value::Error { .. })
}
pub fn is_true(&self) -> bool {
matches!(self, Value::Bool { val: true, .. })
}

View File

@ -5,10 +5,10 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std"
edition = "2021"
license = "MIT"
name = "nu-std"
version = "0.87.1"
version = "0.87.2"
[dependencies]
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
nu-parser = { version = "0.87.1", path = "../nu-parser" }
nu-protocol = { version = "0.87.1", path = "../nu-protocol" }
nu-engine = { version = "0.87.1", path = "../nu-engine" }
nu-parser = { version = "0.87.2", path = "../nu-parser" }
nu-protocol = { version = "0.87.2", path = "../nu-protocol" }
nu-engine = { version = "0.87.2", path = "../nu-engine" }

View File

@ -335,3 +335,16 @@ export def repeat [
1..$n | each { $item }
}
# return a null device file.
#
# # Examples
# run a command and ignore it's stderr output
# > cat xxx.txt e> (null-device)
export def null-device []: nothing -> path {
if ($nu.os-info.name | str downcase) == "windows" {
'\\.\NUL'
} else {
"/dev/null"
}
}

View File

@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
description = "Nushell system querying"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
name = "nu-system"
version = "0.87.1"
version = "0.87.2"
edition = "2021"
license = "MIT"
@ -21,7 +21,7 @@ sysinfo = "0.29"
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
procfs = "0.15"
procfs = "0.16"
[target.'cfg(target_os = "macos")'.dependencies]
libproc = "0.14"

View File

@ -1,6 +1,6 @@
use log::info;
use procfs::process::{FDInfo, Io, Process, Stat, Status};
use procfs::{ProcError, ProcessCgroup};
use procfs::{ProcError, ProcessCGroups, WithCurrentSystemInfo};
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};
@ -25,7 +25,7 @@ impl ProcessTask {
}
}
pub fn cgroups(&self) -> Result<Vec<ProcessCgroup>, ProcError> {
pub fn cgroups(&self) -> Result<ProcessCGroups, ProcError> {
match self {
ProcessTask::Process(x) => x.cgroups(),
_ => Err(ProcError::Other("not supported".to_string())),
@ -218,7 +218,7 @@ impl ProcessInfo {
/// Memory size in number of bytes
pub fn mem_size(&self) -> u64 {
match self.curr_proc.stat() {
Ok(p) => p.rss_bytes(),
Ok(p) => p.rss_bytes().get(),
Err(_) => 0,
}
}

View File

@ -969,9 +969,9 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
let ret = LookupAccountSidW(
ptr::null::<u16>() as *mut u16,
psid,
name.as_mut_ptr() as *mut u16,
name.as_mut_ptr(),
&mut cc_name,
domainname.as_mut_ptr() as *mut u16,
domainname.as_mut_ptr(),
&mut cc_domainname,
&mut pe_use,
);

View File

@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
edition = "2021"
license = "MIT"
name = "nu-table"
version = "0.87.1"
version = "0.87.2"
[lib]
bench = false
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.87.1" }
nu-utils = { path = "../nu-utils", version = "0.87.1" }
nu-engine = { path = "../nu-engine", version = "0.87.1" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" }
nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-ansi-term = "0.49.0"
once_cell = "1.18"
fancy-regex = "0.11"
tabled = { version = "0.14.0", features = ["color"], default-features = false }
[dev-dependencies]
# nu-test-support = { path="../nu-test-support", version = "0.87.1" }
# nu-test-support = { path="../nu-test-support", version = "0.87.2" }

View File

@ -17,9 +17,10 @@ pub fn create_nu_table_config(
comp: &StyleComputer,
out: &TableOutput,
expand: bool,
mode: TableMode,
) -> NuTableConfig {
NuTableConfig {
theme: load_theme_from_config(config),
theme: load_theme(mode),
with_footer: with_footer(config, out.with_header, out.table.count_rows()),
with_index: out.with_index,
with_header: out.with_header,
@ -173,8 +174,8 @@ fn is_cfg_trim_keep_words(config: &Config) -> bool {
)
}
pub fn load_theme_from_config(config: &Config) -> TableTheme {
match config.table_mode {
pub fn load_theme(mode: TableMode) -> TableTheme {
match mode {
TableMode::Basic => TableTheme::basic(),
TableMode::Thin => TableTheme::thin(),
TableMode::Light => TableTheme::light(),

View File

@ -1,11 +1,11 @@
use nu_color_config::StyleComputer;
use nu_protocol::{Config, Record, Value};
use nu_protocol::{Config, Record, TableMode, Value};
use crate::UnstructuredTable;
use crate::common::nu_value_to_string_clean;
use crate::{
common::{get_index_style, load_theme_from_config},
common::{get_index_style, load_theme},
StringResult, TableOpts,
};
@ -13,7 +13,13 @@ pub struct CollapsedTable;
impl CollapsedTable {
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
collapsed_table(value, opts.config, opts.width, opts.style_computer)
collapsed_table(
value,
opts.config,
opts.width,
opts.style_computer,
opts.mode,
)
}
}
@ -22,10 +28,11 @@ fn collapsed_table(
config: &Config,
term_width: usize,
style_computer: &StyleComputer,
mode: TableMode,
) -> StringResult {
colorize_value(&mut value, config, style_computer);
let theme = load_theme_from_config(config);
let theme = load_theme(mode);
let mut table = UnstructuredTable::new(value, config);
let is_empty = table.truncate(&theme, term_width);
if is_empty {

View File

@ -3,17 +3,18 @@ use std::collections::HashMap;
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{Config, Record, ShellError, Span, TableIndexMode, Value};
use nu_protocol::{Config, Record, ShellError, Span, Value};
use tabled::grid::config::Position;
use crate::{
common::{
create_nu_table_config, error_sign, get_header_style, get_index_style,
load_theme_from_config, nu_value_to_string, nu_value_to_string_clean,
nu_value_to_string_colored, wrap_text, NuText, StringResult, TableResult,
INDEX_COLUMN_NAME,
create_nu_table_config, error_sign, get_header_style, get_index_style, load_theme,
nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text,
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
},
string_width, NuTable, NuTableCell, TableOpts, TableOutput,
string_width,
types::has_index,
NuTable, NuTableCell, TableOpts, TableOutput,
};
#[derive(Debug, Clone)]
@ -83,11 +84,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let headers = get_columns(input);
let with_index = match cfg.opts.config.table_index_mode {
TableIndexMode::Always => true,
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
};
let with_index = has_index(&cfg.opts, &headers);
let row_offset = cfg.opts.index_offset;
// The header with the INDEX is removed from the table headers since
// it is added to the natural table index
@ -115,7 +113,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let index = row + cfg.opts.row_offset;
let index = row + row_offset;
let text = item
.as_record()
.ok()
@ -156,8 +154,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let mut inner_cfg = cfg.clone();
inner_cfg.opts.width = available_width;
let inner_cfg = update_config(cfg.clone(), available_width);
let (mut text, style) = expanded_table_entry2(item, inner_cfg);
let value_width = string_width(&text);
@ -244,8 +241,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone());
}
let mut inner_cfg = cfg.clone();
inner_cfg.opts.width = available;
let inner_cfg = update_config(cfg.clone(), available);
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
let mut value_width = string_width(&text);
@ -343,7 +339,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> StringResult {
let theme = load_theme_from_config(cfg.opts.config);
let theme = load_theme(cfg.opts.mode);
let key_width = record
.columns()
.map(|col| string_width(col))
@ -407,8 +403,7 @@ fn expand_table_value(
let span = value.span();
match value {
Value::List { vals, .. } => {
let mut inner_cfg = dive_options(cfg, span);
inner_cfg.opts.width = value_width;
let inner_cfg = update_config(dive_options(cfg, span), value_width);
let table = expanded_table_list(vals, inner_cfg)?;
match table {
@ -438,8 +433,7 @@ fn expand_table_value(
)));
}
let mut inner_cfg = dive_options(cfg, span);
inner_cfg.opts.width = value_width;
let inner_cfg = update_config(dive_options(cfg, span), value_width);
let result = expanded_table_kv(record, inner_cfg)?;
match result {
Some(result) => Ok(Some((result, true))),
@ -556,7 +550,8 @@ fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
}
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
let mut table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
let mut table_config =
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
let total_width = out.table.total_width(&table_config);
if total_width < term_width {
const EXPAND_THRESHOLD: f32 = 0.80;
@ -577,7 +572,13 @@ fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
}
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
create_nu_table_config(cfg.opts.config, cfg.opts.style_computer, out, false)
create_nu_table_config(
cfg.opts.config,
cfg.opts.style_computer,
out,
false,
cfg.opts.mode,
)
}
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
@ -596,3 +597,10 @@ fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usiz
let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer);
wrap_text(&text, value_width, cfg.opts.config)
}
fn update_config(cfg: Cfg<'_>, width: usize) -> Cfg<'_> {
let mut inner_cfg = cfg.clone();
inner_cfg.opts.width = width;
inner_cfg.opts.index_offset = 0;
inner_cfg
}

View File

@ -1,6 +1,6 @@
use nu_color_config::TextStyle;
use nu_engine::column::get_columns;
use nu_protocol::{Config, Record, ShellError, TableIndexMode, Value};
use nu_protocol::{Config, Record, ShellError, Value};
use crate::{
clean_charset, colorize_space,
@ -11,6 +11,8 @@ use crate::{
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
};
use super::has_index;
pub struct JustTable;
impl JustTable {
@ -24,7 +26,7 @@ impl JustTable {
}
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
match table(input, opts.row_offset, opts.clone())? {
match table(input, &opts)? {
Some(mut out) => {
let left = opts.config.table_indent.left;
let right = opts.config.table_indent.right;
@ -33,7 +35,7 @@ fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>,
colorize_space(out.table.get_records_mut(), opts.style_computer);
let table_config =
create_nu_table_config(opts.config, opts.style_computer, &out, false);
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
Ok(out.table.draw(table_config, opts.width))
}
None => Ok(None),
@ -65,23 +67,21 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
let right = opts.config.table_indent.right;
out.table.set_indent(left, right);
let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
let table_config =
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
let table = out.table.draw(table_config, opts.width);
Ok(table)
}
fn table(input: &[Value], row_offset: usize, opts: TableOpts<'_>) -> TableResult {
fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
if input.is_empty() {
return Ok(None);
}
let mut headers = get_columns(input);
let with_index = match opts.config.table_index_mode {
TableIndexMode::Always => true,
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
};
let with_index = has_index(opts, &headers);
let row_offset = opts.index_offset;
let with_header = !headers.is_empty();
if !with_header {
@ -112,7 +112,7 @@ fn to_table_with_header(
headers: Vec<String>,
with_index: bool,
row_offset: usize,
opts: TableOpts<'_>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let count_rows = input.len() + 1;
let count_columns = headers.len();
@ -140,7 +140,7 @@ fn to_table_with_header(
let skip_index = usize::from(with_index);
for (col, header) in headers.iter().enumerate().skip(skip_index) {
let (text, style) = get_string_value_with_header(item, header, &opts);
let (text, style) = get_string_value_with_header(item, header, opts);
table.insert((row + 1, col), text);
table.insert_style((row + 1, col), style);
@ -154,7 +154,7 @@ fn to_table_with_no_header(
input: &[Value],
with_index: bool,
row_offset: usize,
opts: TableOpts<'_>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), with_index as usize + 1);
table.set_index_style(get_index_style(opts.style_computer));
@ -173,7 +173,7 @@ fn to_table_with_no_header(
table.insert((row, 0), text);
}
let (text, style) = get_string_value(item, &opts);
let (text, style) = get_string_value(item, opts);
let pos = (row, with_index as usize);
table.insert(pos, text);

View File

@ -8,9 +8,9 @@ pub use collapse::CollapsedTable;
pub use expanded::ExpandedTable;
pub use general::JustTable;
use nu_color_config::StyleComputer;
use nu_protocol::{Config, Span};
use nu_protocol::{Config, Span, TableIndexMode, TableMode};
use crate::NuTable;
use crate::{common::INDEX_COLUMN_NAME, NuTable};
pub struct TableOutput {
pub table: NuTable,
@ -34,29 +34,46 @@ pub struct TableOpts<'a> {
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
span: Span,
row_offset: usize,
width: usize,
indent: (usize, usize),
mode: TableMode,
index_offset: usize,
index_remove: bool,
}
impl<'a> TableOpts<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
config: &'a Config,
style_computer: &'a StyleComputer<'a>,
ctrlc: Option<Arc<AtomicBool>>,
span: Span,
row_offset: usize,
width: usize,
indent: (usize, usize),
mode: TableMode,
index_offset: usize,
index_remove: bool,
) -> Self {
Self {
ctrlc,
config,
style_computer,
span,
row_offset,
indent,
width,
mode,
index_offset,
index_remove,
}
}
}
fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool {
let with_index = match opts.config.table_index_mode {
TableIndexMode::Always => true,
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
};
with_index && !opts.index_remove
}

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