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

View File

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

View File

@ -67,7 +67,7 @@ impl NuCompleter {
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures); let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
// Line // 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 { if let Some(var_id) = pos_arg.var_id {
callee_stack.add_var( callee_stack.add_var(
var_id, var_id,

View File

@ -111,7 +111,7 @@ fn gather_env_vars(
let name = if let Some(Token { let name = if let Some(Token {
contents: TokenContents::Item, contents: TokenContents::Item,
span, span,
}) = parts.get(0) }) = parts.first()
{ {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let bytes = working_set.get_span_contents(*span); 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()); let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.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 // Test use completion
let completion_str = "use ".to_string(); let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.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] #[test]
@ -141,7 +141,7 @@ fn external_completer_trailing_space() {
let suggestions = run_external_completion(block, &input); let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len()); 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!("alias", suggestions.get(1).unwrap().value);
assert_eq!("", suggestions.get(2).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); let suggestions = run_external_completion(block, &input);
assert_eq!(2, suggestions.len()); 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); assert_eq!("alias", suggestions.get(1).unwrap().value);
} }
@ -164,7 +164,7 @@ fn external_completer_pass_flags() {
let suggestions = run_external_completion(block, &input); let suggestions = run_external_completion(block, &input);
assert_eq!(3, suggestions.len()); 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!("api", suggestions.get(1).unwrap().value);
assert_eq!("--", suggestions.get(2).unwrap().value); assert_eq!("--", suggestions.get(2).unwrap().value);
} }

View File

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

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-dataframe" name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/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 # 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 bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
# Potential dependencies for extras # Potential dependencies for extras
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false } chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
@ -66,5 +66,5 @@ dataframe = ["num", "polars", "polars-io", "sqlparser"]
default = [] default = []
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", 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.1" } 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 df = NuDataFrame::try_from_pipeline(input, call.head)?;
let new_df = col_string let new_df = col_string
.get(0) .first()
.ok_or_else(|| { .ok_or_else(|| {
ShellError::GenericError( ShellError::GenericError(
"Empty names list".into(), "Empty names list".into(),

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-extra" name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/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 # 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 bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
# Potential dependencies for extras # Potential dependencies for extras
heck = "0.4.1" heck = "0.4.1"
@ -27,8 +27,8 @@ nu-ansi-term = "0.49.0"
fancy-regex = "0.11.0" fancy-regex = "0.11.0"
rust-embed = "8.0.0" rust-embed = "8.0.0"
serde = "1.0.164" serde = "1.0.164"
nu-pretty-hex = { version = "0.87.1", path = "../nu-pretty-hex" } nu-pretty-hex = { version = "0.87.2", path = "../nu-pretty-hex" }
nu-json = { version = "0.87.1", path = "../nu-json" } nu-json = { version = "0.87.2", path = "../nu-json" }
serde_urlencoded = "0.7.1" serde_urlencoded = "0.7.1"
htmlescape = "0.3.1" htmlescape = "0.3.1"
@ -37,6 +37,6 @@ extra = ["default"]
default = [] default = []
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-command = { path = "../nu-command", version = "0.87.1" } nu-command = { path = "../nu-command", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" } 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" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.87.1" version = "0.87.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
fancy-regex = "0.11" 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 const_;
mod continue_; mod continue_;
mod def; mod def;
mod def_env;
mod describe; mod describe;
mod do_; mod do_;
mod echo; mod echo;
@ -13,13 +12,10 @@ mod export;
mod export_alias; mod export_alias;
mod export_const; mod export_const;
mod export_def; mod export_def;
mod export_def_env;
mod export_extern; mod export_extern;
mod export_extern_wrapped;
mod export_module; mod export_module;
mod export_use; mod export_use;
mod extern_; mod extern_;
mod extern_wrapped;
mod for_; mod for_;
mod hide; mod hide;
mod hide_env; mod hide_env;
@ -45,7 +41,6 @@ pub use collect::Collect;
pub use const_::Const; pub use const_::Const;
pub use continue_::Continue; pub use continue_::Continue;
pub use def::Def; pub use def::Def;
pub use def_env::DefEnv;
pub use describe::Describe; pub use describe::Describe;
pub use do_::Do; pub use do_::Do;
pub use echo::Echo; pub use echo::Echo;
@ -54,13 +49,10 @@ pub use export::ExportCommand;
pub use export_alias::ExportAlias; pub use export_alias::ExportAlias;
pub use export_const::ExportConst; pub use export_const::ExportConst;
pub use export_def::ExportDef; pub use export_def::ExportDef;
pub use export_def_env::ExportDefEnv;
pub use export_extern::ExportExtern; pub use export_extern::ExportExtern;
pub use export_extern_wrapped::ExportExternWrapped;
pub use export_module::ExportModule; pub use export_module::ExportModule;
pub use export_use::ExportUse; pub use export_use::ExportUse;
pub use extern_::Extern; pub use extern_::Extern;
pub use extern_wrapped::ExternWrapped;
pub use for_::For; pub use for_::For;
pub use hide::Hide; pub use hide::Hide;
pub use hide_env::HideEnv; pub use hide_env::HideEnv;

View File

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

View File

@ -14,8 +14,7 @@ mod test_examples {
check_example_input_and_output_types_match_command_signature, check_example_input_and_output_types_match_command_signature,
}; };
use crate::{ use crate::{
Break, Collect, Def, DefEnv, Describe, Echo, ExportCommand, ExportDef, ExportDefEnv, If, Break, Collect, Def, Describe, Echo, ExportCommand, ExportDef, If, Let, Module, Mut, Use,
Let, Module, Mut, Use,
}; };
use nu_protocol::{ use nu_protocol::{
engine::{Command, EngineState, StateWorkingSet}, engine::{Command, EngineState, StateWorkingSet},
@ -69,12 +68,10 @@ mod test_examples {
working_set.add_decl(Box::new(Break)); working_set.add_decl(Box::new(Break));
working_set.add_decl(Box::new(Collect)); working_set.add_decl(Box::new(Collect));
working_set.add_decl(Box::new(Def)); 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(Describe));
working_set.add_decl(Box::new(Echo)); working_set.add_decl(Box::new(Echo));
working_set.add_decl(Box::new(ExportCommand)); working_set.add_decl(Box::new(ExportCommand));
working_set.add_decl(Box::new(ExportDef)); 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(If));
working_set.add_decl(Box::new(Let)); working_set.add_decl(Box::new(Let));
working_set.add_decl(Box::new(Module)); 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" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-color-config" name = "nu-color-config"
version = "0.87.1" version = "0.87.2"
[lib] [lib]
bench = false bench = false
[dependencies] [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-ansi-term = "0.49.0"
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-json = { path = "../nu-json", version = "0.87.1" } nu-json = { path = "../nu-json", version = "0.87.2" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
[dev-dependencies] [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" license = "MIT"
name = "nu-command" name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -14,19 +14,19 @@ bench = false
[dependencies] [dependencies]
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
nu-cmd-base = { path = "../nu-cmd-base", 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.1" } nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-glob = { path = "../nu-glob", version = "0.87.1" } nu-glob = { path = "../nu-glob", version = "0.87.2" }
nu-json = { path = "../nu-json", version = "0.87.1" } nu-json = { path = "../nu-json", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.1" } nu-path = { path = "../nu-path", version = "0.87.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-system = { path = "../nu-system", version = "0.87.1" } nu-system = { path = "../nu-system", version = "0.87.2" }
nu-table = { path = "../nu-table", version = "0.87.1" } nu-table = { path = "../nu-table", version = "0.87.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.87.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
alphanumeric-sort = "1.5" alphanumeric-sort = "1.5"
base64 = "0.21" base64 = "0.21"
@ -47,6 +47,7 @@ filesize = "0.2"
filetime = "0.2" filetime = "0.2"
fs_extra = "1.3" fs_extra = "1.3"
htmlescape = "0.3" htmlescape = "0.3"
human-date-parser = "0.1.1"
indexmap = "2.1" indexmap = "2.1"
indicatif = "0.17" indicatif = "0.17"
itertools = "0.11" itertools = "0.11"
@ -67,7 +68,7 @@ os_pipe = "1.1"
pathdiff = "0.2" pathdiff = "0.2"
percent-encoding = "2.3" percent-encoding = "2.3"
print-positions = "0.6" print-positions = "0.6"
quick-xml = "0.30" quick-xml = "0.31"
rand = "0.8" rand = "0.8"
rayon = "1.8" rayon = "1.8"
regex = "1.9.5" regex = "1.9.5"
@ -87,23 +88,27 @@ toml = "0.8"
unicode-segmentation = "1.10" unicode-segmentation = "1.10"
ureq = { version = "2.8", default-features = false, features = ["charset", "gzip", "json", "native-tls"] } ureq = { version = "2.8", default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
url = "2.2" url = "2.2"
uu_cp = "0.0.22" uu_cp = "0.0.23"
uu_whoami = "0.0.22" uu_whoami = "0.0.23"
uu_mkdir = "0.0.22" uu_mkdir = "0.0.23"
uuid = { version = "1.5", features = ["v4"] } uu_mktemp = "0.0.23"
uuid = { version = "1.6", features = ["v4"] }
wax = { version = "0.6" } wax = { version = "0.6" }
which = { version = "5.0", optional = true } which = { version = "5.0", optional = true }
bracoxide = "0.1.2" bracoxide = "0.1.2"
chardetng = "0.1.17" chardetng = "0.1.17"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.51" winreg = "0.52"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
umask = "2.1" umask = "2.1"
nix = { version = "0.27", default-features = false, features = ["user"] } 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] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
optional = true optional = true
version = "3.1" version = "3.1"
@ -126,8 +131,8 @@ trash-support = ["trash"]
which-support = ["which"] which-support = ["which"]
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", 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.1" } nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
dirs-next = "2.0" dirs-next = "2.0"
mockito = { version = "1.2", default-features = false } 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") { let val = if cfg!(target_endian = "little") {
match val.iter().rposition(|&x| x != 0) { match val.iter().rposition(|&x| x != 0) {
Some(idx) => &val[..idx + 1], Some(idx) => &val[..idx + 1],
None => &val,
// all 0s should just return a single 0 byte
None => &[0],
} }
} else { } else {
match val.iter().position(|&x| x != 0) { match val.iter().position(|&x| x != 0) {
Some(idx) => &val[idx..], Some(idx) => &val[idx..],
None => &val, None => &[0],
} }
}; };

View File

@ -1,13 +1,14 @@
use crate::{generate_strftime_list, parse_date_from_string}; use crate::{generate_strftime_list, parse_date_from_string};
use chrono::NaiveTime;
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc}; 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_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, ast::{Call, CellPath},
SyntaxShape, Type, Value, engine::{Command, EngineState, Stack},
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
}; };
struct Arguments { struct Arguments {
@ -95,6 +96,11 @@ impl Command for SubCommand {
"Show all possible variables for use in --format flag", "Show all possible variables for use in --format flag",
Some('l'), Some('l'),
) )
.switch(
"list-human",
"Show human-readable datetime parsing examples",
Some('n'),
)
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
@ -112,6 +118,8 @@ impl Command for SubCommand {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if call.has_flag("list") { if call.has_flag("list") {
Ok(generate_strftime_list(call.head, true).into_pipeline_data()) 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 { } else {
let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
@ -225,6 +233,21 @@ impl Command for SubCommand {
Span::test_data(), 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() { if let Ok(input_val) = input.as_spanned_string() {
match parse_date_from_string(&input_val.item, input_val.span) { match parse_date_from_string(&input_val.item, input_val.span) {
Ok(date) => return Value::date(date, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -22,7 +22,15 @@ impl Command for ViewFiles {
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("view files") 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) .category(Category::Debug)
} }
@ -51,10 +59,17 @@ impl Command for ViewFiles {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "View the files registered in nushell's EngineState memory", Example {
description: "View the files registered in Nushell's EngineState memory",
example: r#"view files"#, example: r#"view files"#,
result: None, 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, EncodeBase64,
DetectColumns, DetectColumns,
Parse, Parse,
Size,
Split, Split,
SplitChars, SplitChars,
SplitColumn, SplitColumn,
@ -203,6 +202,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Ls, Ls,
Mkdir, Mkdir,
UMkdir, UMkdir,
Mktemp,
Mv, Mv,
Cp, Cp,
UCp, UCp,
@ -382,7 +382,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Seq, Seq,
SeqDate, SeqDate,
SeqChar, SeqChar,
Unfold, // deprecated
Generate, Generate,
}; };

View File

@ -43,12 +43,6 @@ impl Command for Glob {
"Whether to filter out symlinks from the returned paths", "Whether to filter out symlinks from the returned paths",
Some('S'), Some('S'),
) )
.named(
"not",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"DEPRECATED OPTION: Patterns to exclude from the results",
Some('n'),
)
.named( .named(
"exclude", "exclude",
SyntaxShape::List(Box::new(SyntaxShape::String)), SyntaxShape::List(Box::new(SyntaxShape::String)),
@ -147,35 +141,7 @@ impl Command for Glob {
let no_files = call.has_flag("no-file"); let no_files = call.has_flag("no-file");
let no_symlinks = call.has_flag("no-symlink"); let no_symlinks = call.has_flag("no-symlink");
if call.has_flag("not") { let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
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 (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude { let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
None => (vec![], span), 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 glob;
mod ls; mod ls;
mod mkdir; mod mkdir;
mod mktemp;
mod mv; mod mv;
mod open; mod open;
mod rm; mod rm;
@ -20,6 +21,7 @@ pub use cp::Cp;
pub use glob::Glob; pub use glob::Glob;
pub use ls::Ls; pub use ls::Ls;
pub use mkdir::Mkdir; pub use mkdir::Mkdir;
pub use mktemp::Mktemp;
pub use mv::Mv; pub use mv::Mv;
pub use rm::Rm; pub use rm::Rm;
pub use save::Save; pub use save::Save;

View File

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

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(Value::Error { .. }) | None => Value::string(error_key, span),
Some(return_value) => return_value.clone(), Some(return_value) => return_value.clone(),
}; };

View File

@ -171,7 +171,7 @@ fn insert(
ctrlc, ctrlc,
) )
} else { } 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 input = input.into_iter();
let mut pre_elems = vec![]; let mut pre_elems = vec![];

View File

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

View File

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

View File

@ -190,7 +190,7 @@ pub fn transpose(
if args.header_row { if args.header_row {
for i in input.iter() { 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) { match &i.get_data_by_key(desc) {
Some(x) => { Some(x) => {
if let Ok(s) = x.as_string() { if let Ok(s) = x.as_string() {

View File

@ -171,7 +171,7 @@ fn update(
)? )?
.set_metadata(mdclone)) .set_metadata(mdclone))
} else { } 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 input = input.into_iter();
let mut pre_elems = vec![]; let mut pre_elems = vec![];

View File

@ -186,7 +186,7 @@ fn upsert(
ctrlc, ctrlc,
) )
} else { } 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 input = input.into_iter();
let mut pre_elems = vec![]; 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); 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(pipeline) = block.pipelines.get(1) {
if let Some(element) = pipeline.elements.get(0) { if let Some(element) = pipeline.elements.first() {
return Err(ShellError::GenericError( return Err(ShellError::GenericError(
"error when loading nuon text".into(), "error when loading nuon text".into(),
"could not load nuon text".into(), "could not load nuon text".into(),

View File

@ -3,11 +3,9 @@ mod generate;
mod seq; mod seq;
mod seq_char; mod seq_char;
mod seq_date; mod seq_date;
mod unfold;
pub use cal::Cal; pub use cal::Cal;
pub use generate::Generate; pub use generate::Generate;
pub use seq::Seq; pub use seq::Seq;
pub use seq_char::SeqChar; pub use seq_char::SeqChar;
pub use seq_date::SeqDate; 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 result
}; };
let result = if let Err(ShellError::CommandNotFound(_)) = result { let result = if let Err(ShellError::CommandNotFound { .. }) = result {
help_modules(engine_state, stack, call) help_modules(engine_state, stack, call)
} else { } else {
result result

View File

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

View File

@ -133,10 +133,9 @@ pub fn help_externs(
.into_pipeline_data(), .into_pipeline_data(),
) )
} else { } else {
Err(ShellError::CommandNotFound(span(&[ Err(ShellError::CommandNotFound {
rest[0].span, span: span(&[rest[0].span, rest[rest.len() - 1].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> { 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 { let mut acc = match initial_value {
Some(v) => { 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> { 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 { let mut acc = match initial_value {
Some(v) => { 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 { fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
match response_err { match response_err {
Error::Status(301, _) => ShellError::NetworkFailure( Error::Status(301, _) => ShellError::NetworkFailure { msg: format!("Resource moved permanently (301): {requested_url:?}"), span },
format!("Resource moved permanently (301): {requested_url:?}"),
span,
),
Error::Status(400, _) => { 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, _) => { 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( Error::Status(404, _) => ShellError::NetworkFailure { msg: format!("Requested file not found (404): {requested_url:?}"), span },
format!("Requested file not found (404): {requested_url:?}"),
span,
),
Error::Status(408, _) => { 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( Error::Status(_, _) => ShellError::NetworkFailure { msg: format!(
format!(
"Cannot make request to {:?}. Error is {:?}", "Cannot make request to {:?}. Error is {:?}",
requested_url, requested_url,
response_err.to_string() response_err.to_string()
), ), span },
span,
),
Error::Transport(t) => match t { Error::Transport(t) => match t {
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure( t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure { msg: format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), span },
format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), t => ShellError::NetworkFailure { msg: t.to_string(), span },
span,
),
t => ShellError::NetworkFailure(t.to_string(), span),
}, },
} }
} }

View File

@ -3,7 +3,6 @@ mod detect_columns;
mod encode_decode; mod encode_decode;
mod format; mod format;
mod parse; mod parse;
mod size;
mod split; mod split;
mod str_; mod str_;
@ -12,7 +11,6 @@ pub use detect_columns::*;
pub use encode_decode::*; pub use encode_decode::*;
pub use format::*; pub use format::*;
pub use parse::*; pub use parse::*;
pub use size::Size;
pub use split::*; pub use split::*;
pub use str_::*; 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, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Type, Value, 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; use std::time::Duration;
@ -127,13 +135,11 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result<PipelineData, Shell
Vec::new(), 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 // If we can't get the start time, just use the current time
chrono::Local::now() 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("start_time", Value::date(proc_start.into(), span));
record.push("user_id", Value::int(proc.curr_proc.owner() as i64, 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 // 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 lscolors::{LsColors, Style};
use nu_color_config::color_from_hex; use nu_color_config::color_from_hex;
use nu_color_config::{StyleComputer, TextStyle}; use nu_color_config::{StyleComputer, TextStyle};
use nu_engine::{env::get_config, env_to_string, CallExt}; use nu_engine::{env::get_config, env_to_string, CallExt};
use nu_protocol::record;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData, Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
PipelineMetadata, RawStream, Record, ShellError, Signature, Span, SyntaxShape, Type, Value, 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::common::create_nu_table_config;
use nu_table::{ use nu_table::{
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
@ -16,6 +20,7 @@ use nu_table::{
}; };
use nu_utils::get_ls_colors; use nu_utils::get_ls_colors;
use std::io::IsTerminal; use std::io::IsTerminal;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use std::{path::PathBuf, sync::atomic::AtomicBool}; use std::{path::PathBuf, sync::atomic::AtomicBool};
@ -60,12 +65,17 @@ impl Command for Table {
.input_output_types(vec![(Type::Any, Type::Any)]) .input_output_types(vec![(Type::Any, Type::Any)])
// TODO: make this more precise: what turns into string and what into raw stream // TODO: make this more precise: what turns into string and what into raw stream
.named( .named(
"start-number", "theme",
SyntaxShape::Int, SyntaxShape::String,
"row number to start viewing from", "set a table mode/theme",
Some('n'), 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( .named(
"width", "width",
SyntaxShape::Int, SyntaxShape::Int,
@ -80,7 +90,7 @@ impl Command for Table {
.named( .named(
"expand-deep", "expand-deep",
SyntaxShape::Int, 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'), Some('d'),
) )
.switch("flatten", "Flatten simple arrays", None) .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", "abbreviate the data in the table by truncating the middle part and only showing amount provided on top and bottom",
Some('a'), Some('a'),
) )
.switch("list", "list available table modes/themes", Some('l'))
.category(Category::Viewers) .category(Category::Viewers)
} }
@ -112,15 +123,15 @@ impl Command for Table {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let list_themes: bool = call.has_flag("list"); 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 argument is present we just need to return a list of supported table themes
if list_themes { if list_themes {
let val = Value::list(supported_table_modes(), Span::test_data()); let val = Value::list(supported_table_modes(), Span::test_data());
return Ok(val.into_pipeline_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 // reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)] #[cfg(windows)]
{ {
@ -133,8 +144,8 @@ impl Command for Table {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "List the files in current directory, with indexes starting from 1.", description: "List the files in current directory, with indexes starting from 1",
example: r#"ls | table --start-number 1"#, example: r#"ls | table --index 1"#,
result: None, result: None,
}, },
Example { 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)] #[derive(Debug, Clone)]
struct TableConfig { struct TableConfig {
row_offset: usize, index: Option<usize>,
table_view: TableView, table_view: TableView,
term_width: usize, term_width: usize,
theme: TableMode,
abbreviation: Option<usize>, abbreviation: Option<usize>,
} }
impl TableConfig { impl TableConfig {
fn new( fn new(
row_offset: usize,
table_view: TableView, table_view: TableView,
term_width: usize, term_width: usize,
theme: TableMode,
abbreviation: Option<usize>, abbreviation: Option<usize>,
index: Option<usize>,
) -> Self { ) -> Self {
Self { Self {
row_offset, index,
table_view, table_view,
term_width, term_width,
abbreviation, abbreviation,
theme,
} }
} }
} }
@ -212,8 +247,6 @@ fn parse_table_config(
state: &EngineState, state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
) -> Result<TableConfig, ShellError> { ) -> 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 width_param: Option<i64> = call.get_flag(state, stack, "width")?;
let expand: bool = call.has_flag("expand"); let expand: bool = call.has_flag("expand");
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?; let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
@ -232,13 +265,74 @@ fn parse_table_config(
flatten_separator, 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 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) 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> { struct CmdInput<'a> {
engine_state: &'a EngineState, engine_state: &'a EngineState,
stack: &'a mut Stack, stack: &'a mut Stack,
@ -366,7 +460,17 @@ fn handle_record(
} }
let indent = (config.table_indent.left, config.table_indent.right); 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 = build_table_kv(record, cfg.table_view, opts, span)?;
let result = match result { let result = match result {
@ -581,6 +685,7 @@ struct PagingTableCreator {
elements_displayed: usize, elements_displayed: usize,
reached_end: bool, reached_end: bool,
cfg: TableConfig, cfg: TableConfig,
row_offset: usize,
} }
impl PagingTableCreator { impl PagingTableCreator {
@ -601,6 +706,7 @@ impl PagingTableCreator {
cfg, cfg,
elements_displayed: 0, elements_displayed: 0,
reached_end: false, reached_end: false,
row_offset: 0,
} }
} }
@ -657,9 +763,11 @@ impl PagingTableCreator {
style_comp, style_comp,
self.ctrlc.clone(), self.ctrlc.clone(),
self.head, self.head,
self.cfg.row_offset,
self.cfg.term_width, self.cfg.term_width,
(cfg.table_indent.left, cfg.table_indent.right), (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); let table = self.build_table(batch);
self.cfg.row_offset += idx; self.row_offset += idx;
let config = get_config(&self.engine_state, &self.stack); let config = get_config(&self.engine_state, &self.stack);
convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width) 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 out = TableOutput::new(table, false, false);
let style_computer = &StyleComputer::from_config(engine_state, stack); 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 out.table
.draw(config, termwidth) .draw(config, termwidth)

View File

@ -269,10 +269,10 @@ fn cd_permission_denied_folder() {
sandbox.mkdir("banned"); sandbox.mkdir("banned");
let actual = nu!( let actual = nu!(
cwd: dirs.test(), cwd: dirs.test(),
r#" r"
icacls banned /deny BUILTIN\Administrators:F icacls banned /deny BUILTIN\Administrators:F
cd banned cd banned
"# "
); );
assert!(actual.err.contains("Folder is not able to read")); 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"); 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 math;
mod merge; mod merge;
mod mkdir; mod mkdir;
mod mktemp;
mod move_; mod move_;
mod mut_; mod mut_;
mod network; mod network;

View File

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

View File

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

View File

@ -21,7 +21,7 @@ fn redirect_err() {
Playground::setup("redirect_err_test", |dirs, _sandbox| { Playground::setup("redirect_err_test", |dirs, _sandbox| {
let output = nu!( let output = nu!(
cwd: dirs.test(), 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")); assert!(output.out.contains("true"));
@ -50,7 +50,7 @@ fn redirect_outerr() {
cwd: dirs.test(), cwd: dirs.test(),
"vol missingdrive out+err> a" "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")); 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"#); nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
assert_eq!(actual.out, "10"); 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] #[test]
#[allow(clippy::needless_raw_string_hashes)]
fn from_csv_text_with_comments_to_table() { fn from_csv_text_with_comments_to_table() {
Playground::setup("filter_from_csv_test_5", |dirs, sandbox| { Playground::setup("filter_from_csv_test_5", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed( sandbox.with_files(vec![FileWithContentToBeTrimmed(

View File

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

View File

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

View File

@ -795,10 +795,10 @@ fn eval_element_with_input(
) )
}) })
} else { } else {
Err(ShellError::CommandNotFound(*span)) Err(ShellError::CommandNotFound { span: *span })
} }
} }
_ => Err(ShellError::CommandNotFound(*span)), _ => Err(ShellError::CommandNotFound { span: *span }),
}, },
PipelineElement::SeparateRedirection { PipelineElement::SeparateRedirection {
out: (out_span, out_expr), out: (out_span, out_expr),
@ -842,14 +842,14 @@ fn eval_element_with_input(
) )
}) })
} else { } else {
Err(ShellError::CommandNotFound(*out_span)) Err(ShellError::CommandNotFound { span: *out_span })
} }
} }
(_out_other, err_other) => { (_out_other, err_other) => {
if let Expr::String(_) = err_other { if let Expr::String(_) = err_other {
Err(ShellError::CommandNotFound(*out_span)) Err(ShellError::CommandNotFound { span: *out_span })
} else { } 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" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-explore" name = "nu-explore"
version = "0.87.1" version = "0.87.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" } nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-table = { path = "../nu-table", version = "0.87.1" } nu-table = { path = "../nu-table", version = "0.87.2" }
nu-json = { path = "../nu-json", version = "0.87.1" } nu-json = { path = "../nu-json", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
terminal_size = "0.2" terminal_size = "0.2"
strip-ansi-escapes = "0.2.0" 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 NuText = (String, TextStyle);
pub type CtrlC = Option<Arc<AtomicBool>>; 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 lscolor::{create_lscolors, lscolorize};
pub use string::{string_width, truncate_str}; pub use string::{string_width, truncate_str};
pub use table::try_build_table; pub use table::try_build_table;

View File

@ -38,9 +38,11 @@ fn try_build_map(
style_computer, style_computer,
ctrlc, ctrlc,
Span::unknown(), Span::unknown(),
0,
usize::MAX, usize::MAX,
(config.table_indent.left, config.table_indent.right), (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); let result = ExpandedTable::new(None, false, String::new()).build_map(&record, opts);
match result { match result {
@ -63,10 +65,13 @@ fn try_build_list(
style_computer, style_computer,
ctrlc, ctrlc,
Span::unknown(), Span::unknown(),
0,
usize::MAX, usize::MAX,
(config.table_indent.left, config.table_indent.right), (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); let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
match result { match result {
Ok(Some(out)) => out, 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 layer = v.get_layer_last();
let cols = layer.columns.to_vec(); 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()) Value::record(Record::from_raw_cols_vals(cols, vals), NuSpan::unknown())
} }

View File

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

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-json" 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 # 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" serde = "1.0"
[dev-dependencies] [dev-dependencies]
# nu-path = { path="../nu-path", version = "0.87.1" } # nu-path = { path="../nu-path", version = "0.87.2" }
# serde_json = "1.0" # serde_json = "1.0"

View File

@ -63,7 +63,7 @@ fn main() {
// Extract the array // Extract the array
let array : &mut Vec<Value> = sample.get_mut("array").unwrap().as_array_mut().unwrap(); 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 // Add a value
array.push(Value::String("tak".to_string())); array.push(Value::String("tak".to_string()));

View File

@ -3,28 +3,29 @@ authors = ["The Nushell Project Developers"]
description = "Nushell's integrated LSP server" description = "Nushell's integrated LSP server"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
name = "nu-lsp" name = "nu-lsp"
version = "0.87.1" version = "0.87.2"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
nu-cli = { path = "../nu-cli", version = "0.87.1" } nu-cli = { path = "../nu-cli", version = "0.87.2" }
nu-parser = { path = "../nu-parser", version = "0.87.1" } nu-parser = { path = "../nu-parser", version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
reedline = { version = "0.26" } reedline = { version = "0.26" }
crossbeam-channel = "0.5.8"
lsp-types = "0.94.1" 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" miette = "5.10"
ropey = "1.6.1" ropey = "1.6.1"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.87.2" }
nu-command = { path = "../nu-command", version = "0.87.1" } nu-command = { path = "../nu-command", version = "0.87.2" }
nu-test-support = { path = "../nu-test-support", version = "0.87.1" } nu-test-support = { path = "../nu-test-support", version = "0.87.2" }
assert-json-diff = "2.0" assert-json-diff = "2.0"
tempfile = "3.2" 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_server::{Connection, IoThreads, Message, Response, ResponseError};
use lsp_types::{ use lsp_types::{
request::{Completion, GotoDefinition, HoverRequest, Request}, request::{Completion, GotoDefinition, HoverRequest, Request},
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams, CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
OneOf, Range, ServerCapabilities, TextEdit, Url, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url,
}; };
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_cli::NuCompleter; use nu_cli::NuCompleter;
@ -17,6 +25,9 @@ use nu_protocol::{
use reedline::Completer; use reedline::Completer;
use ropey::Rope; use ropey::Rope;
mod diagnostics;
mod notification;
#[derive(Debug)] #[derive(Debug)]
enum Id { enum Id {
Variable(VarId), Variable(VarId),
@ -27,6 +38,7 @@ enum Id {
pub struct LanguageServer { pub struct LanguageServer {
connection: Connection, connection: Connection,
io_threads: Option<IoThreads>, io_threads: Option<IoThreads>,
ropes: BTreeMap<PathBuf, Rope>,
} }
impl LanguageServer { impl LanguageServer {
@ -42,11 +54,19 @@ impl LanguageServer {
Ok(Self { Ok(Self {
connection, connection,
io_threads, io_threads,
ropes: BTreeMap::new(),
}) })
} }
pub fn serve_requests(self, engine_state: EngineState) -> Result<()> { pub fn serve_requests(
let server_capabilities = serde_json::to_value(&ServerCapabilities { 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)), definition_provider: Some(OneOf::Left(true)),
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)), hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
completion_provider: Some(lsp_types::CompletionOptions::default()), completion_provider: Some(lsp_types::CompletionOptions::default()),
@ -56,10 +76,22 @@ impl LanguageServer {
let _initialization_params = self let _initialization_params = self
.connection .connection
.initialize(server_capabilities) .initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst))
.into_diagnostic()?; .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 { match msg {
Message::Request(request) => { Message::Request(request) => {
if self if self
@ -71,25 +103,39 @@ impl LanguageServer {
} }
let mut engine_state = engine_state.clone(); let mut engine_state = engine_state.clone();
match request.method.as_str() { let resp = match request.method.as_str() {
GotoDefinition::METHOD => { GotoDefinition::METHOD => Self::handle_lsp_request(
self.handle_lsp_request(
&mut engine_state, &mut engine_state,
request, request,
Self::goto_definition, |engine_state, params| self.goto_definition(engine_state, params),
)?; ),
} HoverRequest::METHOD => Self::handle_lsp_request(
HoverRequest::METHOD => { &mut engine_state,
self.handle_lsp_request(&mut engine_state, request, Self::hover)?; request,
} |engine_state, params| self.hover(engine_state, params),
Completion::METHOD => { ),
self.handle_lsp_request(&mut engine_state, request, Self::complete)?; Completion::METHOD => Self::handle_lsp_request(
} &mut engine_state,
_ => {} request,
|engine_state, params| self.complete(engine_state, params),
),
_ => {
continue;
} }
};
self.connection
.sender
.send(Message::Response(resp))
.into_diagnostic()?;
} }
Message::Response(_) => {} 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,22 +147,23 @@ impl LanguageServer {
} }
fn handle_lsp_request<P, H, R>( fn handle_lsp_request<P, H, R>(
&self,
engine_state: &mut EngineState, engine_state: &mut EngineState,
req: lsp_server::Request, req: lsp_server::Request,
param_handler: H, mut param_handler: H,
) -> Result<()> ) -> Response
where where
P: serde::de::DeserializeOwned, P: serde::de::DeserializeOwned,
H: Fn(&mut EngineState, &P) -> Option<R>, H: FnMut(&mut EngineState, &P) -> Option<R>,
R: serde::ser::Serialize, R: serde::ser::Serialize,
{ {
let resp = {
match serde_json::from_value::<P>(req.params) { match serde_json::from_value::<P>(req.params) {
Ok(params) => Response { Ok(params) => Response {
id: req.id, id: req.id,
result: param_handler(engine_state, &params) result: Some(
.and_then(|response| serde_json::to_value(response).ok()), param_handler(engine_state, &params)
.and_then(|response| serde_json::to_value(response).ok())
.unwrap_or(serde_json::Value::Null),
),
error: None, error: None,
}, },
@ -130,12 +177,6 @@ impl LanguageServer {
}), }),
}, },
} }
};
self.connection
.sender
.send(Message::Response(resp))
.into_diagnostic()
} }
fn span_to_range(span: &Span, rope_of_file: &Rope, offset: usize) -> lsp_types::Range { 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 } 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); let line_idx = rope_of_file.line_to_char(position.line as usize);
line_idx + position.character as usize line_idx + position.character as usize
} }
fn find_id( fn find_id(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
file_path: &str, path: &Path,
file: &[u8], file: &Rope,
location: usize, location: usize,
) -> Option<(Id, usize, Span)> { ) -> Option<(Id, usize, Span)> {
let file_id = working_set.add_file(file_path.to_string(), file); let file_path = path.to_string_lossy();
let offset = working_set.get_span_for_file(file_id).start;
let block = parse(working_set, Some(file_path), file, false); // 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 flattened = flatten_block(working_set, &block);
let offset = working_set.get_span_for_filename(&file_path)?.start;
let location = location + offset; let location = location + offset;
for item in flattened {
if location >= item.0.start && location < item.0.end { for (span, shape) in flattened {
match &item.1 { if location >= span.start && location < span.end {
match &shape {
FlatShape::Variable(var_id) | FlatShape::VarDecl(var_id) => { 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) => { 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 None
} }
fn read_in_file<'a>( fn rope<'a, 'b: 'a>(&'b self, file_url: &Url) -> Option<(&'a Rope, &'a PathBuf)> {
engine_state: &'a mut EngineState, let file_path = file_url.to_file_path().ok()?;
file_path: &str,
) -> Result<(Vec<u8>, StateWorkingSet<'a>)> {
let file = std::fs::read(file_path).into_diagnostic()?;
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); let working_set = StateWorkingSet::new(engine_state);
Ok((file, working_set)) Some((file, path, working_set))
} }
fn goto_definition( fn goto_definition(
&mut self,
engine_state: &mut EngineState, engine_state: &mut EngineState,
params: &GotoDefinitionParams, params: &GotoDefinitionParams,
) -> Option<GotoDefinitionResponse> { ) -> Option<GotoDefinitionResponse> {
let cwd = std::env::current_dir().expect("Could not get current working directory."); 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())); engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params let (file, path, mut working_set) = self.read_in_file(
.text_document_position_params engine_state,
.text_document &params.text_document_position_params.text_document.uri,
.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 (id, _, _) = Self::find_id( let (id, _, _) = Self::find_id(
&mut working_set, &mut working_set,
&file_path, path,
&file, file,
Self::lsp_position_to_location( Self::lsp_position_to_location(&params.text_document_position_params.position, file),
&params.text_document_position_params.position,
&rope_of_file,
),
)?; )?;
match id { match id {
@ -242,7 +288,7 @@ impl LanguageServer {
if span.start >= *file_start && span.start < *file_end { if span.start >= *file_start && span.start < *file_end {
return Some(GotoDefinitionResponse::Scalar(Location { return Some(GotoDefinitionResponse::Scalar(Location {
uri: Url::from_file_path(file_path).ok()?, 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 .text_document
.uri .uri
.clone(), .clone(),
range: Self::span_to_range( range: Self::span_to_range(&var.declaration_span, file, *file_start),
&var.declaration_span,
&rope_of_file,
*file_start,
),
})); }));
} }
} }
@ -275,30 +317,20 @@ impl LanguageServer {
None 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."); 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())); engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params let (file, path, mut working_set) = self.read_in_file(
.text_document_position_params engine_state,
.text_document &params.text_document_position_params.text_document.uri,
.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 (id, _, _) = Self::find_id( let (id, _, _) = Self::find_id(
&mut working_set, &mut working_set,
&file_path, path,
&file, file,
Self::lsp_position_to_location( Self::lsp_position_to_location(&params.text_document_position_params.position, file),
&params.text_document_position_params.position,
&rope_of_file,
),
)?; )?;
match id { match id {
@ -489,27 +521,23 @@ impl LanguageServer {
} }
fn complete( fn complete(
&mut self,
engine_state: &mut EngineState, engine_state: &mut EngineState,
params: &CompletionParams, params: &CompletionParams,
) -> Option<CompletionResponse> { ) -> Option<CompletionResponse> {
let cwd = std::env::current_dir().expect("Could not get current working directory."); 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())); engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
let file_path = params let (rope_of_file, _, _) = self.read_in_file(
.text_document_position engine_state,
.text_document &params.text_document_position.text_document.uri,
.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 stack = Stack::new(); let stack = Stack::new();
let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack); let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack);
let location = 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); let results = completer.complete(&rope_of_file.to_string(), location);
if results.is_empty() { if results.is_empty() {
None None
@ -545,15 +573,18 @@ mod tests {
use super::*; use super::*;
use assert_json_diff::assert_json_eq; use assert_json_diff::assert_json_eq;
use lsp_types::{ use lsp_types::{
notification::{Exit, Initialized, Notification}, notification::{
DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
},
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown}, request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
CompletionParams, GotoDefinitionParams, InitializeParams, InitializedParams, CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
TextDocumentIdentifier, TextDocumentPositionParams, Url, GotoDefinitionParams, InitializeParams, InitializedParams, TextDocumentContentChangeEvent,
TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
}; };
use nu_test_support::fs::{fixtures, root}; use nu_test_support::fs::{fixtures, root};
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
fn initialize_language_server() -> (Connection, Receiver<Result<()>>) { pub fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
use std::sync::mpsc; use std::sync::mpsc;
let (client_connection, server_connection) = Connection::memory(); let (client_connection, server_connection) = Connection::memory();
let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap(); let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap();
@ -562,7 +593,7 @@ mod tests {
std::thread::spawn(move || { std::thread::spawn(move || {
let engine_state = nu_cmd_lang::create_default_context(); let engine_state = nu_cmd_lang::create_default_context();
let engine_state = nu_command::add_shell_command_context(engine_state); 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 client_connection
@ -651,16 +682,91 @@ mod tests {
.receiver .receiver
.recv_timeout(std::time::Duration::from_secs(2)) .recv_timeout(std::time::Duration::from_secs(2))
.unwrap(); .unwrap();
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert!(matches!( assert_json_eq!(result, serde_json::json!(null));
resp,
Message::Response(response) if response.result.is_none()
));
} }
fn goto_definition(uri: Url, line: u32, character: u32) -> Message { pub fn open(client_connection: &Connection, uri: Url) -> lsp_server::Notification {
let (client_connection, _recv) = initialize_language_server(); 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 client_connection
.sender .sender
.send(Message::Request(lsp_server::Request { .send(Message::Request(lsp_server::Request {
@ -686,13 +792,17 @@ mod tests {
#[test] #[test]
fn goto_definition_of_variable() { fn goto_definition_of_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("goto"); script.push("goto");
script.push("var.nu"); script.push("var.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -713,13 +823,17 @@ mod tests {
#[test] #[test]
fn goto_definition_of_command() { fn goto_definition_of_command() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("goto"); script.push("goto");
script.push("command.nu"); script.push("command.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -740,13 +854,17 @@ mod tests {
#[test] #[test]
fn goto_definition_of_command_parameter() { fn goto_definition_of_command_parameter() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("goto"); script.push("goto");
script.push("command.nu"); script.push("command.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -765,9 +883,7 @@ mod tests {
); );
} }
fn hover(uri: Url, line: u32, character: u32) -> Message { pub fn hover(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
let (client_connection, _recv) = initialize_language_server();
client_connection client_connection
.sender .sender
.send(Message::Request(lsp_server::Request { .send(Message::Request(lsp_server::Request {
@ -792,13 +908,17 @@ mod tests {
#[test] #[test]
fn hover_on_variable() { fn hover_on_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("hover"); script.push("hover");
script.push("var.nu"); script.push("var.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -815,13 +935,17 @@ mod tests {
#[test] #[test]
fn hover_on_command() { fn hover_on_command() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("hover"); script.push("hover");
script.push("command.nu"); script.push("command.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -839,9 +963,7 @@ mod tests {
); );
} }
fn complete(uri: Url, line: u32, character: u32) -> Message { fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
let (client_connection, _recv) = initialize_language_server();
client_connection client_connection
.sender .sender
.send(Message::Request(lsp_server::Request { .send(Message::Request(lsp_server::Request {
@ -868,13 +990,17 @@ mod tests {
#[test] #[test]
fn complete_on_variable() { fn complete_on_variable() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("completion"); script.push("completion");
script.push("var.nu"); script.push("var.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } else {
@ -900,13 +1026,17 @@ mod tests {
#[test] #[test]
fn complete_command_with_space() { fn complete_command_with_space() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures(); let mut script = fixtures();
script.push("lsp"); script.push("lsp");
script.push("completion"); script.push("completion");
script.push("command.nu"); script.push("command.nu");
let script = Url::from_file_path(script).unwrap(); 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 { let result = if let Message::Response(response) = resp {
response.result response.result
} else { } 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" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-parser" name = "nu-parser"
version = "0.87.1" version = "0.87.2"
exclude = ["/fuzz"] exclude = ["/fuzz"]
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.1" } nu-path = { path = "../nu-path", version = "0.87.2" }
nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.1" } nu-plugin = { path = "../nu-plugin", optional = true, version = "0.87.2" }
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
bytesize = "1.3" bytesize = "1.3"
chrono = { default-features = false, features = ['std'], version = "0.4" } 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"export def",
b"for", b"for",
b"extern", b"extern",
b"extern-wrapped",
b"export extern", b"export extern",
b"export extern-wrapped",
b"alias", b"alias",
b"export alias", b"export alias",
b"export-env", 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 /// Check whether spans start with a parser keyword that can be aliased
pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool { pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
// try two words // 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])); let cmd_name = working_set.get_span_contents(span(&[*span1, *span2]));
return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name); return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
} }
// try one word // 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); let cmd_name = working_set.get_span_contents(*span1);
UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name) UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
} else { } else {
@ -151,11 +149,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
return; return;
}; };
if def_type_name != b"def" if def_type_name != b"def" && def_type_name != b"extern" {
&& def_type_name != b"def-env"
&& def_type_name != b"extern"
&& def_type_name != b"extern-wrapped"
{
return; return;
} }
@ -378,7 +372,7 @@ pub fn parse_def(
}; };
let def_call = working_set.get_span_contents(name_span).to_vec(); 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( working_set.error(ParseError::UnknownState(
"internal error: Wrong call name for def function".into(), "internal error: Wrong call name for def function".into(),
span(spans), span(spans),
@ -575,7 +569,7 @@ pub fn parse_def(
let calls_itself = block_calls_itself(block, decl_id); let calls_itself = block_calls_itself(block, decl_id);
block.recursive = Some(calls_itself); block.recursive = Some(calls_itself);
block.signature = signature; 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() { if block.signature.input_output_types.is_empty() {
block block
@ -635,9 +629,9 @@ pub fn parse_extern(
}; };
let extern_call = working_set.get_span_contents(name_span).to_vec(); 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( 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), span(spans),
)); ));
return garbage_pipeline(spans); return garbage_pipeline(spans);
@ -659,7 +653,7 @@ pub fn parse_extern(
let (command_spans, rest_spans) = spans.split_at(split_id); 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( if let Some(err) = detect_params_in_name(
working_set, working_set,
*name_span, *name_span,
@ -1055,8 +1049,9 @@ pub fn parse_export_in_block(
let full_name = if lite_command.parts.len() > 1 { let full_name = if lite_command.parts.len() > 1 {
let sub = working_set.get_span_contents(lite_command.parts[1]); let sub = working_set.get_span_contents(lite_command.parts[1]);
match sub { match sub {
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module" b"alias" | b"def" | b"extern" | b"use" | b"module" | b"const" => {
| b"const" => [b"export ", sub].concat(), [b"export ", sub].concat()
}
_ => b"export".to_vec(), _ => b"export".to_vec(),
} }
} else { } else {
@ -1113,7 +1108,7 @@ pub fn parse_export_in_block(
match full_name.as_slice() { match full_name.as_slice() {
b"export alias" => parse_alias(working_set, lite_command, None), 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 const" => parse_const(working_set, &lite_command.parts[1..]),
b"export use" => { b"export use" => {
let (pipeline, _) = parse_use(working_set, &lite_command.parts); 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 module" => parse_module(working_set, lite_command, None).0,
b"export extern" => parse_extern(working_set, lite_command, None), 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( working_set.error(ParseError::UnexpectedKeyword(
String::from_utf8_lossy(&full_name).to_string(), String::from_utf8_lossy(&full_name).to_string(),
@ -1141,7 +1135,7 @@ pub fn parse_export_in_module(
) -> (Pipeline, Vec<Exportable>) { ) -> (Pipeline, Vec<Exportable>) {
let spans = &lite_command.parts[..]; 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" { if working_set.get_span_contents(*sp) != b"export" {
working_set.error(ParseError::UnknownState( working_set.error(ParseError::UnknownState(
"expected export statement".into(), "expected export statement".into(),
@ -1215,7 +1209,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call), expr: Expr::Call(ref def_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = def_call.clone(); call = def_call.clone();
@ -1230,67 +1224,7 @@ pub fn parse_export_in_module(
result result
} }
b"def-env" => { b"extern" => {
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" => {
let lite_command = LiteCommand { let lite_command = LiteCommand {
comments: lite_command.comments.clone(), comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(), parts: spans[1..].to_vec(),
@ -1303,7 +1237,7 @@ pub fn parse_export_in_module(
id id
} else { } else {
working_set.error(ParseError::InternalError( working_set.error(ParseError::InternalError(
"missing 'export extern' or 'export extern-wrapped' command".into(), "missing 'export extern' command".into(),
export_span, export_span,
)); ));
return (garbage_pipeline(spans), vec![]); return (garbage_pipeline(spans), vec![]);
@ -1316,7 +1250,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call), expr: Expr::Call(ref def_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = def_call.clone(); call = def_call.clone();
@ -1376,7 +1310,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref alias_call), expr: Expr::Call(ref alias_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = alias_call.clone(); call = alias_call.clone();
@ -1435,7 +1369,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref use_call), expr: Expr::Call(ref use_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = use_call.clone(); call = use_call.clone();
@ -1472,7 +1406,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref module_call), expr: Expr::Call(ref module_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = module_call.clone(); call = module_call.clone();
@ -1529,7 +1463,7 @@ pub fn parse_export_in_module(
expr: Expr::Call(ref def_call), expr: Expr::Call(ref def_call),
.. ..
}, },
)) = pipeline.elements.get(0) )) = pipeline.elements.first()
{ {
call = def_call.clone(); call = def_call.clone();
@ -1569,7 +1503,7 @@ pub fn parse_export_in_module(
} }
_ => { _ => {
working_set.error(ParseError::Expected( 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], spans[1],
)); ));
@ -1578,9 +1512,9 @@ pub fn parse_export_in_module(
} }
} else { } else {
working_set.error(ParseError::MissingPositional( 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), 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![] vec![]
@ -1749,7 +1683,7 @@ pub fn parse_module_block(
let name = working_set.get_span_contents(command.parts[0]); let name = working_set.get_span_contents(command.parts[0]);
match name { match name {
b"def" | b"def-env" => { b"def" => {
block.pipelines.push( block.pipelines.push(
parse_def( parse_def(
working_set, working_set,
@ -1762,11 +1696,9 @@ pub fn parse_module_block(
b"const" => block b"const" => block
.pipelines .pipelines
.push(parse_const(working_set, &command.parts)), .push(parse_const(working_set, &command.parts)),
b"extern" | b"extern-wrapped" => { b"extern" => block
block
.pipelines .pipelines
.push(parse_extern(working_set, command, None)) .push(parse_extern(working_set, command, None)),
}
b"alias" => { b"alias" => {
block.pipelines.push(parse_alias( block.pipelines.push(parse_alias(
working_set, working_set,
@ -1906,7 +1838,7 @@ pub fn parse_module_block(
} }
_ => { _ => {
working_set.error(ParseError::ExpectedKeyword( 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], 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 (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
let second_token = tokens let second_token = tokens
.get(0) .first()
.map(|token| working_set.get_span_contents(token.span)); .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 let third_token = tokens
.get(1) .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 { 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( working_set.error(ParseError::WrongImportPattern(
"needs at least one component of import pattern".to_string(), "needs at least one component of import pattern".to_string(),
span(spans), span(spans),
@ -4918,8 +4918,8 @@ pub fn parse_expression(
// For now, check for special parses of certain keywords // For now, check for special parses of certain keywords
match bytes.as_slice() { match bytes.as_slice() {
b"def" | b"extern" | b"extern-wrapped" | b"for" | b"module" | b"use" | b"source" b"def" | b"extern" | b"for" | b"module" | b"use" | b"source" | b"alias" | b"export"
| b"alias" | b"export" | b"hide" => { | b"hide" => {
working_set.error(ParseError::BuiltinCommandInPipeline( working_set.error(ParseError::BuiltinCommandInPipeline(
String::from_utf8(bytes) String::from_utf8(bytes)
.expect("builtin commands bytes should be able to convert to string"), .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]); let name = working_set.get_span_contents(lite_command.parts[0]);
match name { match name {
b"def" | b"def-env" => parse_def(working_set, lite_command, None).0, b"def" => parse_def(working_set, lite_command, None).0,
b"extern" | b"extern-wrapped" => parse_extern(working_set, lite_command, None), b"extern" => parse_extern(working_set, lite_command, None),
b"let" => parse_let(working_set, &lite_command.parts), b"let" => parse_let(working_set, &lite_command.parts),
b"const" => parse_const(working_set, &lite_command.parts), b"const" => parse_const(working_set, &lite_command.parts),
b"mut" => parse_mut(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" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-path" name = "nu-path"
version = "0.87.1" version = "0.87.2"
exclude = ["/fuzz"] exclude = ["/fuzz"]
[lib] [lib]

View File

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

View File

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

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-protocol" 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 # 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 bench = false
[dependencies] [dependencies]
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-path = { path = "../nu-path", version = "0.87.1" } nu-path = { path = "../nu-path", version = "0.87.2" }
nu-system = { path = "../nu-system", version = "0.87.1" } nu-system = { path = "../nu-system", version = "0.87.2" }
byte-unit = "4.0" byte-unit = "4.0"
chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false } chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false }
@ -37,5 +37,5 @@ plugin = ["serde_json"]
serde_json = "1.0" serde_json = "1.0"
strum = "0.25" strum = "0.25"
strum_macros = "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" rstest = "0.18"

View File

@ -12,6 +12,28 @@ pub enum Argument {
Unknown(Expression), // unknown argument used in "fall-through" signatures 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)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Call { pub struct Call {
/// identifier of the declaration to 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( pub fn named_iter(
&self, &self,
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> { ) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> {
@ -166,3 +208,64 @@ impl Call {
span 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; return true;
} }
if let Some(pipeline) = block.pipelines.get(0) { if let Some(pipeline) = block.pipelines.first() {
match pipeline.elements.get(0) { match pipeline.elements.first() {
Some(element) => element.has_in_variable(working_set), Some(element) => element.has_in_variable(working_set),
None => false, None => false,
} }
@ -150,8 +150,8 @@ impl Expression {
return true; return true;
} }
if let Some(pipeline) = block.pipelines.get(0) { if let Some(pipeline) = block.pipelines.first() {
match pipeline.elements.get(0) { match pipeline.elements.first() {
Some(element) => element.has_in_variable(working_set), Some(element) => element.has_in_variable(working_set),
None => false, None => false,
} }
@ -258,8 +258,8 @@ impl Expression {
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
if let Some(pipeline) = block.pipelines.get(0) { if let Some(pipeline) = block.pipelines.first() {
if let Some(expr) = pipeline.elements.get(0) { if let Some(expr) = pipeline.elements.first() {
expr.has_in_variable(working_set) expr.has_in_variable(working_set)
} else { } else {
false false
@ -304,8 +304,8 @@ impl Expression {
Expr::Block(block_id) => { Expr::Block(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let new_expr = if let Some(pipeline) = block.pipelines.get(0) { let new_expr = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.get(0) { if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone(); let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id); new_element.replace_in_variable(working_set, new_var_id);
Some(new_element) Some(new_element)
@ -335,8 +335,8 @@ impl Expression {
Expr::Closure(block_id) => { Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.get(0) { let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.get(0) { if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone(); let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id); new_element.replace_in_variable(working_set, new_var_id);
Some(new_element) Some(new_element)
@ -433,8 +433,8 @@ impl Expression {
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.get(0) { let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.get(0) { if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone(); let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id); new_element.replace_in_variable(working_set, new_var_id);
Some(new_element) Some(new_element)

View File

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

View File

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

View File

@ -317,6 +317,15 @@ impl<'a> StateWorkingSet<'a> {
self.num_virtual_paths() - 1 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 { pub fn get_span_for_file(&self, file_id: usize) -> Span {
let result = self let result = self
.files() .files()

View File

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

View File

@ -335,3 +335,16 @@ export def repeat [
1..$n | each { $item } 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" description = "Nushell system querying"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
name = "nu-system" name = "nu-system"
version = "0.87.1" version = "0.87.2"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -21,7 +21,7 @@ sysinfo = "0.29"
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] } nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
procfs = "0.15" procfs = "0.16"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
libproc = "0.14" libproc = "0.14"

View File

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

View File

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

View File

@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-table" name = "nu-table"
version = "0.87.1" version = "0.87.2"
[lib] [lib]
bench = false bench = false
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.87.1" } nu-protocol = { path = "../nu-protocol", version = "0.87.2" }
nu-utils = { path = "../nu-utils", version = "0.87.1" } nu-utils = { path = "../nu-utils", version = "0.87.2" }
nu-engine = { path = "../nu-engine", version = "0.87.1" } nu-engine = { path = "../nu-engine", version = "0.87.2" }
nu-color-config = { path = "../nu-color-config", version = "0.87.1" } nu-color-config = { path = "../nu-color-config", version = "0.87.2" }
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
once_cell = "1.18" once_cell = "1.18"
fancy-regex = "0.11" fancy-regex = "0.11"
tabled = { version = "0.14.0", features = ["color"], default-features = false } tabled = { version = "0.14.0", features = ["color"], default-features = false }
[dev-dependencies] [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, comp: &StyleComputer,
out: &TableOutput, out: &TableOutput,
expand: bool, expand: bool,
mode: TableMode,
) -> NuTableConfig { ) -> NuTableConfig {
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_footer: with_footer(config, out.with_header, out.table.count_rows()),
with_index: out.with_index, with_index: out.with_index,
with_header: out.with_header, 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 { pub fn load_theme(mode: TableMode) -> TableTheme {
match config.table_mode { match mode {
TableMode::Basic => TableTheme::basic(), TableMode::Basic => TableTheme::basic(),
TableMode::Thin => TableTheme::thin(), TableMode::Thin => TableTheme::thin(),
TableMode::Light => TableTheme::light(), TableMode::Light => TableTheme::light(),

View File

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

View File

@ -3,17 +3,18 @@ use std::collections::HashMap;
use nu_color_config::{Alignment, StyleComputer, TextStyle}; use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns; 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 tabled::grid::config::Position;
use crate::{ use crate::{
common::{ common::{
create_nu_table_config, error_sign, get_header_style, get_index_style, create_nu_table_config, error_sign, get_header_style, get_index_style, load_theme,
load_theme_from_config, nu_value_to_string, nu_value_to_string_clean, nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text,
nu_value_to_string_colored, wrap_text, NuText, StringResult, TableResult, NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
INDEX_COLUMN_NAME,
}, },
string_width, NuTable, NuTableCell, TableOpts, TableOutput, string_width,
types::has_index,
NuTable, NuTableCell, TableOpts, TableOutput,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -83,11 +84,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let headers = get_columns(input); let headers = get_columns(input);
let with_index = match cfg.opts.config.table_index_mode { let with_index = has_index(&cfg.opts, &headers);
TableIndexMode::Always => true, let row_offset = cfg.opts.index_offset;
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
};
// The header with the INDEX is removed from the table headers since // The header with the INDEX is removed from the table headers since
// it is added to the natural table index // 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()); return Err(*error.clone());
} }
let index = row + cfg.opts.row_offset; let index = row + row_offset;
let text = item let text = item
.as_record() .as_record()
.ok() .ok()
@ -156,8 +154,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone()); return Err(*error.clone());
} }
let mut inner_cfg = cfg.clone(); let inner_cfg = update_config(cfg.clone(), available_width);
inner_cfg.opts.width = available_width;
let (mut text, style) = expanded_table_entry2(item, inner_cfg); let (mut text, style) = expanded_table_entry2(item, inner_cfg);
let value_width = string_width(&text); let value_width = string_width(&text);
@ -244,8 +241,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
return Err(*error.clone()); return Err(*error.clone());
} }
let mut inner_cfg = cfg.clone(); let inner_cfg = update_config(cfg.clone(), available);
inner_cfg.opts.width = available;
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg); let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
let mut value_width = string_width(&text); 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 { 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 let key_width = record
.columns() .columns()
.map(|col| string_width(col)) .map(|col| string_width(col))
@ -407,8 +403,7 @@ fn expand_table_value(
let span = value.span(); let span = value.span();
match value { match value {
Value::List { vals, .. } => { Value::List { vals, .. } => {
let mut inner_cfg = dive_options(cfg, span); let inner_cfg = update_config(dive_options(cfg, span), value_width);
inner_cfg.opts.width = value_width;
let table = expanded_table_list(vals, inner_cfg)?; let table = expanded_table_list(vals, inner_cfg)?;
match table { match table {
@ -438,8 +433,7 @@ fn expand_table_value(
))); )));
} }
let mut inner_cfg = dive_options(cfg, span); let inner_cfg = update_config(dive_options(cfg, span), value_width);
inner_cfg.opts.width = value_width;
let result = expanded_table_kv(record, inner_cfg)?; let result = expanded_table_kv(record, inner_cfg)?;
match result { match result {
Some(result) => Ok(Some((result, true))), 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 { 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); let total_width = out.table.total_width(&table_config);
if total_width < term_width { if total_width < term_width {
const EXPAND_THRESHOLD: f32 = 0.80; 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 { 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 { 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); let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer);
wrap_text(&text, value_width, cfg.opts.config) 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_color_config::TextStyle;
use nu_engine::column::get_columns; use nu_engine::column::get_columns;
use nu_protocol::{Config, Record, ShellError, TableIndexMode, Value}; use nu_protocol::{Config, Record, ShellError, Value};
use crate::{ use crate::{
clean_charset, colorize_space, clean_charset, colorize_space,
@ -11,6 +11,8 @@ use crate::{
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult, NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
}; };
use super::has_index;
pub struct JustTable; pub struct JustTable;
impl JustTable { impl JustTable {
@ -24,7 +26,7 @@ impl JustTable {
} }
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> { 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) => { Some(mut out) => {
let left = opts.config.table_indent.left; let left = opts.config.table_indent.left;
let right = opts.config.table_indent.right; 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); colorize_space(out.table.get_records_mut(), opts.style_computer);
let table_config = 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)) Ok(out.table.draw(table_config, opts.width))
} }
None => Ok(None), None => Ok(None),
@ -65,23 +67,21 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
let right = opts.config.table_indent.right; let right = opts.config.table_indent.right;
out.table.set_indent(left, 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); let table = out.table.draw(table_config, opts.width);
Ok(table) Ok(table)
} }
fn table(input: &[Value], row_offset: usize, opts: TableOpts<'_>) -> TableResult { fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
if input.is_empty() { if input.is_empty() {
return Ok(None); return Ok(None);
} }
let mut headers = get_columns(input); let mut headers = get_columns(input);
let with_index = match opts.config.table_index_mode { let with_index = has_index(opts, &headers);
TableIndexMode::Always => true, let row_offset = opts.index_offset;
TableIndexMode::Never => false,
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
};
let with_header = !headers.is_empty(); let with_header = !headers.is_empty();
if !with_header { if !with_header {
@ -112,7 +112,7 @@ fn to_table_with_header(
headers: Vec<String>, headers: Vec<String>,
with_index: bool, with_index: bool,
row_offset: usize, row_offset: usize,
opts: TableOpts<'_>, opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> { ) -> Result<Option<NuTable>, ShellError> {
let count_rows = input.len() + 1; let count_rows = input.len() + 1;
let count_columns = headers.len(); let count_columns = headers.len();
@ -140,7 +140,7 @@ fn to_table_with_header(
let skip_index = usize::from(with_index); let skip_index = usize::from(with_index);
for (col, header) in headers.iter().enumerate().skip(skip_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((row + 1, col), text);
table.insert_style((row + 1, col), style); table.insert_style((row + 1, col), style);
@ -154,7 +154,7 @@ fn to_table_with_no_header(
input: &[Value], input: &[Value],
with_index: bool, with_index: bool,
row_offset: usize, row_offset: usize,
opts: TableOpts<'_>, opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> { ) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), with_index as usize + 1); let mut table = NuTable::new(input.len(), with_index as usize + 1);
table.set_index_style(get_index_style(opts.style_computer)); 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); 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); let pos = (row, with_index as usize);
table.insert(pos, text); table.insert(pos, text);

View File

@ -8,9 +8,9 @@ pub use collapse::CollapsedTable;
pub use expanded::ExpandedTable; pub use expanded::ExpandedTable;
pub use general::JustTable; pub use general::JustTable;
use nu_color_config::StyleComputer; 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 struct TableOutput {
pub table: NuTable, pub table: NuTable,
@ -34,29 +34,46 @@ pub struct TableOpts<'a> {
config: &'a Config, config: &'a Config,
style_computer: &'a StyleComputer<'a>, style_computer: &'a StyleComputer<'a>,
span: Span, span: Span,
row_offset: usize,
width: usize, width: usize,
indent: (usize, usize), indent: (usize, usize),
mode: TableMode,
index_offset: usize,
index_remove: bool,
} }
impl<'a> TableOpts<'a> { impl<'a> TableOpts<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
config: &'a Config, config: &'a Config,
style_computer: &'a StyleComputer<'a>, style_computer: &'a StyleComputer<'a>,
ctrlc: Option<Arc<AtomicBool>>, ctrlc: Option<Arc<AtomicBool>>,
span: Span, span: Span,
row_offset: usize,
width: usize, width: usize,
indent: (usize, usize), indent: (usize, usize),
mode: TableMode,
index_offset: usize,
index_remove: bool,
) -> Self { ) -> Self {
Self { Self {
ctrlc, ctrlc,
config, config,
style_computer, style_computer,
span, span,
row_offset,
indent, indent,
width, 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