Compare commits

...

28 Commits

Author SHA1 Message Date
Antoine Stevan
c74cff213e
remove std clip (#11131)
follow up to
- https://github.com/nushell/nushell/pull/11097

related to
- https://github.com/nushell/nu_scripts/pull/674

> **Important**
> wait for in between Nushell 0.88.0 and 0.89.0

# Description
this PR removes the `std clip` command after it's been deprecated in
https://github.com/nushell/nushell/pull/11097 😋

# User-Facing Changes
`std clip` will no longer be available.

# Tests + Formatting

# After Submitting
2024-01-08 21:46:10 +02:00
dependabot[bot]
2ea5819b02 Bump crate-ci/typos from 1.16.26 to 1.17.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.26 to 1.17.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.16.26...v1.17.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 01:17:04 +00:00
dependabot[bot]
1115190a49 Bump shadow-rs from 0.25.0 to 0.26.0
Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/baoyachi/shadow-rs/releases)
- [Changelog](https://github.com/baoyachi/shadow-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/baoyachi/shadow-rs/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: shadow-rs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 01:06:59 +00:00
Darren Schroeder
7132c5ad02
add wheres 2024-01-07 19:04:04 -06:00
Antoine Büsch
1920ece759
fix: closure captures can also be constants (#11493)
<!--
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.
-->
When evaluating a closure (in
`EvalRuntime::eval_row_condition_or_closure()`), we try to resolve the
closure's block's captures, but we only check if they're variables on
the stack. We need to also check if they are constants (see the logic in
`Stack::gather_captures()`).

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

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

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

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

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

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-01-07 12:51:39 +02:00
WindSoilder
6f59abaf43
update reedline version to latest main (#11490)
# Description
Update reedline version to fix
https://github.com/nushell/nushell/issues/11399
2024-01-05 17:19:43 -06:00
Artemiy
5f7425a7b4
Xml errors fix (#11487)
# Description
Fixes #11264
This PR adds checks in `to xml` to output error for malformed xml
entries:
* With columns that are not one of `tag`, `attributes` or `content`
* With no `tag` when entry is not a string
* With `tag` that is not a string
This PR also replaces `attrs` with `attributes` in example and
extra_usage of `to xml` (column was originally named attrs and renamed
to attributes, but this was missed in docs)

# User-Facing Changes
`to xml` will produce error for conditions described above instead of
silently returning nothing

# Tests + Formatting
Added tests for `to xml` to check handling of malformed xml entries
2024-01-05 15:56:13 -06:00
nibon7
1ab9ec3ebc
Bump terminal_size to 0.3 (#11486)
# Description
Simplify the dependencies. There are two different versions of
`terminal_size` that nushell directly depends on.

Related: #8060
2024-01-05 10:19:46 -06:00
nibon7
f2095ed0cc
Fix cross building for target x86_64-pc-windows-gnu on linux (#11485)
# Description

Cross build for target `x86_64-pc-windows-gnu` fails on linux.

```console
nushell on  main [?] is 📦 v0.88.2 via 🦀 v1.77.0-nightly
❯ cargo build --target x86_64-pc-windows-gnu -p nu-system
   Compiling nu-system v0.88.2 (/data/source/nushell/crates/nu-system)
error[E0432]: unresolved import `chrono::Local`
   --> crates/nu-system/src/windows.rs:5:14
    |
5   | use chrono::{Local, NaiveDate};
    |              ^^^^^ no `Local` in the root
    |
note: found an item that was configured out
   --> /path/to/home/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/chrono-0.4.31/src/lib.rs:537:17
    |
537 | pub use offset::Local;
    |                 ^^^^^
    = note: the item is gated behind the `clock` feature

error[E0412]: cannot find type `Local` in crate `chrono`
   --> crates/nu-system/src/windows.rs:68:46
    |
68  |     pub start_time: chrono::DateTime<chrono::Local>,
    |                                              ^^^^^ not found in `chrono`
    |
note: found an item that was configured out
   --> /path/to/home/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/chrono-0.4.31/src/lib.rs:537:17
    |
537 | pub use offset::Local;
    |                 ^^^^^
    = note: the item is gated behind the `clock` feature

Some errors have detailed explanations: E0412, E0432.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `nu-system` (lib) due to 2 previous errors
```
2024-01-05 07:31:38 -06:00
nibon7
7e26b4fcc2
Bump sysinfo from 0.29 to 0.30 (#11484)
# Description
Bumps `sysinfo` to 0.30.

* Changelog
 https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md#0304

# User-Facing Changes
N/A
2024-01-05 05:31:29 -06:00
tomoda
ad95e4cc27
Refactor tests (using cococo instead of ^echo) (#11479)
- related PR: #11478 

# Description

Now we can use `nu --testbin cococo` instead of `^echo` to echo messages
to stdout in tests.

But `nu` treats parameters as its own flags when parameter starts with
`-`. So `^echo --foo='bar'` still use `^echo`.

# User-Facing Changes

(none)

# Tests + Formatting

- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used`
to check that you're using the standard code style
- [x] `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))
- [x] `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library


# After Submitting

(none)
2024-01-05 11:40:56 +08:00
nibon7
ee5a18167c
Replace winapi with windows (#11481)
# Description
`winapi` is not actively maintained, use the Microsoft blessed `windows`
crate instead.

# User-Facing Changes
N/A
2024-01-04 11:17:19 -06:00
Justin Ma
75c9e3e5df
Try to fix riscv64 building by using unbuntu-latest (#11476)
Try to fix `riscv64gc-unknown-linux-gnu` building by using
`unbuntu-latest`
This PR should close https://github.com/nushell/nushell/issues/11452
The action should run without errors:
https://github.com/nushell/nightly/actions/runs/7394915358
2024-01-04 07:55:10 +08:00
tomoda
77f10eb270
Fix the test which fails on windows (#11478)
- related PR: #11463

# Description

Currently, `commands::complete::basic` fails on Windows without git
bash.
This pr fixes it.

# User-Facing Changes

(none)

# Tests + Formatting

- [x] (on Windows) `cargo fmt --all -- --check` to check standard code
formatting (`cargo fmt --all` applies these changes)
- [x] (on Windows) `cargo clippy --workspace -- -D warnings -D
clippy::unwrap_used` to check that you're using the standard code style
- [x] (on Windows without git bash, Windows with git bash and Ubuntu)
`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))
- on my Windows with Japanese lang pack: 1 test still fails. (see
#11463)
- [x] (on Windows and Ubuntu) `cargo run -- -c "use std testing; testing
run-tests --path crates/nu-std"` to run the tests for the standard
library

# After Submitting

(none)
2024-01-03 07:22:43 -06:00
tomoda
42bb42a2e1
Fix rm for symlinks pointing to directory on windows (issue #11461) (#11463)
<!--
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!
-->

- this PR closes #11461

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

Using `std::fs::remove_dir` instead of `std::fs::remove_file` when try
remove symlinks pointing to a directory on Windows.

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

none

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

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

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

- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used`
to check that you're using the standard code style
- [x] `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))
- I got 2 test fails on my Windows devenv; these fails in main branch
too
- `commands::complete::basic` : passed on Ubuntu, failed on Windows (a
bug?)
- `commands::cp::copy_file_with_read_permission`: failed on Windows with
Japanese environment (This test refers error message, so that fails on
environments using a language except for english.)
- [x] `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

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

This fix has no changes to user-facing interface.
2024-01-02 21:27:03 +08:00
dependabot[bot]
f597380112
Bump lsp-types from 0.94.1 to 0.95.0 (#11457)
Bumps [lsp-types](https://github.com/gluon-lang/lsp-types) from 0.94.1
to 0.95.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/gluon-lang/lsp-types/blob/master/CHANGELOG.md">lsp-types's
changelog</a>.</em></p>
<blockquote>
<h2>v0.95.0 (2023-12-12)</h2>
<p><!-- raw HTML omitted --><!-- raw HTML omitted --></p>
<h3>v0.94.2 (2023-12-12)</h3>
<p><!-- raw HTML omitted --><!-- raw HTML omitted --></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ccf64e2fb6"><code>ccf64e2</code></a>
chore: Release lsp-types version 0.95.0</li>
<li><a
href="bccf50c1c6"><code>bccf50c</code></a>
Update changelog</li>
<li><a
href="75cea03884"><code>75cea03</code></a>
chore: Release lsp-types version 0.94.2</li>
<li><a
href="4084a00cd1"><code>4084a00</code></a>
Update changelog</li>
<li><a
href="b588e166be"><code>b588e16</code></a>
Merge pull request <a
href="https://redirect.github.com/gluon-lang/lsp-types/issues/274">#274</a>
from lapce/inline-completion</li>
<li><a
href="3031a76c44"><code>3031a76</code></a>
Add support for textDocument/inlineCompletion</li>
<li><a
href="038577b0b5"><code>038577b</code></a>
doc: Update readme to request links to the spec for PRs</li>
<li><a
href="f106ccb584"><code>f106ccb</code></a>
Merge pull request <a
href="https://redirect.github.com/gluon-lang/lsp-types/issues/257">#257</a>
from ahlinc/init-work-done-token</li>
<li><a
href="730924021a"><code>7309240</code></a>
Merge pull request <a
href="https://redirect.github.com/gluon-lang/lsp-types/issues/259">#259</a>
from ebkalderon/fix-telemetry-event-params</li>
<li><a
href="a15daede51"><code>a15daed</code></a>
Merge pull request <a
href="https://redirect.github.com/gluon-lang/lsp-types/issues/264">#264</a>
from tage64/master</li>
<li>Additional commits viewable in <a
href="https://github.com/gluon-lang/lsp-types/compare/v0.94.1...v0.95.0">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 07:22:38 +08:00
dependabot[bot]
b92b4120dc
Bump crate-ci/typos from 1.16.25 to 1.16.26 (#11458)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.25
to 1.16.26.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.16.26</h2>
<h2>[1.16.26] - 2023-12-27</h2>
<h3>Fixes</h3>
<ul>
<li>Apply <code>extend-ignore-re</code> to file names in addition to
file content</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h2>[1.16.26] - 2023-12-27</h2>
<h3>Fixes</h3>
<ul>
<li>Apply <code>extend-ignore-re</code> to file names in addition to
file content</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="45a880d9f8"><code>45a880d</code></a>
chore: Release</li>
<li><a
href="1e55ede61c"><code>1e55ede</code></a>
docs: Update changelog</li>
<li><a
href="e32ec882ea"><code>e32ec88</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/886">#886</a>
from epage/olt</li>
<li><a
href="bf66cbd0b6"><code>bf66cbd</code></a>
fix(config): Apply extend-ignore-re to file names</li>
<li><a
href="a9afeef275"><code>a9afeef</code></a>
test(cli): Show extend-ignore on file names</li>
<li>See full diff in <a
href="https://github.com/crate-ci/typos/compare/v1.16.25...v1.16.26">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.16.25&new-version=1.16.26)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 07:22:19 +08:00
Ralf Steube
de5ad5de19
Revert "Return external file completions if not empty (#10898)" (#11446)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

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

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

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

This reverts #10898 which breaks external completion.
Not having file completion fallback on empty result is **intentional**
as this indicates that there is nothing to complete at this position.
To have nushell fallback to file completion the external completer can
simply return *nothing*.


`NO RECORDS FOUND`:
```nushell
let external_completer = {|spans|
    []
}
```

Fallback to file completion:
```nushell
let external_completer = {|spans|
}
```

# 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-12-31 08:45:05 -06:00
Darren Schroeder
64695cd67c
bump rust toolchain to 1.73.0 (#11445)
This PR bumps the nushell rust toolchain from 1.72.1 to 1.73.0 since
1.75.0 was released recently.

# 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.
-->
2023-12-30 10:41:27 -06:00
Yash Thakur
21b3eeed99
Allow spreading arguments to commands (#11289)
<!--
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!
-->

Finishes implementing https://github.com/nushell/nushell/issues/10598,
which asks for a spread operator in lists, in records, and when calling
commands.

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

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

This PR will allow spreading arguments to commands (both internal and
external). It will also deprecate spreading arguments automatically when
passing to external commands.

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

- Users will be able to use `...` to spread arguments to custom/builtin
commands that have rest parameters or allow unknown arguments, or to any
external command
- If a custom command doesn't have a rest parameter and it doesn't allow
unknown arguments either, the spread operator will not be allowed
- Passing lists to external commands without `...` will work for now but
will cause a deprecation warning saying that it'll stop working in 0.91
(is 2 versions enough time?)

Here's a function to help with demonstrating some behavior:
```nushell
> def foo [ a, b, c?, d?, ...rest ] { [$a $b $c $d $rest] | to nuon }
```

You can pass a list of arguments to fill in the `rest` parameter using
`...`:
```nushell
> foo 1 2 3 4 ...[5 6]
[1, 2, 3, 4, [5, 6]]
```

If you don't use `...`, the list `[5 6]` will be treated as a single
argument:

```nushell
> foo 1 2 3 4 [5 6] # Note the double [[]]
[1, 2, 3, 4, [[5, 6]]]
```

You can omit optional parameters before the spread arguments:
```nushell
> foo 1 2 3 ...[4 5] # d is omitted here
[1, 2, 3, null, [4, 5]]
```

If you have multiple lists, you can spread them all:
```nushell
> foo 1 2 3 ...[4 5] 6 7 ...[8] ...[]
[1, 2, 3, null, [4, 5, 6, 7, 8]]
```

Here's the kind of error you get when you try to spread arguments to a
command with no rest parameter:

![image](https://github.com/nushell/nushell/assets/45539777/93faceae-00eb-4e59-ac3f-17f98436e6e4)

And this is the warning you get when you pass a list to an external now
(without `...`):


![image](https://github.com/nushell/nushell/assets/45539777/d368f590-201e-49fb-8b20-68476ced415e)


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

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

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

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

Added tests to cover the following cases:
- Spreading arguments to a command that doesn't have a rest parameter
(unexpected spread argument error)
- Spreading arguments to a command that doesn't have a rest parameter
*but* there's also a missing positional argument (missing positional
error)
- Spreading arguments to a command that doesn't have a rest parameter
but does allow unknown arguments, such as `exec` (allowed)
- Spreading a list literal containing arguments of the wrong type (parse
error)
- Spreading a non-list value, both to internal and external commands
- Having named arguments in the middle of rest arguments
- `explain`ing a command call that spreads its arguments

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

# Examples

Suppose you have multiple tables:
```nushell
let people = [[id name age]; [0 alice 100] [1 bob 200] [2 eve 300]]
let evil_twins = [[id name age]; [0 ecila 100] [-1 bob 200] [-2 eve 300]]
```

Maybe you often find yourself needing to merge multiple tables and want
a utility to do that. You could write a function like this:
```nushell
def merge_all [ ...tables ] { $tables | reduce { |it, acc| $acc | merge $it } }
```

Then you can use it like this:
```nushell
> merge_all ...([$people $evil_twins] | each { |$it| $it | select name age })
╭───┬───────┬─────╮
│ # │ name  │ age │
├───┼───────┼─────┤
│ 0 │ ecila │ 100 │
│ 1 │ bob   │ 200 │
│ 2 │ eve   │ 300 │
╰───┴───────┴─────╯
```

Except they had duplicate columns, so now you first want to suffix every
column with a number to tell you which table the column came from. You
can make a command for that:
```nushell
def select_and_merge [ --cols: list<string>, ...tables ] {
  let renamed_tables = $tables
    | enumerate
    | each { |it|
      $it.item | select $cols | rename ...($cols | each { |col| $col + ($it.index | into string) })
    };
  merge_all ...$renamed_tables
}
```
And call it like this:
```nushell
> select_and_merge --cols [name age] $people $evil_twins
╭───┬───────┬──────┬───────┬──────╮
│ # │ name0 │ age0 │ name1 │ age1 │
├───┼───────┼──────┼───────┼──────┤
│ 0 │ alice │  100 │ ecila │  100 │
│ 1 │ bob   │  200 │ bob   │  200 │
│ 2 │ eve   │  300 │ eve   │  300 │
╰───┴───────┴──────┴───────┴──────╯
```

---

Suppose someone's made a command to search for APT packages:

```nushell
# The main command
def search-pkgs [
    --install                   # Whether to install any packages it finds
    log_level: int              # Pretend it's a good idea to make this a required positional parameter
    exclude?: list<string>      # Packages to exclude
    repositories?: list<string> # Which repositories to look in (searches in all if not given)
    ...pkgs                     # Package names to search for
] {
  { install: $install, log_level: $log_level, exclude: ($exclude | to nuon), repositories: ($repositories | to nuon), pkgs: ($pkgs | to nuon) }
}
```

It has a lot of parameters to configure it, so you might make your own
helper commands to wrap around it for specific cases. Here's one
example:
```nushell
# Only look for packages locally
def search-pkgs-local [
    --install              # Whether to install any packages it finds
    log_level: int
    exclude?: list<string> # Packages to exclude
    ...pkgs                # Package names to search for
] {
  # All required and optional positional parameters are given
  search-pkgs --install=$install $log_level [] ["<local URI or something>"] ...$pkgs
}
```
And you can run it like this:
```nushell
> search-pkgs-local --install=false 5 ...["python2.7" "vim"]
╭──────────────┬──────────────────────────────╮
│ install      │ false                        │
│ log_level    │ 5                            │
│ exclude      │ []                           │
│ repositories │ ["<local URI or something>"] │
│ pkgs         │ ["python2.7", vim]           │
╰──────────────┴──────────────────────────────╯
```

One thing I realized when writing this was that if we decide to not
allow passing optional arguments using the spread operator, then you can
(mis?)use the spread operator to skip optional parameters. Here, I
didn't want to give `exclude` explicitly, so I used a spread operator to
pass the packages to install. Without it, I would've needed to do
`search-pkgs-local --install=false 5 [] "python2.7" "vim"` (explicitly
pass `[]` (or `null`, in the general case) to `exclude`). There are
probably more idiomatic ways to do this, but I just thought it was
something interesting.

If you're a virologist of the [xkcd](https://xkcd.com/350/) kind,
another helper command you might make is this:
```nushell
# Install any packages it finds
def live-dangerously [ ...pkgs ] {
  # One optional argument was given (exclude), while another was not (repositories)
  search-pkgs 0 [] ...$pkgs --install # Flags can go after spread arguments
}
```

Running it:
```nushell
> live-dangerously "git" "*vi*" # *vi* because I don't feel like typing out vim and neovim
╭──────────────┬─────────────╮
│ install      │ true        │
│ log_level    │ 0           │
│ exclude      │ []          │
│ repositories │ null        │
│ pkgs         │ [git, *vi*] │
╰──────────────┴─────────────╯
```

Here's an example that uses the spread operator more than once within
the same command call:
```nushell
let extras = [ chrome firefox python java git ]

def search-pkgs-curated [ ...pkgs ] {
  (search-pkgs
      1
      [emacs]
      ["example.com", "foo.com"]
      vim # A must for everyone!
      ...($pkgs | filter { |p| not ($p | str contains "*") }) # Remove packages with globs
      python # Good tool to have
      ...$extras
      --install=false
      python3) # I forget, did I already put Python in extras?
}
```

Running it:
```nushell
> search-pkgs-curated "git" "*vi*"
╭──────────────┬───────────────────────────────────────────────────────────────────╮
│ install      │ false                                                             │
│ log_level    │ 1                                                                 │
│ exclude      │ [emacs]                                                           │
│ repositories │ [example.com, foo.com]                                            │
│ pkgs         │ [vim, git, python, chrome, firefox, python, java, git, "python3"] │
╰──────────────┴───────────────────────────────────────────────────────────────────╯
```
2023-12-28 15:43:20 +08:00
Kira
a86a7e6c29
Allow http commands' automatic redirect-following to be disabled (#11329)
Intends to close #8920 

This PR suggests a new flag for the `http` commands, `--redirect-mode`,
which enables users to choose between different redirect handling modes.
The current behaviour of letting ureq silently follow redirects remains
the default, but two new options are introduced here, following the lead
of [JavaScript's `fetch`
API](https://developer.mozilla.org/en-US/docs/Web/API/fetch#redirect):
"manual", where any 3xx response to a request is simply returned as the
command's result, and "error", where any 3xx response causes a network
error like those caused by 4xx and 5xx responses.

This PR is a draft. Tests have not been added or run, the flag is
currently only implemented for the `http get` command, and design tweaks
are likely to be appropriate.

Most notably, it's not obvious to me whether a single flag which can
take one of three values is the nicest solution here.
We might instead consider two binary flags (like
`--no-following-redirects` and `--disallow-redirects`, although I'm bad
at naming things so I need help with that anyway), or completely drop
the "error" option if it's not deemed useful enough. (I personally think
it has some merit, especially since 4xx and 5xx responses are already
treated as errors by default; So this would allow users to treat only
immediate 2xx responses as success)

# User-facing changes
New options for the `http [method]` commands. Behaviour remains
unchanged when the command line flag introduced here is not used.


![image](https://github.com/nushell/nushell/assets/12228688/1eb89f14-7d48-4f41-8a3e-cc0f1bd0a4f8)
2023-12-28 15:26:34 +08:00
Ionel Sebastian
15421dc45e
Fix the bug for "bytes remove --end" . (#11428)
This PR should close #11426 .

# Description
> ### Describe the bug
> When using the `--end` option of bytes remove, nushell panics if the
provided bytes don't exist. This doesn't seem to affect `bytes remove`
w/o flag or `bytes remove --all`.

# User-Facing Changes
Nushell doesn`t panic anymore.

# Tests + Formatting
Behavior before fixing the bug:
![nu-before
changes](https://github.com/UPB-CS-OpenSourceUpstream/nushell/assets/119429832/f9c26d88-8962-4f38-a373-ba436a26ca7c)
Behavior after fixing the bug:
![nu- after
changes](https://github.com/UPB-CS-OpenSourceUpstream/nushell/assets/119429832/0dd2b487-1696-45a6-9ea2-928cbd3a33a8)
2023-12-28 07:01:55 +08:00
Yash Thakur
9522052063
More specific errors for missing values in records (#11423)
# Description
Currently, when writing a record, if you don't give the value for a
field, the syntax error highlights the entire record instead of
pinpointing the issue. Here's some examples:

```nushell
> { a: 2, 3 } # Missing colon (and value)
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #2:1:1]
 1 │  { a: 2, 3 }
   ·  ─────┬─────
   ·       ╰── expected record
   ╰────

> { a: 2, 3: } # Missing value
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #3:1:1]
 1 │  { a: 2, 3: }
   ·  ──────┬─────
   ·        ╰── expected record
   ╰────

> { a: 2, 3 4 } # Missing colon
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #4:1:1]
 1 │  { a: 2, 3 4 }
   ·  ──────┬──────
   ·        ╰── expected record
   ╰────
```

In all of them, the entire record is highlighted red because an
`Expr::Garbage` is returned covering that whole span:


![image](https://github.com/nushell/nushell/assets/45539777/36660b50-23be-4353-b180-3f84eff3c220)

This PR is for highlighting only the part inside the record that could
not be parsed. If the record literal is big, an error message pointing
to the start of where the parser thinks things went wrong should help
people fix their code.

# User-Facing Changes
Below are screenshots of the new errors:

If there's a stray record key right before the record ends, it
highlights only that key and tells the user it expected a colon after
it:


![image](https://github.com/nushell/nushell/assets/45539777/94503256-8ea2-47dd-b69a-4b520c66f7b6)

If the record ends before the value for the last field was given, it
highlights the key and colon of that field and tells the user it
expected a value after the colon:


![image](https://github.com/nushell/nushell/assets/45539777/2f3837ec-3b35-4b81-8c57-706f8056ac04)

If there are two consecutive expressions without a colon between them,
it highlights everything from the second expression to the end of the
record and tells the user it expected a colon. I was tempted to add a
help message suggesting adding a colon in between, but that may not
always be the right thing to do.


![image](https://github.com/nushell/nushell/assets/45539777/1abaaaa8-1896-4909-bbb7-9a38cece5250)

# Tests + Formatting

# After Submitting
2023-12-27 10:15:12 +01:00
Ian Manske
ba880277bf
Remove unnecessary replace_in_variable (#11424)
# Description
`Expression::replace_in_variable` is only called in one place, and it is
called with `new_var_id` = `IN_VARIABLE_ID`. So, it ends up doing
nothing. E.g., adding `debug_assert_eq!(new_var_id, IN_VARIABLE_ID)` in
`replace_in_variable` does not trigger any panic.

# User-Facing Changes
Breaking change for `nu_protocol`.
2023-12-26 18:46:49 +01:00
Darren Schroeder
40241e9be6
Revert "Bump reedline development version" (#11425)
Reverts nushell/nushell#11406

This PR https://github.com/nushell/reedline/pull/688 is causing clear
screens to happen at strange times. Documented here
https://github.com/nushell/nushell/pull/11406#issuecomment-1869591405
2023-12-26 09:08:32 -06:00
nibon7
34f3da7150
Don't panic when http_client fails (#11422)
# Description

This PR makes `http_client` return `Result<ureq::Agent, ShellError>`, so
errors can be propagated to the caller.
2023-12-25 23:09:37 +08:00
nibon7
534287ed65
Don't create a thread if stderr_stream is None (#11421)
# Description

There is no need to create a thread if `stderr_stream` is `None`.
2023-12-25 08:10:15 -06:00
dependabot[bot]
913c2b8d1c
Bump ical from 0.8.0 to 0.9.0 (#11419)
Bumps [ical](https://github.com/Peltoche/ical-rs) from 0.8.0 to 0.9.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Peltoche/ical-rs/releases">ical's
releases</a>.</em></p>
<blockquote>
<h2>v0.9.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Add the github actions by <a
href="https://github.com/Peltoche"><code>@​Peltoche</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/35">Peltoche/ical-rs#35</a></li>
<li>Use thiserror instead of failure by <a
href="https://github.com/Peltoche"><code>@​Peltoche</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/36">Peltoche/ical-rs#36</a></li>
<li>Accept lowercase vCards by <a
href="https://github.com/link2xt"><code>@​link2xt</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/37">Peltoche/ical-rs#37</a></li>
<li>Parse multiline attributes with tabs correctly by <a
href="https://github.com/FliegendeWurst"><code>@​FliegendeWurst</code></a>
in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/41">Peltoche/ical-rs#41</a></li>
<li>Ical serde by <a
href="https://github.com/daladim"><code>@​daladim</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/44">Peltoche/ical-rs#44</a></li>
<li>Set the license to Apache 2 by <a
href="https://github.com/Peltoche"><code>@​Peltoche</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/47">Peltoche/ical-rs#47</a></li>
<li>Update README.md by <a
href="https://github.com/Zearin"><code>@​Zearin</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/51">Peltoche/ical-rs#51</a></li>
<li>Adding ical-output support by <a
href="https://github.com/migmedia"><code>@​migmedia</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/49">Peltoche/ical-rs#49</a></li>
<li>Improve documentation of builders. by <a
href="https://github.com/migmedia"><code>@​migmedia</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/52">Peltoche/ical-rs#52</a></li>
<li>Add access functions for properties by <a
href="https://github.com/reedts"><code>@​reedts</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/53">Peltoche/ical-rs#53</a></li>
<li>Fix README by <a
href="https://github.com/westy92"><code>@​westy92</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/55">Peltoche/ical-rs#55</a></li>
<li>Handle the case were a line contains non-utf8 characters by <a
href="https://github.com/Peltoche"><code>@​Peltoche</code></a> in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/57">Peltoche/ical-rs#57</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/link2xt"><code>@​link2xt</code></a> made
their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/37">Peltoche/ical-rs#37</a></li>
<li><a
href="https://github.com/FliegendeWurst"><code>@​FliegendeWurst</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/41">Peltoche/ical-rs#41</a></li>
<li><a href="https://github.com/daladim"><code>@​daladim</code></a> made
their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/44">Peltoche/ical-rs#44</a></li>
<li><a href="https://github.com/Zearin"><code>@​Zearin</code></a> made
their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/51">Peltoche/ical-rs#51</a></li>
<li><a href="https://github.com/migmedia"><code>@​migmedia</code></a>
made their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/49">Peltoche/ical-rs#49</a></li>
<li><a href="https://github.com/reedts"><code>@​reedts</code></a> made
their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/53">Peltoche/ical-rs#53</a></li>
<li><a href="https://github.com/westy92"><code>@​westy92</code></a> made
their first contribution in <a
href="https://redirect.github.com/Peltoche/ical-rs/pull/55">Peltoche/ical-rs#55</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Peltoche/ical-rs/compare/0.6.0...v0.9.0">https://github.com/Peltoche/ical-rs/compare/0.6.0...v0.9.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/Peltoche/ical-rs/commits/v0.9.0">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 09:23:27 +08:00
69 changed files with 1504 additions and 952 deletions

1
.github/.typos.toml vendored
View File

@ -12,3 +12,4 @@ IIF = "IIF"
numer = "numer"
ratatui = "ratatui"
doas = "doas"
wheres = "wheres"

View File

@ -119,7 +119,7 @@ jobs:
os: ubuntu-20.04
target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-latest
target_rustflags: ''
runs-on: ${{matrix.os}}

View File

@ -82,8 +82,8 @@ print $'Start building ($bin)...'; hr-line
# ----------------------------------------------------------------------------
# Build for Ubuntu and macOS
# ----------------------------------------------------------------------------
if $os in [$USE_UBUNTU, 'macos-latest'] {
if $os == $USE_UBUNTU {
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
if $os starts-with ubuntu {
sudo apt update
sudo apt-get install libxcb-composite0-dev -y
}
@ -106,7 +106,7 @@ if $os in [$USE_UBUNTU, 'macos-latest'] {
_ => {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
if $os starts-with ubuntu { sudo apt install musl-tools -y }
cargo-build-nu $flags
}
}
@ -153,7 +153,7 @@ if ($ver | str trim | is-empty) {
# Create a release archive and send it to output for the following steps
# ----------------------------------------------------------------------------
cd $dist; print $'(char nl)Creating release archive...'; hr-line
if $os in [$USE_UBUNTU, 'macos-latest'] {
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
let files = (ls | get name)
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }

View File

@ -66,7 +66,7 @@ jobs:
os: ubuntu-20.04
target_rustflags: ''
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-latest
target_rustflags: ''
runs-on: ${{matrix.os}}

View File

@ -10,6 +10,6 @@ jobs:
uses: actions/checkout@v4
- name: Check spelling
uses: crate-ci/typos@v1.16.25
uses: crate-ci/typos@v1.17.0
with:
config: ./.github/.typos.toml

105
Cargo.lock generated
View File

@ -1281,7 +1281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
"rustix 0.38.28",
"rustix",
"windows-sys 0.48.0",
]
@ -1802,9 +1802,9 @@ dependencies = [
[[package]]
name = "ical"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356d82bd58997815d55ea6f9081bd4cac149e50ca943f7a4f7c050fec7271c1f"
checksum = "26393c372d4c4d51616084afe36c0b44e4467febaa6f91f11f789094b4863bf9"
dependencies = [
"thiserror",
]
@ -1884,17 +1884,6 @@ version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0508c56cfe9bfd5dfeb0c22ab9a6abfda2f27bdca422132e494266351ed8d83c"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "is-docker"
version = "0.2.0"
@ -1911,7 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.28",
"rustix",
"windows-sys 0.48.0",
]
@ -2233,12 +2222,6 @@ dependencies = [
"serde",
]
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
@ -2293,9 +2276,9 @@ dependencies = [
[[package]]
name = "lsp-types"
version = "0.94.1"
version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
dependencies = [
"bitflags 1.3.2",
"serde",
@ -2697,7 +2680,7 @@ dependencies = [
"percent-encoding",
"reedline",
"rstest",
"sysinfo",
"sysinfo 0.30.4",
"unicode-segmentation",
"uuid",
"which 5.0.0",
@ -2876,7 +2859,7 @@ dependencies = [
"serde_urlencoded",
"serde_yaml",
"sha2",
"sysinfo",
"sysinfo 0.30.4",
"tabled",
"terminal_size 0.3.0",
"titlecase",
@ -2924,7 +2907,7 @@ dependencies = [
"nu-utils",
"ratatui",
"strip-ansi-escapes",
"terminal_size 0.2.6",
"terminal_size 0.3.0",
"unicode-width",
]
@ -3060,8 +3043,8 @@ dependencies = [
"ntapi",
"once_cell",
"procfs",
"sysinfo",
"winapi",
"sysinfo 0.30.4",
"windows 0.52.0",
]
[[package]]
@ -4089,7 +4072,7 @@ dependencies = [
"polars-error",
"rayon",
"smartstring",
"sysinfo",
"sysinfo 0.29.11",
"version_check",
]
@ -4194,7 +4177,7 @@ dependencies = [
"hex",
"lazy_static",
"procfs-core",
"rustix 0.38.28",
"rustix",
]
[[package]]
@ -4392,12 +4375,12 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.27.1"
source = "git+https://github.com/nushell/reedline.git?branch=main#e097b88dab538705c7b165cf3a1f5cf3a74a23bb"
source = "git+https://github.com/nushell/reedline.git?branch=main#b68ce33c750b700bb4f8adccbc2e160d1b7c8742"
dependencies = [
"chrono",
"crossterm",
"fd-lock",
"itertools 0.10.5",
"itertools 0.12.0",
"nu-ansi-term",
"rusqlite",
"serde",
@ -4644,20 +4627,6 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "0.38.28"
@ -4667,7 +4636,7 @@ dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys 0.4.12",
"linux-raw-sys",
"windows-sys 0.52.0",
]
@ -4899,9 +4868,9 @@ dependencies = [
[[package]]
name = "shadow-rs"
version = "0.25.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615d846f7174a0850dca101bca72f6913e3376a64c5fda2b965d7fc3d1ff60cb"
checksum = "878cb1e3162d98ee1016b832efbb683ad6302b462a2894c54f488dc0bd96f11c"
dependencies = [
"const_format",
"is_debug",
@ -5280,10 +5249,24 @@ dependencies = [
"libc",
"ntapi",
"once_cell",
"rayon",
"winapi",
]
[[package]]
name = "sysinfo"
version = "0.30.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "717570a2533606f81f8cfac02a1915a620e725ffb78f6fc5e259769a4d747407"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"rayon",
"windows 0.52.0",
]
[[package]]
name = "tabled"
version = "0.14.0"
@ -5311,7 +5294,7 @@ dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix 0.38.28",
"rustix",
"windows-sys 0.48.0",
]
@ -5345,23 +5328,13 @@ dependencies = [
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
dependencies = [
"rustix 0.37.27",
"windows-sys 0.48.0",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix 0.38.28",
"rustix",
"windows-sys 0.48.0",
]
@ -6069,7 +6042,7 @@ dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.28",
"rustix",
]
[[package]]
@ -6081,7 +6054,7 @@ dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.28",
"rustix",
"windows-sys 0.48.0",
]
@ -6396,8 +6369,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995"
dependencies = [
"libc",
"linux-raw-sys 0.4.12",
"rustix 0.38.28",
"linux-raw-sys",
"rustix",
]
[[package]]

View File

@ -37,7 +37,7 @@ miette = { version = "5.10", features = ["fancy-no-backtrace"] }
once_cell = "1.18"
percent-encoding = "2"
pathdiff = "0.2"
sysinfo = "0.29"
sysinfo = "0.30"
unicode-segmentation = "1.10"
uuid = { version = "1.6.0", features = ["v4"] }
which = "5.0.0"

View File

@ -354,9 +354,7 @@ impl NuCompleter {
if let Some(external_result) = self.external_completion(
block_id, &spans, offset, new_span,
) {
if !external_result.is_empty() {
return external_result;
}
return external_result;
}
}

View File

@ -32,7 +32,7 @@ use std::{
sync::atomic::Ordering,
time::Instant,
};
use sysinfo::SystemExt;
use sysinfo::System;
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
@ -135,17 +135,6 @@ pub fn evaluate_repl(
use_color,
);
start_time = std::time::Instant::now();
let sys = sysinfo::System::new();
perf(
"get sysinfo",
start_time,
file!(),
line!(),
column!(),
use_color,
);
if let Some(s) = prerun_command {
eval_source(
engine_state,
@ -428,7 +417,7 @@ pub fn evaluate_repl(
match input {
Ok(Signal::Success(s)) => {
let hostname = sys.host_name();
let hostname = System::host_name();
let history_supports_meta =
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()

View File

@ -385,6 +385,7 @@ fn find_matching_block_end_in_expr(
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
Argument::Positional(inner_expr) => Some(inner_expr),
Argument::Unknown(inner_expr) => Some(inner_expr),
Argument::Spread(inner_expr) => Some(inner_expr),
};
if let Some(inner_expr) = opt_expr {

View File

@ -20,10 +20,10 @@ nu-ansi-term = "0.49.0"
fancy-regex = "0.12"
itertools = "0.12"
shadow-rs = { version = "0.25", default-features = false }
shadow-rs = { version = "0.26", default-features = false }
[build-dependencies]
shadow-rs = { version = "0.25", default-features = false }
shadow-rs = { version = "0.26", default-features = false }
[features]
mimalloc = []

View File

@ -80,7 +80,7 @@ serde_json = "1.0"
serde_urlencoded = "0.7"
serde_yaml = "0.9"
sha2 = "0.10"
sysinfo = "0.29"
sysinfo = "0.30"
tabled = { version = "0.14.0", features = ["color"], default-features = false }
terminal_size = "0.3"
titlecase = "2.0"

View File

@ -1,4 +1,4 @@
use nu_engine::eval_expression;
use nu_engine::{eval_expression, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
@ -48,8 +48,7 @@ impl Command for BytesBuild {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut output = vec![];
for expr in call.positional_iter() {
let val = eval_expression(engine_state, stack, expr)?;
for val in call.rest_iter_flattened(0, |expr| eval_expression(engine_state, stack, expr))? {
match val {
Value::Binary { mut val, .. } => output.append(&mut val),
// Explicitly propagate errors instead of dropping them.

View File

@ -105,6 +105,13 @@ impl Command for BytesRemove {
vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
)),
},
Example {
description: "Remove find binary from end not found",
example: "0x[10 AA 10 BB CC AA 10] | bytes remove --end 0x[11]",
result: Some(Value::test_binary (
vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA, 0x10],
)),
},
Example {
description: "Remove all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
@ -159,8 +166,11 @@ fn remove_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
}
// append the remaining thing to result, this can be happening when
// we have something to remove and remove_all is False.
let mut remain = input[..left as usize].iter().copied().rev().collect();
result.append(&mut remain);
// check if the left is positive, if it is not, we don't need to append anything.
if left > 0 {
let mut remain = input[..left as usize].iter().copied().rev().collect();
result.append(&mut remain);
}
result = result.into_iter().rev().collect();
Value::binary(result, span)
} else {

View File

@ -191,6 +191,24 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, inner_expr.span),
"type" => Value::string(arg_value_type, span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(Value::record(record, inner_expr.span));
}
Argument::Spread(inner_expr) => {
let arg_type = "spread";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.span();
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, inner_expr.span),

View File

@ -4,7 +4,7 @@ use nu_protocol::{
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
Signature, Span, Type, Value,
};
use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
const ENV_PATH_SEPARATOR_CHAR: char = {
#[cfg(target_family = "windows")]
{
@ -98,8 +98,9 @@ impl LazySystemInfoRecord {
}
"system" => {
// only get information requested
let system_opt =
SystemOpt::from((system_option, || RefreshKind::new().with_memory()));
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
}));
let system = system_opt.get_system();
@ -135,53 +136,66 @@ impl LazySystemInfoRecord {
"virtual_memory" => Value::filesize(p.virtual_memory() as i64, self.span),
"status" => Value::string(p.status().to_string(), self.span),
"root" => {
if let Some(filename) = p.exe().parent() {
Value::string(filename.to_string_lossy().to_string(), self.span)
if let Some(path) = p.exe().and_then(|p| p.parent()) {
Value::string(path.to_string_lossy().to_string(), self.span)
} else {
Value::nothing(self.span)
}
},
"cwd" => Value::string(p.cwd().to_string_lossy().to_string(), self.span),
"exe_path" => Value::string(p.exe().to_string_lossy().to_string(), self.span),
"cwd" => {
if let Some(path) = p.cwd() {
Value::string(path.to_string_lossy().to_string(), self.span)
}else{
Value::nothing(self.span)
}
},
"exe_path" => {
if let Some(path)= p.exe() {
Value::string(path.to_string_lossy().to_string(), self.span)
}else{
Value::nothing(self.span)
}
},
"command" => Value::string(p.cmd().join(" "), self.span),
"name" => Value::string(p.name().to_string(), self.span),
"environment" => {
let mut env_rec = Record::new();
for val in p.environ() {
if let Some((key, value)) = val.split_once('=') {
let is_env_var_a_list = {
"environment" => {
let mut env_rec = Record::new();
for val in p.environ() {
if let Some((key, value)) = val.split_once('=') {
let is_env_var_a_list = {
{
#[cfg(target_family = "windows")]
{
#[cfg(target_family = "windows")]
{
key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath"
}
#[cfg(not(target_family = "windows"))]
{
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
}
key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath"
}
#[cfg(not(target_family = "windows"))]
{
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
}
};
if is_env_var_a_list {
let items = value.split(ENV_PATH_SEPARATOR_CHAR).map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.span));
} else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs
let items = value.split(':').map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.span));
} else {
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
}
};
if is_env_var_a_list {
let items = value.split(ENV_PATH_SEPARATOR_CHAR).map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.span));
} else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs
let items = value.split(':').map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
env_rec.push(key.to_string(), Value::list(items, self.span));
} else {
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
}
}
Value::record(env_rec, self.span)
},
}
Value::record(env_rec, self.span)
},
},
self.span,
))
} else {
// If we can't get the process information, just return the system information
// only get information requested
let system_opt =
SystemOpt::from((system_option, || RefreshKind::new().with_memory()));
let system_opt = SystemOpt::from((system_option, || {
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
}));
let system = system_opt.get_system();
Ok(Value::record(
@ -228,7 +242,7 @@ impl<'a> LazyRecord<'a> for LazySystemInfoRecord {
.without_disk_usage()
.without_user(),
)
.with_memory();
.with_memory(MemoryRefreshKind::everything());
// only get information requested
let system = System::new_with_specifics(rk);

View File

@ -381,11 +381,23 @@ fn rm(
{
unreachable!()
}
} else if metadata.is_file()
|| is_socket
|| is_fifo
|| metadata.file_type().is_symlink()
{
} else if metadata.is_symlink() {
// In Windows, symlink pointing to a directory can be removed using
// std::fs::remove_dir instead of std::fs::remove_file.
#[cfg(windows)]
{
f.metadata().and_then(|metadata| {
if metadata.is_dir() {
std::fs::remove_dir(&f)
} else {
std::fs::remove_file(&f)
}
})
}
#[cfg(not(windows))]
std::fs::remove_file(&f)
} else if metadata.is_file() || is_socket || is_fifo {
std::fs::remove_file(&f)
} else {
std::fs::remove_dir_all(&f)

View File

@ -34,10 +34,10 @@ impl Command for ToXml {
fn extra_usage(&self) -> &str {
r#"Every XML entry is represented via a record with tag, attribute and content fields.
To represent different types of entries different values must be written to this fields:
1. Tag entry: `{tag: <tag name> attrs: {<attr name>: "<string value>" ...} content: [<entries>]}`
2. Comment entry: `{tag: '!' attrs: null content: "<comment string>"}`
3. Processing instruction (PI): `{tag: '?<pi name>' attrs: null content: "<pi content string>"}`
4. Text: `{tag: null attrs: null content: "<text>"}`. Or as plain `<text>` instead of record.
1. Tag entry: `{tag: <tag name> attributes: {<attr name>: "<string value>" ...} content: [<entries>]}`
2. Comment entry: `{tag: '!' attributes: null content: "<comment string>"}`
3. Processing instruction (PI): `{tag: '?<pi name>' attributes: null content: "<pi content string>"}`
4. Text: `{tag: null attributes: null content: "<text>"}`. Or as plain `<text>` instead of record.
Additionally any field which is: empty record, empty list or null, can be omitted."#
}
@ -46,7 +46,7 @@ Additionally any field which is: empty record, empty list or null, can be omitte
vec![
Example {
description: "Outputs an XML string representing the contents of this table",
example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attrs: null content : Event}]}]} | to xml"#,
example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attributes: null content : Event}]}]} | to xml"#,
result: Some(Value::test_string(
"<note><remember>Event</remember></note>",
)),
@ -110,6 +110,17 @@ fn to_xml_entry<W: Write>(
}
if let Value::Record { val: record, .. } = &entry {
if let Some(bad_column) = find_invalid_column(record) {
return Err(ShellError::CantConvert {
to_type: "XML".into(),
from_type: "record".into(),
span: entry_span,
help: Some(format!(
"Invalid column \"{}\" in xml entry. Only \"{}\", \"{}\" and \"{}\" are permitted",
bad_column, COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME
)),
});
}
// If key is not found it is assumed to be nothing. This way
// user can write a tag like {tag: a content: [...]} instead
// of longer {tag: a attributes: {} content: [...]}
@ -144,7 +155,12 @@ fn to_xml_entry<W: Write>(
(Value::String { val: tag_name, .. }, attrs, children) => to_tag_like(
entry_span, tag_name, tag_span, attrs, children, top_level, writer,
),
_ => Ok(()),
_ => Err(ShellError::CantConvert {
to_type: "XML".into(),
from_type: "record".into(),
span: entry_span,
help: Some("Tag missing or is not a string".into()),
}),
}
} else {
Err(ShellError::CantConvert {
@ -156,6 +172,14 @@ fn to_xml_entry<W: Write>(
}
}
fn find_invalid_column(record: &Record) -> Option<&String> {
const VALID_COLS: [&str; 3] = [COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME];
record
.cols
.iter()
.find(|col| !VALID_COLS.contains(&col.as_str()))
}
/// Convert record to tag-like entry: tag, PI, comment.
fn to_tag_like<W: Write>(
entry_span: Span,

View File

@ -5,7 +5,8 @@ use base64::{alphabet, Engine};
use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{
record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Value,
record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Spanned,
Value,
};
use ureq::{Error, ErrorKind, Request, Response};
@ -26,29 +27,45 @@ pub enum BodyType {
Unknown,
}
// Only panics if the user agent is invalid but we define it statically so either
// it always or never fails
#[derive(Clone, Copy, PartialEq)]
pub enum RedirectMode {
Follow,
Error,
Manual,
}
pub fn http_client(
allow_insecure: bool,
redirect_mode: RedirectMode,
engine_state: &EngineState,
stack: &mut Stack,
) -> ureq::Agent {
) -> Result<ureq::Agent, ShellError> {
let tls = native_tls::TlsConnector::builder()
.danger_accept_invalid_certs(allow_insecure)
.build()
.expect("Failed to build network tls");
.map_err(|e| ShellError::GenericError {
error: format!("Failed to build network tls: {}", e),
msg: String::new(),
span: None,
help: None,
inner: vec![],
})?;
let mut agent_builder = ureq::builder()
.user_agent("nushell")
.tls_connector(std::sync::Arc::new(tls));
if let RedirectMode::Manual | RedirectMode::Error = redirect_mode {
agent_builder = agent_builder.redirects(0);
}
if let Some(http_proxy) = retrieve_http_proxy_from_env(engine_state, stack) {
if let Ok(proxy) = ureq::Proxy::new(http_proxy) {
agent_builder = agent_builder.proxy(proxy);
}
};
agent_builder.build()
Ok(agent_builder.build())
}
pub fn http_parse_url(
@ -68,6 +85,18 @@ pub fn http_parse_url(
Ok((requested_url, url))
}
pub fn http_parse_redirect_mode(mode: Option<Spanned<String>>) -> Result<RedirectMode, ShellError> {
mode.map_or(Ok(RedirectMode::Follow), |v| match &v.item[..] {
"follow" | "f" => Ok(RedirectMode::Follow),
"error" | "e" => Ok(RedirectMode::Error),
"manual" | "m" => Ok(RedirectMode::Manual),
_ => Err(ShellError::TypeMismatch {
err_message: "Invalid redirect handling mode".to_string(),
span: v.span,
}),
})
}
pub fn response_to_buffer(
response: Response,
engine_state: &EngineState,
@ -452,6 +481,26 @@ fn transform_response_using_content_type(
};
}
pub fn check_response_redirection(
redirect_mode: RedirectMode,
span: Span,
response: &Result<Response, ShellErrorOrRequestError>,
) -> Result<(), ShellError> {
if let Ok(resp) = response {
if RedirectMode::Error == redirect_mode && (300..400).contains(&resp.status()) {
return Err(ShellError::NetworkFailure {
msg: format!(
"Redirect encountered when redirect handling mode was 'error' ({} {})",
resp.status(),
resp.status_text()
),
span,
});
}
}
Ok(())
}
fn request_handle_response_content(
engine_state: &EngineState,
stack: &mut Stack,

View File

@ -2,12 +2,13 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request,
};
use super::client::RequestFlags;
@ -79,6 +80,11 @@ impl Command for SubCommand {
"allow-errors",
"do not fail if the server returns an error code",
Some('e'),
).named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
@ -150,6 +156,7 @@ struct Arguments {
timeout: Option<Value>,
full: bool,
allow_errors: bool,
redirect: Option<Spanned<String>>,
}
fn run_delete(
@ -170,6 +177,7 @@ fn run_delete(
timeout: call.get_flag(engine_state, stack, "max-time")?,
full: call.has_flag("full"),
allow_errors: call.has_flag("allow-errors"),
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
helper(engine_state, stack, call, args)
@ -186,8 +194,9 @@ fn helper(
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.delete(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -202,6 +211,7 @@ fn helper(
allow_errors: args.allow_errors,
};
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response(
engine_state,
stack,

View File

@ -2,16 +2,15 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request, RequestFlags,
};
use super::client::RequestFlags;
#[derive(Clone)]
pub struct SubCommand;
@ -73,6 +72,12 @@ impl Command for SubCommand {
"do not fail if the server returns an error code",
Some('e'),
)
.named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
}
@ -137,6 +142,7 @@ struct Arguments {
timeout: Option<Value>,
full: bool,
allow_errors: bool,
redirect: Option<Spanned<String>>,
}
fn run_get(
@ -155,6 +161,7 @@ fn run_get(
timeout: call.get_flag(engine_state, stack, "max-time")?,
full: call.has_flag("full"),
allow_errors: call.has_flag("allow-errors"),
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
helper(engine_state, stack, call, args)
}
@ -170,8 +177,9 @@ fn helper(
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.get(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -186,6 +194,7 @@ fn helper(
allow_errors: args.allow_errors,
};
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response(
engine_state,
stack,

View File

@ -5,12 +5,13 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response_headers, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response_headers,
request_set_timeout, send_request,
};
#[derive(Clone)]
@ -58,6 +59,11 @@ impl Command for SubCommand {
"insecure",
"allow insecure server connections when using SSL",
Some('k'),
).named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
@ -114,6 +120,7 @@ struct Arguments {
user: Option<String>,
password: Option<String>,
timeout: Option<Value>,
redirect: Option<Spanned<String>>,
}
fn run_head(
@ -129,6 +136,7 @@ fn run_head(
user: call.get_flag(engine_state, stack, "user")?,
password: call.get_flag(engine_state, stack, "password")?,
timeout: call.get_flag(engine_state, stack, "max-time")?,
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
let ctrl_c = engine_state.ctrlc.clone();
@ -146,8 +154,9 @@ fn helper(
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.head(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -155,6 +164,7 @@ fn helper(
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request, None, None, ctrlc);
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response_headers(span, response)
}

View File

@ -10,7 +10,7 @@ use crate::network::http::client::{
request_handle_response, request_set_timeout, send_request,
};
use super::client::RequestFlags;
use super::client::{RedirectMode, RequestFlags};
#[derive(Clone)]
pub struct SubCommand;
@ -160,7 +160,7 @@ fn helper(
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?;
let mut request = client.request("OPTIONS", &requested_url);
request = request_set_timeout(args.timeout, request)?;

View File

@ -2,12 +2,13 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request,
};
use super::client::RequestFlags;
@ -75,6 +76,11 @@ impl Command for SubCommand {
"allow-errors",
"do not fail if the server returns an error code",
Some('e'),
).named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
@ -142,6 +148,7 @@ struct Arguments {
timeout: Option<Value>,
full: bool,
allow_errors: bool,
redirect: Option<Spanned<String>>,
}
fn run_patch(
@ -162,6 +169,7 @@ fn run_patch(
timeout: call.get_flag(engine_state, stack, "max-time")?,
full: call.has_flag("full"),
allow_errors: call.has_flag("allow-errors"),
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
helper(engine_state, stack, call, args)
@ -178,8 +186,9 @@ fn helper(
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.patch(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -194,6 +203,7 @@ fn helper(
allow_errors: args.allow_errors,
};
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response(
engine_state,
stack,

View File

@ -2,12 +2,13 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request,
};
use super::client::RequestFlags;
@ -75,6 +76,11 @@ impl Command for SubCommand {
"allow-errors",
"do not fail if the server returns an error code",
Some('e'),
).named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
@ -140,6 +146,7 @@ struct Arguments {
timeout: Option<Value>,
full: bool,
allow_errors: bool,
redirect: Option<Spanned<String>>,
}
fn run_post(
@ -160,6 +167,7 @@ fn run_post(
timeout: call.get_flag(engine_state, stack, "max-time")?,
full: call.has_flag("full"),
allow_errors: call.has_flag("allow-errors"),
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
helper(engine_state, stack, call, args)
@ -176,8 +184,9 @@ fn helper(
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.post(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -192,6 +201,7 @@ fn helper(
allow_errors: args.allow_errors,
};
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response(
engine_state,
stack,

View File

@ -2,12 +2,13 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use crate::network::http::client::{
http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
request_handle_response, request_set_timeout, send_request,
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request,
};
use super::client::RequestFlags;
@ -75,6 +76,11 @@ impl Command for SubCommand {
"allow-errors",
"do not fail if the server returns an error code",
Some('e'),
).named(
"redirect-mode",
SyntaxShape::String,
"What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
Some('R')
)
.filter()
.category(Category::Network)
@ -140,6 +146,7 @@ struct Arguments {
timeout: Option<Value>,
full: bool,
allow_errors: bool,
redirect: Option<Spanned<String>>,
}
fn run_put(
@ -160,6 +167,7 @@ fn run_put(
timeout: call.get_flag(engine_state, stack, "max-time")?,
full: call.has_flag("full"),
allow_errors: call.has_flag("allow-errors"),
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
helper(engine_state, stack, call, args)
@ -176,8 +184,9 @@ fn helper(
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
let client = http_client(args.insecure, engine_state, stack);
let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
let mut request = client.put(&requested_url);
request = request_set_timeout(args.timeout, request)?;
@ -192,6 +201,7 @@ fn helper(
allow_errors: args.allow_errors,
};
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response(
engine_state,
stack,

View File

@ -1,8 +1,9 @@
use nu_cmd_base::hook::eval_hook;
use nu_engine::env_to_strings;
use nu_engine::eval_expression;
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, Expr, Expression},
ast::{Call, Expr},
did_you_mean,
engine::{Command, EngineState, Stack},
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, Span, Spanned,
@ -113,7 +114,6 @@ pub fn create_external_command(
trim_end_newline: bool,
) -> Result<ExternalCommand, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
// Translate environment variables from Values to Strings
let env_vars_str = env_to_strings(engine_state, stack)?;
@ -132,11 +132,24 @@ pub fn create_external_command(
}
let mut spanned_args = vec![];
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
let mut arg_keep_raw = vec![];
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
match one_arg {
for (arg, spread) in call.rest_iter(1) {
// TODO: Disallow automatic spreading entirely later. This match block will
// have to be refactored, and lists will have to be disallowed in the parser too
match eval_expression(engine_state, stack, arg)? {
Value::List { vals, .. } => {
if !spread {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "Automatically spreading lists is deprecated".into(),
msg: "Spreading lists automatically when calling external commands is deprecated and will be removed in 0.91.".into(),
span: Some(arg.span),
help: Some("Use the spread operator (put a '...' before the argument)".into()),
inner: vec![],
},
);
}
// turn all the strings in the array into params.
// Example: one_arg may be something like ["ls" "-a"]
// convert it to "ls" "-a"
@ -147,15 +160,20 @@ pub fn create_external_command(
}
}
val => {
spanned_args.push(value_as_spanned(val)?);
match one_arg_expr.expr {
// refer to `parse_dollar_expr` function
// the expression type of $variable_name, $"($variable_name)"
// will be Expr::StringInterpolation, Expr::FullCellPath
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
_ => arg_keep_raw.push(false),
if spread {
return Err(ShellError::CannotSpreadAsList { span: arg.span });
} else {
spanned_args.push(value_as_spanned(val)?);
match arg.expr {
// refer to `parse_dollar_expr` function
// the expression type of $variable_name, $"($variable_name)"
// will be Expr::StringInterpolation, Expr::FullCellPath
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => {
arg_keep_raw.push(true)
}
_ => arg_keep_raw.push(false),
}
}
{}
}
}
}

View File

@ -8,7 +8,7 @@ use nu_protocol::{
};
use std::time::{Duration, UNIX_EPOCH};
use sysinfo::{
ComponentExt, CpuExt, CpuRefreshKind, DiskExt, NetworkExt, System, SystemExt, UserExt,
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
};
#[derive(Clone)]
@ -106,14 +106,12 @@ pub fn trim_cstyle_null(s: String) -> String {
}
pub fn disks(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_disks();
sys.refresh_disks_list();
let disks = Disks::new_with_refreshed_list();
let mut output = vec![];
for disk in sys.disks() {
for disk in disks.list() {
let device = trim_cstyle_null(disk.name().to_string_lossy().to_string());
let typ = trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string());
let typ = trim_cstyle_null(disk.file_system().to_string_lossy().to_string());
let record = record! {
"device" => Value::string(device, span),
@ -131,12 +129,10 @@ pub fn disks(span: Span) -> Value {
}
pub fn net(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_networks();
sys.refresh_networks_list();
let networks = Networks::new_with_refreshed_list();
let mut output = vec![];
for (iface, data) in sys.networks() {
for (iface, data) in networks.list() {
let record = record! {
"name" => Value::string(trim_cstyle_null(iface.to_string()), span),
"sent" => Value::filesize(data.total_transmitted() as i64, span),
@ -154,7 +150,7 @@ pub fn cpu(span: Span) -> Value {
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL * 2);
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
let mut output = vec![];
@ -163,7 +159,7 @@ pub fn cpu(span: Span) -> Value {
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
let load_avg = sys.load_average();
let load_avg = System::load_average();
let load_avg = trim_cstyle_null(format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
@ -211,42 +207,39 @@ pub fn mem(span: Span) -> Value {
}
pub fn host(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_users_list();
let mut record = Record::new();
if let Some(name) = sys.name() {
if let Some(name) = System::name() {
record.push("name", Value::string(trim_cstyle_null(name), span));
}
if let Some(version) = sys.os_version() {
if let Some(version) = System::os_version() {
record.push("os_version", Value::string(trim_cstyle_null(version), span));
}
if let Some(long_version) = sys.long_os_version() {
if let Some(long_version) = System::long_os_version() {
record.push(
"long_os_version",
Value::string(trim_cstyle_null(long_version), span),
);
}
if let Some(version) = sys.kernel_version() {
if let Some(version) = System::kernel_version() {
record.push(
"kernel_version",
Value::string(trim_cstyle_null(version), span),
);
}
if let Some(hostname) = sys.host_name() {
if let Some(hostname) = System::host_name() {
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
}
record.push(
"uptime",
Value::duration(1000000000 * sys.uptime() as i64, span),
Value::duration(1000000000 * System::uptime() as i64, span),
);
// Creates a new SystemTime from the specified number of whole seconds
let d = UNIX_EPOCH + Duration::from_secs(sys.boot_time());
let d = UNIX_EPOCH + Duration::from_secs(System::boot_time());
// Create DateTime from SystemTime
let datetime = DateTime::<Local>::from(d);
// Convert to local time and then rfc3339
@ -254,11 +247,16 @@ pub fn host(span: Span) -> Value {
record.push("boot_time", Value::string(timestamp_str, span));
let mut users = vec![];
for user in sys.users() {
let users = Users::new_with_refreshed_list();
let mut users_list = vec![];
for user in users.list() {
let mut groups = vec![];
for group in user.groups() {
groups.push(Value::string(trim_cstyle_null(group.to_string()), span));
groups.push(Value::string(
trim_cstyle_null(group.name().to_string()),
span,
));
}
let record = record! {
@ -266,24 +264,22 @@ pub fn host(span: Span) -> Value {
"groups" => Value::list(groups, span),
};
users.push(Value::record(record, span));
users_list.push(Value::record(record, span));
}
if !users.is_empty() {
record.push("sessions", Value::list(users, span));
record.push("sessions", Value::list(users_list, span));
}
Value::record(record, span)
}
pub fn temp(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_components();
sys.refresh_components_list();
let components = Components::new_with_refreshed_list();
let mut output = vec![];
for component in sys.components() {
for component in components.list() {
let mut record = record! {
"unit" => Value::string(component.label(), span),
"temp" => Value::float(component.temperature() as f64, span),

View File

@ -1,12 +1,24 @@
use nu_test_support::nu;
#[test]
fn basic() {
let actual = nu!(r#"
(^echo a | complete) == {stdout: "a\n", exit_code: 0}
fn basic_stdout() {
let without_complete = nu!(r#"
nu --testbin cococo test
"#);
let with_complete = nu!(r#"
(nu --testbin cococo test | complete).stdout
"#);
assert_eq!(actual.out, "true");
assert_eq!(with_complete.out, without_complete.out);
}
#[test]
fn basic_exit_code() {
let with_complete = nu!(r#"
(nu --testbin cococo test | complete).exit_code
"#);
assert_eq!(with_complete.out, "0");
}
#[test]

View File

@ -197,7 +197,7 @@ fn def_wrapped_with_block() {
#[test]
fn def_wrapped_from_module() {
let actual = nu!(r#"module spam {
export def --wrapped my-echo [...rest] { ^echo $rest }
export def --wrapped my-echo [...rest] { nu --testbin cococo ...$rest }
}
use spam

View File

@ -49,7 +49,7 @@ fn exec_misc_values() {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
nu -c 'let x = "abc"; exec nu --testbin cococo $x [ a b c ]'
nu -c 'let x = "abc"; exec nu --testbin cococo $x ...[ a b c ]'
"#
));

View File

@ -38,3 +38,68 @@ fn http_delete_failed_due_to_server_error() {
assert!(actual.err.contains("Bad request (400)"))
}
#[test]
fn http_delete_follows_redirect() {
let mut server = Server::new();
let _mock = server.mock("GET", "/bar").with_body("bar").create();
let _mock = server
.mock("DELETE", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!("http delete {url}/foo", url = server.url()).as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_delete_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("DELETE", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http delete --redirect-mode manual {url}/foo",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "foo");
}
#[test]
fn http_delete_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("DELETE", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http delete --redirect-mode error {url}/foo",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}

View File

@ -176,6 +176,71 @@ fn http_get_full_response() {
assert_eq!(header["value"], "close");
}
#[test]
fn http_get_follows_redirect() {
let mut server = Server::new();
let _mock = server.mock("GET", "/bar").with_body("bar").create();
let _mock = server
.mock("GET", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!("http get {url}/foo", url = server.url()).as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_get_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http get --redirect-mode manual {url}/foo",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "foo");
}
#[test]
fn http_get_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http get --redirect-mode error {url}/foo",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}
// These tests require network access; they use badssl.com which is a Google-affiliated site for testing various SSL errors.
// Revisit this if these tests prove to be flaky or unstable.

View File

@ -39,3 +39,75 @@ fn http_head_failed_due_to_server_error() {
assert!(actual.err.contains("Bad request (400)"))
}
#[test]
fn http_head_follows_redirect() {
let mut server = Server::new();
let _mock = server
.mock("HEAD", "/bar")
.with_header("bar", "bar")
.create();
let _mock = server
.mock("HEAD", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http head {url}/foo | (where name == bar).0.value",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_head_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("HEAD", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http head --redirect-mode manual {url}/foo | (where name == location).0.value",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "/bar");
}
#[test]
fn http_head_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("HEAD", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http head --redirect-mode error {url}/foo",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}

View File

@ -76,3 +76,68 @@ fn http_patch_failed_due_to_unexpected_body() {
assert!(actual.err.contains("Cannot make request"))
}
#[test]
fn http_patch_follows_redirect() {
let mut server = Server::new();
let _mock = server.mock("GET", "/bar").with_body("bar").create();
let _mock = server
.mock("PATCH", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!("http patch {url}/foo patchbody", url = server.url()).as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_patch_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("PATCH", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http patch --redirect-mode manual {url}/foo patchbody",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "foo");
}
#[test]
fn http_patch_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("PATCH", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http patch --redirect-mode error {url}/foo patchbody",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}

View File

@ -112,3 +112,68 @@ fn http_post_json_list_is_success() {
mock.assert();
assert!(actual.out.is_empty())
}
#[test]
fn http_post_follows_redirect() {
let mut server = Server::new();
let _mock = server.mock("GET", "/bar").with_body("bar").create();
let _mock = server
.mock("POST", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!("http post {url}/foo postbody", url = server.url()).as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_post_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("POST", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http post --redirect-mode manual {url}/foo postbody",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "foo");
}
#[test]
fn http_post_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("POST", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http post --redirect-mode error {url}/foo postbody",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}

View File

@ -76,3 +76,68 @@ fn http_put_failed_due_to_unexpected_body() {
assert!(actual.err.contains("Cannot make request"))
}
#[test]
fn http_put_follows_redirect() {
let mut server = Server::new();
let _mock = server.mock("GET", "/bar").with_body("bar").create();
let _mock = server
.mock("PUT", "/foo")
.with_status(301)
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!("http put {url}/foo putbody", url = server.url()).as_str()
));
assert_eq!(&actual.out, "bar");
}
#[test]
fn http_put_redirect_mode_manual() {
let mut server = Server::new();
let _mock = server
.mock("PUT", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http put --redirect-mode manual {url}/foo putbody",
url = server.url()
)
.as_str()
));
assert_eq!(&actual.out, "foo");
}
#[test]
fn http_put_redirect_mode_error() {
let mut server = Server::new();
let _mock = server
.mock("PUT", "/foo")
.with_status(301)
.with_body("foo")
.with_header("Location", "/bar")
.create();
let actual = nu!(pipeline(
format!(
"http put --redirect-mode error {url}/foo putbody",
url = server.url()
)
.as_str()
));
assert!(&actual.err.contains("nu::shell::network_failure"));
assert!(&actual.err.contains(
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
));
}

View File

@ -375,6 +375,19 @@ fn removes_symlink() {
});
}
#[test]
fn removes_symlink_pointing_to_directory() {
Playground::setup("rm_symlink_to_directory", |dirs, sandbox| {
sandbox.mkdir("test").symlink("test", "test_link");
nu!(cwd: sandbox.cwd(), "rm test_link");
assert!(!dirs.test().join("test_link").exists());
// The pointed directory should not be deleted.
assert!(dirs.test().join("test").exists());
});
}
#[test]
fn removes_file_after_cd() {
Playground::setup("rm_after_cd", |dirs, sandbox| {

View File

@ -139,14 +139,13 @@ fn failed_command_with_semicolon_will_not_execute_following_cmds() {
})
}
#[cfg(not(windows))]
#[test]
fn external_args_with_quoted() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo "foo=bar 'hi'"
nu --testbin cococo "foo=bar 'hi'"
"#
));
@ -187,14 +186,13 @@ fn external_arg_with_variable_name() {
})
}
#[cfg(not(windows))]
#[test]
fn external_command_escape_args() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo "\"abcd"
nu --testbin cococo "\"abcd"
"#
));
@ -308,13 +306,12 @@ fn can_run_batch_files_without_bat_extension() {
);
}
#[cfg(windows)]
#[test]
fn quotes_trimmed_when_shelling_out() {
// regression test for a bug where we weren't trimming quotes around string args before shelling out to cmd.exe
let actual = nu!(pipeline(
r#"
^echo "foo"
nu --testbin cococo "foo"
"#
));
@ -328,7 +325,7 @@ fn redirect_combine() {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
run-external --redirect-combine sh [-c 'echo Foo; echo >&2 Bar']
run-external --redirect-combine sh ...[-c 'echo Foo; echo >&2 Bar']
"#
));

View File

@ -22,3 +22,39 @@ fn table_to_xml_text_and_from_xml_text_back_into_table() {
assert_eq!(actual.out, "true");
}
#[test]
fn to_xml_error_unknown_column() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
{tag: a bad_column: b} | to xml
"#
));
assert!(actual.err.contains("Invalid column \"bad_column\""));
}
#[test]
fn to_xml_error_no_tag() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
{attributes: {a: b c: d}} | to xml
"#
));
assert!(actual.err.contains("Tag missing"));
}
#[test]
fn to_xml_error_tag_not_string() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
{tag: 1 attributes: {a: b c: d}} | to xml
"#
));
assert!(actual.err.contains("not a string"));
}

View File

@ -1,8 +1,8 @@
use nu_protocol::{
ast::Call,
ast::{Call, Expression},
engine::{EngineState, Stack, StateWorkingSet},
eval_const::eval_constant,
FromValue, ShellError,
FromValue, ShellError, Value,
};
use crate::eval_expression;
@ -34,6 +34,10 @@ pub trait CallExt {
starting_pos: usize,
) -> Result<Vec<T>, ShellError>;
fn rest_iter_flattened<F>(&self, start: usize, eval: F) -> Result<Vec<Value>, ShellError>
where
F: FnMut(&Expression) -> Result<Value, ShellError>;
fn opt<T: FromValue>(
&self,
engine_state: &EngineState,
@ -98,8 +102,9 @@ impl CallExt for Call {
) -> Result<Vec<T>, ShellError> {
let mut output = vec![];
for expr in self.positional_iter().skip(starting_pos) {
let result = eval_expression(engine_state, stack, expr)?;
for result in self.rest_iter_flattened(starting_pos, |expr| {
eval_expression(engine_state, stack, expr)
})? {
output.push(FromValue::from_value(result)?);
}
@ -113,14 +118,36 @@ impl CallExt for Call {
) -> Result<Vec<T>, ShellError> {
let mut output = vec![];
for expr in self.positional_iter().skip(starting_pos) {
let result = eval_constant(working_set, expr)?;
for result in
self.rest_iter_flattened(starting_pos, |expr| eval_constant(working_set, expr))?
{
output.push(FromValue::from_value(result)?);
}
Ok(output)
}
fn rest_iter_flattened<F>(&self, start: usize, mut eval: F) -> Result<Vec<Value>, ShellError>
where
F: FnMut(&Expression) -> Result<Value, ShellError>,
{
let mut output = Vec::new();
for (expr, spread) in self.rest_iter(start) {
let result = eval(expr)?;
if spread {
match result {
Value::List { mut vals, .. } => output.append(&mut vals),
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
}
} else {
output.push(result);
}
}
Ok(output)
}
fn opt<T: FromValue>(
&self,
engine_state: &EngineState,

View File

@ -1,9 +1,9 @@
use crate::{current_dir_str, get_full_help};
use crate::{call_ext::CallExt, current_dir_str, get_full_help};
use nu_path::expand_path_with;
use nu_protocol::{
ast::{
Argument, Assignment, Block, Call, Expr, Expression, PathMember, PipelineElement,
Redirection,
Argument, Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember,
PipelineElement, Redirection,
},
engine::{Closure, EngineState, Stack},
eval_base::Eval,
@ -66,11 +66,11 @@ pub fn eval_call(
if let Some(rest_positional) = decl.signature().rest_positional {
let mut rest_items = vec![];
for arg in call.positional_iter().skip(
for result in call.rest_iter_flattened(
decl.signature().required_positional.len()
+ decl.signature().optional_positional.len(),
) {
let result = eval_expression(engine_state, caller_stack, arg)?;
|expr| eval_expression(engine_state, caller_stack, expr),
)? {
rest_items.push(result);
}
@ -182,7 +182,7 @@ fn eval_external(
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
args: &[Expression],
args: &[ExternalArgument],
input: PipelineData,
redirect_target: RedirectTarget,
is_subexpression: bool,
@ -198,7 +198,10 @@ fn eval_external(
call.add_positional(head.clone());
for arg in args {
call.add_positional(arg.clone())
match arg {
ExternalArgument::Regular(expr) => call.add_positional(expr.clone()),
ExternalArgument::Spread(expr) => call.add_spread(expr.clone()),
}
}
match redirect_target {
@ -947,7 +950,7 @@ impl Eval for EvalRuntime {
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
args: &[Expression],
args: &[ExternalArgument],
is_subexpression: bool,
_: Span,
) -> Result<Value, ShellError> {
@ -1099,7 +1102,18 @@ impl Eval for EvalRuntime {
.get_block(block_id)
.captures
.iter()
.map(|&id| stack.get_var(id, span).map(|var| (id, var)))
.map(|&id| {
stack
.get_var(id, span)
.or_else(|_| {
engine_state
.get_var(id)
.const_val
.clone()
.ok_or(ShellError::VariableNotFoundAtRuntime { span })
})
.map(|var| (id, var))
})
.collect::<Result<_, _>>()?;
Ok(Value::closure(Closure { block_id, captures }, span))

View File

@ -20,7 +20,7 @@ nu-table = { path = "../nu-table", version = "0.88.2" }
nu-json = { path = "../nu-json", version = "0.88.2" }
nu-utils = { path = "../nu-utils", version = "0.88.2" }
terminal_size = "0.2"
terminal_size = "0.3"
strip-ansi-escapes = "0.2.0"
crossterm = "0.27"
ratatui = "0.23"

View File

@ -15,7 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
reedline = { version = "0.27" }
crossbeam-channel = "0.5.8"
lsp-types = "0.94.1"
lsp-types = "0.95.0"
lsp-server = "0.7.5"
miette = "5.10"
ropey = "1.6.1"

View File

@ -1,6 +1,6 @@
use nu_protocol::ast::{
Block, Expr, Expression, ImportPatternMember, MatchPattern, PathMember, Pattern, Pipeline,
PipelineElement, RecordItem,
Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, MatchPattern,
PathMember, Pattern, Pipeline, PipelineElement, RecordItem,
};
use nu_protocol::{engine::StateWorkingSet, Span};
use nu_protocol::{DeclId, VarId};
@ -193,17 +193,28 @@ pub fn flatten_expression(
}
let mut args = vec![];
for positional in call.positional_iter() {
let flattened = flatten_expression(working_set, positional);
args.extend(flattened);
}
for named in call.named_iter() {
if named.0.span.end != 0 {
// Ignore synthetic flags
args.push((named.0.span, FlatShape::Flag));
}
if let Some(expr) = &named.2 {
args.extend(flatten_expression(working_set, expr));
for arg in &call.arguments {
match arg {
Argument::Positional(positional) | Argument::Unknown(positional) => {
let flattened = flatten_expression(working_set, positional);
args.extend(flattened);
}
Argument::Named(named) => {
if named.0.span.end != 0 {
// Ignore synthetic flags
args.push((named.0.span, FlatShape::Flag));
}
if let Some(expr) = &named.2 {
args.extend(flatten_expression(working_set, expr));
}
}
Argument::Spread(expr) => {
args.push((
Span::new(expr.span.start - 3, expr.span.start),
FlatShape::Operator,
));
args.extend(flatten_expression(working_set, expr));
}
}
}
// sort these since flags and positional args can be intermixed
@ -231,15 +242,24 @@ pub fn flatten_expression(
for arg in args {
//output.push((*arg, FlatShape::ExternalArg));
match arg {
Expression {
expr: Expr::String(..),
span,
..
} => {
output.push((*span, FlatShape::ExternalArg));
}
_ => {
output.extend(flatten_expression(working_set, arg));
ExternalArgument::Regular(expr) => match expr {
Expression {
expr: Expr::String(..),
span,
..
} => {
output.push((*span, FlatShape::ExternalArg));
}
_ => {
output.extend(flatten_expression(working_set, expr));
}
},
ExternalArgument::Spread(expr) => {
output.push((
Span::new(expr.span.start - 3, expr.span.start),
FlatShape::Operator,
));
output.extend(flatten_expression(working_set, expr));
}
}
}

View File

@ -106,6 +106,7 @@ impl Command for KnownExternal {
}
}
Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
Argument::Spread(args) => extern_call.add_spread(args.clone()),
}
}

View File

@ -12,9 +12,9 @@ use nu_engine::DIR_VAR_PARSER_INFO;
use nu_protocol::{
ast::{
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, MatchPattern, Math,
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
RecordItem,
ExternalArgument, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember,
MatchPattern, Math, Operator, PathMember, Pattern, Pipeline, PipelineElement,
RangeInclusion, RangeOperator, RecordItem,
},
engine::StateWorkingSet,
eval_const::eval_constant,
@ -266,13 +266,22 @@ pub fn check_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> O
}
}
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
let contents = working_set.get_span_contents(span);
if contents.starts_with(b"$") || contents.starts_with(b"(") {
parse_dollar_expr(working_set, span)
ExternalArgument::Regular(parse_dollar_expr(working_set, span))
} else if contents.starts_with(b"[") {
parse_list_expression(working_set, span, &SyntaxShape::Any)
ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any))
} else if contents.len() > 3
&& contents.starts_with(b"...")
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
{
ExternalArgument::Spread(parse_value(
working_set,
Span::new(span.start + 3, span.end),
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
))
} else {
// Eval stage trims the quotes, so we don't have to do the same thing when parsing.
let contents = if contents.starts_with(b"\"") {
@ -285,12 +294,12 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expressi
String::from_utf8_lossy(contents).to_string()
};
Expression {
ExternalArgument::Regular(Expression {
expr: Expr::String(contents),
span,
ty: Type::String,
custom_completion: None,
}
})
}
}
@ -978,6 +987,49 @@ pub fn parse_internal_call(
continue;
}
{
let contents = working_set.get_span_contents(spans[spans_idx]);
if contents.len() > 3
&& contents.starts_with(b"...")
&& (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
{
if signature.rest_positional.is_none() && !signature.allows_unknown_args {
working_set.error(ParseError::UnexpectedSpreadArg(
signature.call_signature(),
arg_span,
));
call.add_positional(Expression::garbage(arg_span));
} else if positional_idx < signature.required_positional.len() {
working_set.error(ParseError::MissingPositional(
signature.required_positional[positional_idx].name.clone(),
Span::new(spans[spans_idx].start, spans[spans_idx].start),
signature.call_signature(),
));
call.add_positional(Expression::garbage(arg_span));
} else {
let rest_shape = match &signature.rest_positional {
Some(arg) => arg.shape.clone(),
None => SyntaxShape::Any,
};
// Parse list of arguments to be spread
let args = parse_value(
working_set,
Span::new(arg_span.start + 3, arg_span.end),
&SyntaxShape::List(Box::new(rest_shape)),
);
call.add_spread(args);
// Let the parser know that it's parsing rest arguments now
positional_idx =
signature.required_positional.len() + signature.optional_positional.len();
}
spans_idx += 1;
continue;
}
}
// Parse a positional arg if there is one
if let Some(positional) = signature.get_positional(positional_idx) {
let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx);
@ -5272,15 +5324,43 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression
idx += 1;
if idx == tokens.len() {
working_set.error(ParseError::Expected("record", span));
return garbage(span);
working_set.error(ParseError::Expected(
"':'",
Span::new(curr_span.end, curr_span.end),
));
output.push(RecordItem::Pair(
garbage(curr_span),
garbage(Span::new(curr_span.end, curr_span.end)),
));
break;
}
let colon = working_set.get_span_contents(tokens[idx].span);
let colon_span = tokens[idx].span;
let colon = working_set.get_span_contents(colon_span);
idx += 1;
if idx == tokens.len() || colon != b":" {
//FIXME: need better error
working_set.error(ParseError::Expected("record", span));
return garbage(span);
if colon != b":" {
working_set.error(ParseError::Expected(
"':'",
Span::new(colon_span.start, colon_span.start),
));
output.push(RecordItem::Pair(
field,
garbage(Span::new(
colon_span.start,
tokens[tokens.len() - 1].span.end,
)),
));
break;
}
if idx == tokens.len() {
working_set.error(ParseError::Expected(
"value for record field",
Span::new(colon_span.end, colon_span.end),
));
output.push(RecordItem::Pair(
garbage(Span::new(curr_span.start, colon_span.end)),
garbage(Span::new(colon_span.end, tokens[tokens.len() - 1].span.end)),
));
break;
}
let value = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any);
idx += 1;
@ -5857,22 +5937,27 @@ pub fn discover_captures_in_expr(
}
}
for named in call.named_iter() {
if let Some(arg) = &named.2 {
discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
for arg in &call.arguments {
match arg {
Argument::Named(named) => {
if let Some(arg) = &named.2 {
discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
}
}
Argument::Positional(expr)
| Argument::Unknown(expr)
| Argument::Spread(expr) => {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
}
}
}
for positional in call.positional_iter() {
discover_captures_in_expr(working_set, positional, seen, seen_blocks, output)?;
}
}
Expr::CellPath(_) => {}
Expr::DateTime(_) => {}
Expr::ExternalCall(head, exprs, _) => {
Expr::ExternalCall(head, args, _) => {
discover_captures_in_expr(working_set, head, seen, seen_blocks, output)?;
for expr in exprs {
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
}
}
@ -6086,11 +6171,8 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
default_value: None,
});
let mut expr = expr.clone();
expr.replace_in_variable(working_set, var_id);
let block = Block {
pipelines: vec![Pipeline::from_vec(vec![expr])],
pipelines: vec![Pipeline::from_vec(vec![expr.clone()])],
signature: Box::new(signature),
..Default::default()
};

View File

@ -1,4 +1,4 @@
use nu_engine::eval_expression;
use nu_engine::{eval_expression, CallExt};
use nu_protocol::{
ast::Call,
engine::{EngineState, Stack},
@ -33,10 +33,8 @@ impl EvaluatedCall {
engine_state: &EngineState,
stack: &mut Stack,
) -> Result<Self, ShellError> {
let positional = call
.positional_iter()
.map(|expr| eval_expression(engine_state, stack, expr))
.collect::<Result<Vec<Value>, ShellError>>()?;
let positional =
call.rest_iter_flattened(0, |expr| eval_expression(engine_state, stack, expr))?;
let mut named = Vec::with_capacity(call.named_len());
for (string, _, expr) in call.named_iter() {

View File

@ -10,6 +10,7 @@ pub enum Argument {
Positional(Expression),
Named((Spanned<String>, Option<Spanned<String>>, Option<Expression>)),
Unknown(Expression), // unknown argument used in "fall-through" signatures
Spread(Expression), // a list spread to fill in rest arguments
}
impl Argument {
@ -30,10 +31,17 @@ impl Argument {
Span::new(start, end)
}
Argument::Unknown(e) => e.span,
Argument::Spread(e) => e.span,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExternalArgument {
Regular(Expression),
Spread(Expression),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Call {
/// identifier of the declaration to call
@ -85,6 +93,7 @@ impl Call {
Argument::Named(named) => Some(named),
Argument::Positional(_) => None,
Argument::Unknown(_) => None,
Argument::Spread(_) => None,
})
}
@ -96,6 +105,7 @@ impl Call {
Argument::Named(named) => Some(named),
Argument::Positional(_) => None,
Argument::Unknown(_) => None,
Argument::Spread(_) => None,
})
}
@ -118,26 +128,45 @@ impl Call {
self.arguments.push(Argument::Unknown(unknown));
}
pub fn add_spread(&mut self, args: Expression) {
self.arguments.push(Argument::Spread(args));
}
pub fn positional_iter(&self) -> impl Iterator<Item = &Expression> {
self.arguments.iter().filter_map(|arg| match arg {
Argument::Named(_) => None,
Argument::Positional(positional) => Some(positional),
Argument::Unknown(unknown) => Some(unknown),
})
self.arguments
.iter()
.take_while(|arg| match arg {
Argument::Spread(_) => false, // Don't include positional arguments given to rest parameter
_ => true,
})
.filter_map(|arg| match arg {
Argument::Named(_) => None,
Argument::Positional(positional) => Some(positional),
Argument::Unknown(unknown) => Some(unknown),
Argument::Spread(_) => None,
})
}
pub fn positional_iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
self.arguments.iter_mut().filter_map(|arg| match arg {
Argument::Named(_) => None,
Argument::Positional(positional) => Some(positional),
Argument::Unknown(unknown) => Some(unknown),
})
self.arguments
.iter_mut()
.take_while(|arg| match arg {
Argument::Spread(_) => false, // Don't include positional arguments given to rest parameter
_ => true,
})
.filter_map(|arg| match arg {
Argument::Named(_) => None,
Argument::Positional(positional) => Some(positional),
Argument::Unknown(unknown) => Some(unknown),
Argument::Spread(_) => None,
})
}
pub fn positional_nth(&self, i: usize) -> Option<&Expression> {
self.positional_iter().nth(i)
}
// TODO this method is never used. Delete?
pub fn positional_nth_mut(&mut self, i: usize) -> Option<&mut Expression> {
self.positional_iter_mut().nth(i)
}
@ -146,6 +175,24 @@ impl Call {
self.positional_iter().count()
}
/// Returns every argument to the rest parameter, as well as whether each argument
/// is spread or a normal positional argument (true for spread, false for normal)
pub fn rest_iter(&self, start: usize) -> impl Iterator<Item = (&Expression, bool)> {
// todo maybe rewrite to be more elegant or something
let args = self
.arguments
.iter()
.filter_map(|arg| match arg {
Argument::Named(_) => None,
Argument::Positional(positional) => Some((positional, false)),
Argument::Unknown(unknown) => Some((unknown, false)),
Argument::Spread(args) => Some((args, true)),
})
.collect::<Vec<_>>();
let spread_start = args.iter().position(|(_, spread)| *spread).unwrap_or(start);
args.into_iter().skip(start.min(spread_start))
}
pub fn get_parser_info(&self, name: &str) -> Option<&Expression> {
self.parser_info.get(name)
}

View File

@ -1,7 +1,10 @@
use chrono::FixedOffset;
use serde::{Deserialize, Serialize};
use super::{Call, CellPath, Expression, FullCellPath, MatchPattern, Operator, RangeOperator};
use super::{
Call, CellPath, Expression, ExternalArgument, FullCellPath, MatchPattern, Operator,
RangeOperator,
};
use crate::{ast::ImportPattern, BlockId, Signature, Span, Spanned, Unit, VarId};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -19,7 +22,7 @@ pub enum Expr {
Var(VarId),
VarDecl(VarId),
Call(Box<Call>),
ExternalCall(Box<Expression>, Vec<Expression>, bool), // head, args, is_subexpression
ExternalCall(Box<Expression>, Vec<ExternalArgument>, bool), // head, args, is_subexpression
Operator(Operator),
RowCondition(BlockId),
UnaryNot(Box<Expression>),

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use super::{Expr, RecordItem};
use super::{Argument, Expr, ExternalArgument, RecordItem};
use crate::ast::ImportPattern;
use crate::DeclId;
use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID};
@ -162,15 +162,21 @@ impl Expression {
Expr::Binary(_) => false,
Expr::Bool(_) => false,
Expr::Call(call) => {
for positional in call.positional_iter() {
if positional.has_in_variable(working_set) {
return true;
}
}
for named in call.named_iter() {
if let Some(expr) = &named.2 {
if expr.has_in_variable(working_set) {
return true;
for arg in &call.arguments {
match arg {
Argument::Positional(expr)
| Argument::Unknown(expr)
| Argument::Spread(expr) => {
if expr.has_in_variable(working_set) {
return true;
}
}
Argument::Named(named) => {
if let Some(expr) = &named.2 {
if expr.has_in_variable(working_set) {
return true;
}
}
}
}
}
@ -182,8 +188,8 @@ impl Expression {
if head.has_in_variable(working_set) {
return true;
}
for arg in args {
if arg.has_in_variable(working_set) {
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
if expr.has_in_variable(working_set) {
return true;
}
}
@ -301,204 +307,6 @@ impl Expression {
}
}
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
match &mut self.expr {
Expr::BinaryOp(left, _, right) => {
left.replace_in_variable(working_set, new_var_id);
right.replace_in_variable(working_set, new_var_id);
}
Expr::UnaryNot(expr) => {
expr.replace_in_variable(working_set, new_var_id);
}
Expr::Block(block_id) => {
let block = working_set.get_block(*block_id);
let new_expr = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)
} else {
None
}
} else {
None
};
let block = working_set.get_block_mut(*block_id);
if let Some(new_expr) = new_expr {
if let Some(pipeline) = block.pipelines.get_mut(0) {
if let Some(expr) = pipeline.elements.get_mut(0) {
*expr = new_expr
}
}
}
block.captures = block
.captures
.iter()
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
.collect();
}
Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)
} else {
None
}
} else {
None
};
let block = working_set.get_block_mut(*block_id);
if let Some(new_element) = new_element {
if let Some(pipeline) = block.pipelines.get_mut(0) {
if let Some(element) = pipeline.elements.get_mut(0) {
*element = new_element
}
}
}
block.captures = block
.captures
.iter()
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
.collect();
}
Expr::Binary(_) => {}
Expr::Bool(_) => {}
Expr::Call(call) => {
for positional in call.positional_iter_mut() {
positional.replace_in_variable(working_set, new_var_id);
}
for named in call.named_iter_mut() {
if let Some(expr) = &mut named.2 {
expr.replace_in_variable(working_set, new_var_id)
}
}
}
Expr::CellPath(_) => {}
Expr::DateTime(_) => {}
Expr::ExternalCall(head, args, _) => {
head.replace_in_variable(working_set, new_var_id);
for arg in args {
arg.replace_in_variable(working_set, new_var_id)
}
}
Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Float(_) => {}
Expr::FullCellPath(full_cell_path) => {
full_cell_path
.head
.replace_in_variable(working_set, new_var_id);
}
Expr::ImportPattern(_) => {}
Expr::Overlay(_) => {}
Expr::Garbage => {}
Expr::Nothing => {}
Expr::GlobPattern(_) => {}
Expr::Int(_) => {}
Expr::MatchBlock(_) => {}
Expr::Keyword(_, _, expr) => expr.replace_in_variable(working_set, new_var_id),
Expr::List(list) => {
for l in list {
l.replace_in_variable(working_set, new_var_id)
}
}
Expr::Operator(_) => {}
Expr::Range(left, middle, right, ..) => {
if let Some(left) = left {
left.replace_in_variable(working_set, new_var_id)
}
if let Some(middle) = middle {
middle.replace_in_variable(working_set, new_var_id)
}
if let Some(right) = right {
right.replace_in_variable(working_set, new_var_id)
}
}
Expr::Record(items) => {
for item in items {
match item {
RecordItem::Pair(field_name, field_value) => {
field_name.replace_in_variable(working_set, new_var_id);
field_value.replace_in_variable(working_set, new_var_id);
}
RecordItem::Spread(_, record) => {
record.replace_in_variable(working_set, new_var_id);
}
}
}
}
Expr::Signature(_) => {}
Expr::String(_) => {}
Expr::StringInterpolation(items) => {
for i in items {
i.replace_in_variable(working_set, new_var_id)
}
}
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id);
let new_element = if let Some(pipeline) = block.pipelines.first() {
if let Some(element) = pipeline.elements.first() {
let mut new_element = element.clone();
new_element.replace_in_variable(working_set, new_var_id);
Some(new_element)
} else {
None
}
} else {
None
};
let block = working_set.get_block_mut(*block_id);
if let Some(new_element) = new_element {
if let Some(pipeline) = block.pipelines.get_mut(0) {
if let Some(element) = pipeline.elements.get_mut(0) {
*element = new_element
}
}
}
block.captures = block
.captures
.iter()
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
.collect();
}
Expr::Table(headers, cells) => {
for header in headers {
header.replace_in_variable(working_set, new_var_id)
}
for row in cells {
for cell in row.iter_mut() {
cell.replace_in_variable(working_set, new_var_id)
}
}
}
Expr::ValueWithUnit(expr, _) => expr.replace_in_variable(working_set, new_var_id),
Expr::Var(x) => {
if *x == IN_VARIABLE_ID {
*x = new_var_id
}
}
Expr::VarDecl(_) => {}
Expr::Spread(expr) => expr.replace_in_variable(working_set, new_var_id),
}
}
pub fn replace_span(
&mut self,
working_set: &mut StateWorkingSet,
@ -544,12 +352,18 @@ impl Expression {
if replaced.contains_span(call.head) {
call.head = new_span;
}
for positional in call.positional_iter_mut() {
positional.replace_span(working_set, replaced, new_span);
}
for named in call.named_iter_mut() {
if let Some(expr) = &mut named.2 {
expr.replace_span(working_set, replaced, new_span)
for arg in call.arguments.iter_mut() {
match arg {
Argument::Positional(expr)
| Argument::Unknown(expr)
| Argument::Spread(expr) => {
expr.replace_span(working_set, replaced, new_span);
}
Argument::Named(named) => {
if let Some(expr) = &mut named.2 {
expr.replace_span(working_set, replaced, new_span);
}
}
}
}
}
@ -557,8 +371,8 @@ impl Expression {
Expr::DateTime(_) => {}
Expr::ExternalCall(head, args, _) => {
head.replace_span(working_set, replaced, new_span);
for arg in args {
arg.replace_span(working_set, replaced, new_span)
for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args {
expr.replace_span(working_set, replaced, new_span);
}
}
Expr::Filepath(_) => {}

View File

@ -1,4 +1,4 @@
use crate::{ast::Expression, engine::StateWorkingSet, Span, VarId};
use crate::{ast::Expression, engine::StateWorkingSet, Span};
use serde::{Deserialize, Serialize};
use std::ops::{Index, IndexMut};
@ -88,30 +88,6 @@ impl PipelineElement {
}
}
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
match self {
PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression, _)
| PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression)
| PipelineElement::SameTargetRedirection {
cmd: (_, expression),
..
} => expression.replace_in_variable(working_set, new_var_id),
PipelineElement::SeparateRedirection {
out: (_, out_expr, _),
err: (_, err_expr, _),
} => {
if out_expr.has_in_variable(working_set) {
out_expr.replace_in_variable(working_set, new_var_id)
}
if err_expr.has_in_variable(working_set) {
err_expr.replace_in_variable(working_set, new_var_id)
}
}
}
}
pub fn replace_span(
&mut self,
working_set: &mut StateWorkingSet,

View File

@ -1,7 +1,7 @@
use crate::{
ast::{
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, Math,
Operator, RecordItem,
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression,
ExternalArgument, Math, Operator, RecordItem,
},
Range, Record, ShellError, Span, Value, VarId,
};
@ -319,7 +319,7 @@ pub trait Eval {
state: Self::State<'_>,
mut_state: &mut Self::MutState,
head: &Expression,
args: &[Expression],
args: &[ExternalArgument],
is_subexpression: bool,
span: Span,
) -> Result<Value, ShellError>;

View File

@ -1,5 +1,5 @@
use crate::{
ast::{Assignment, Block, Call, Expr, Expression, PipelineElement},
ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument, PipelineElement},
engine::{EngineState, StateWorkingSet},
eval_base::Eval,
record, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value, VarId,
@ -317,7 +317,7 @@ impl Eval for EvalConst {
_: &StateWorkingSet,
_: &mut (),
_: &Expression,
_: &[Expression],
_: &[ExternalArgument],
_: bool,
span: Span,
) -> Result<Value, ShellError> {

View File

@ -484,9 +484,12 @@ pub enum ParseError {
#[label("...and here")] Option<Span>,
),
#[error("Unexpected spread operator outside list")]
#[diagnostic(code(nu::parser::unexpected_spread_operator))]
UnexpectedSpread(#[label("Spread operator not allowed here")] Span),
#[error("This command does not have a ...rest parameter")]
#[diagnostic(
code(nu::parser::unexpected_spread_arg),
help("To spread arguments, the command needs to define a multi-positional parameter in its signature, such as ...rest")
)]
UnexpectedSpreadArg(String, #[label = "unexpected spread argument"] Span),
}
impl ParseError {
@ -573,7 +576,7 @@ impl ParseError {
ParseError::InvalidLiteral(_, _, s) => *s,
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
ParseError::RedirectionInLetMut(s, _) => *s,
ParseError::UnexpectedSpread(s) => *s,
ParseError::UnexpectedSpreadArg(_, s) => *s,
}
}
}

View File

@ -861,10 +861,13 @@ pub fn print_if_stream(
// NOTE: currently we don't need anything from stderr
// so we just consume and throw away `stderr_stream` to make sure the pipe doesn't fill up
thread::Builder::new()
.name("stderr consumer".to_string())
.spawn(move || stderr_stream.map(|x| x.into_bytes()))
.expect("could not create thread");
if let Some(stderr_stream) = stderr_stream {
thread::Builder::new()
.name("stderr consumer".to_string())
.spawn(move || stderr_stream.into_bytes())
.expect("could not create thread");
}
if let Some(stream) = stream {
for s in stream {
let s_live = s?;

View File

@ -1281,15 +1281,15 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
span: Span,
},
/// Tried spreading a non-list inside a list.
/// Tried spreading a non-list inside a list or command call.
///
/// ## Resolution
///
/// Only lists can be spread inside lists. Try converting the value to a list before spreading.
/// Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.
#[error("Not a list")]
#[diagnostic(
code(nu::shell::cannot_spread_as_list),
help("Only lists can be spread inside lists. Try converting the value to a list before spreading")
help("Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.")
)]
CannotSpreadAsList {
#[label = "cannot spread value"]

View File

@ -90,127 +90,6 @@ export def --env "path add" [
}
}
# print a command name as dimmed and italic
def pretty-command [] {
let command = $in
return $"(ansi default_dimmed)(ansi default_italic)($command)(ansi reset)"
}
# give a hint error when the clip command is not available on the system
def check-clipboard [
clipboard: string # the clipboard command name
--system: string # some information about the system running, for better error
] {
if (which $clipboard | is-empty) {
error make --unspanned {
msg: $"(ansi red)clipboard_not_found(ansi reset):
you are running ($system)
but
the ($clipboard | pretty-command) clipboard command was not found on your system."
}
}
}
# put the end of a pipe into the system clipboard.
#
# Dependencies:
# - xclip on linux x11
# - wl-copy on linux wayland
# - clip.exe on windows
# - termux-api on termux
#
# Examples:
# put a simple string to the clipboard, will be stripped to remove ANSI sequences
# >_ "my wonderful string" | clip
# my wonderful string
# saved to clipboard (stripped)
#
# put a whole table to the clipboard
# >_ ls *.toml | clip
# ╭───┬─────────────────────┬──────┬────────┬───────────────╮
# │ # │ name │ type │ size │ modified │
# ├───┼─────────────────────┼──────┼────────┼───────────────┤
# │ 0 │ Cargo.toml │ file │ 5.0 KB │ 3 minutes ago │
# │ 1 │ Cross.toml │ file │ 363 B │ 2 weeks ago │
# │ 2 │ rust-toolchain.toml │ file │ 1.1 KB │ 2 weeks ago │
# ╰───┴─────────────────────┴──────┴────────┴───────────────╯
#
# saved to clipboard
#
# put huge structured data in the clipboard, but silently
# >_ open Cargo.toml --raw | from toml | clip --silent
#
# when the clipboard system command is not installed
# >_ "mm this is fishy..." | clip
# Error:
# × clipboard_not_found:
# │ you are using xorg on linux
# │ but
# │ the xclip clipboard command was not found on your system.
export def clip [
--silent # do not print the content of the clipboard to the standard output
--no-notify # do not throw a notification (only on linux)
--no-strip # do not strip ANSI escape sequences from a string
--expand (-e) # auto-expand the data given as input
--codepage (-c): int # the id of the codepage to use (only on Windows), see https://en.wikipedia.org/wiki/Windows_code_page, e.g. 65001 is for UTF-8
] {
let input = $in
print $"Warning: (char -u 26a0) (ansi yellow_bold)deprecated_command(ansi reset)"
print "| the `std clip` command is deprecated and will be removed in Nushell 0.89"
print ""
print $"(ansi cyan)help(ansi reset): please use (ansi {fg: cyan, attr: du})[`modules/system clip`]\(https://github.com/nushell/nu_scripts/tree/main/modules#system\)(ansi reset)"
let input = $input
| if $expand { table --expand } else { table }
| into string
| if $no_strip {} else { ansi strip }
match $nu.os-info.name {
"linux" => {
if ($env.WAYLAND_DISPLAY? | is-empty) {
check-clipboard xclip --system $"('xorg' | pretty-command) on linux"
$input | xclip -sel clip
} else {
check-clipboard wl-copy --system $"('wayland' | pretty-command) on linux"
$input | wl-copy
}
},
"windows" => {
if $codepage != null {
chcp $codepage
}
check-clipboard clip.exe --system "Windows"
$input | clip.exe
},
"macos" => {
check-clipboard pbcopy --system "MacOS"
$input | pbcopy
},
"android" => {
check-clipboard termux-clipboard-set --system "Termux"
$input | termux-clipboard-set
},
_ => {
error make --unspanned {
msg: $"(ansi red)unknown_operating_system(ansi reset):
'($nu.os-info.name)' is not supported by the ('clip' | pretty-command) command.
please open a feature request in the [issue tracker](char lparen)https://github.com/nushell/nushell/issues/new/choose(char rparen) to add your operating system to the standard library."
}
},
}
if not $silent {
print $input
print $"(ansi white_italic)(ansi white_dimmed)saved to clipboard(ansi reset)"
}
if (not $no_notify) and ($nu.os-info.name == linux) {
notify-send "std clip" "saved to clipboard"
}
}
# convert an integer amount of nanoseconds to a real duration
def "from ns" [] {
[$in "ns"] | str join | into duration

View File

@ -15,7 +15,7 @@ bench = false
[dependencies]
libc = "0.2"
log = "0.4"
sysinfo = "0.29"
sysinfo = "0.30"
[target.'cfg(target_family = "unix")'.dependencies]
nix = { version = "0.27", default-features = false, features = ["fs", "term", "process", "signal"] }
@ -28,36 +28,20 @@ libproc = "0.14"
mach2 = "0.4"
[target.'cfg(target_os = "windows")'.dependencies]
chrono = { version = "0.4", default-features = false }
chrono = { version = "0.4", default-features = false, features = ["clock"] }
ntapi = "0.4"
once_cell = "1.18"
winapi = { version = "0.3", features = [
"tlhelp32",
"fileapi",
"handleapi",
"ifdef",
"ioapiset",
"minwindef",
"pdh",
"psapi",
"synchapi",
"sysinfoapi",
"winbase",
"winerror",
"winioctl",
"winnt",
"oleauto",
"wbemcli",
"rpcdce",
"combaseapi",
"objidl",
"powerbase",
"netioapi",
"lmcons",
"lmaccess",
"lmapibuf",
"memoryapi",
"shellapi",
"std",
"securitybaseapi",
] }
windows = { version = "0.52", features = [
"Wdk_System_SystemServices",
"Wdk_System_Threading",
"Win32_Foundation",
"Win32_Security",
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_ProcessStatus",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_UI_Shell",
]}

View File

@ -1,5 +1,3 @@
use sysinfo::SystemExt;
pub fn get_os_name() -> &'static str {
std::env::consts::OS
}
@ -13,8 +11,7 @@ pub fn get_os_family() -> &'static str {
}
pub fn get_kernel_version() -> String {
let sys = sysinfo::System::new();
match sys.kernel_version() {
match sysinfo::System::kernel_version() {
Some(v) => v,
None => "unknown".to_string(),
}

View File

@ -4,13 +4,10 @@
use chrono::offset::TimeZone;
use chrono::{Local, NaiveDate};
use libc::c_void;
use ntapi::ntpebteb::PEB;
use ntapi::ntpsapi::{
NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation,
ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION,
};
use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS};
use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32};
use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS;
use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32};
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::collections::HashMap;
@ -22,33 +19,48 @@ use std::ptr;
use std::ptr::null_mut;
use std::thread;
use std::time::{Duration, Instant};
use winapi::shared::basetsd::SIZE_T;
use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG};
use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING};
use winapi::shared::ntstatus::{
STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH,
use windows::core::{PCWSTR, PWSTR};
use windows::Wdk::System::SystemServices::RtlGetVersion;
use windows::Wdk::System::Threading::{
NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation,
ProcessWow64Information, PROCESSINFOCLASS,
};
use winapi::um::handleapi::CloseHandle;
use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx};
use winapi::um::processthreadsapi::{
GetCurrentProcess, GetPriorityClass, GetProcessTimes, OpenProcess, OpenProcessToken,
use windows::Win32::Foundation::{
CloseHandle, LocalFree, FALSE, FILETIME, HANDLE, HLOCAL, HMODULE, MAX_PATH, PSID,
STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING,
};
use winapi::um::psapi::{
use windows::Win32::Security::{
AdjustTokenPrivileges, GetTokenInformation, LookupAccountSidW, LookupPrivilegeValueW,
TokenGroups, TokenUser, SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED, SID, SID_NAME_USE,
TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, TOKEN_PRIVILEGES, TOKEN_QUERY, TOKEN_USER,
};
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};
use windows::Win32::System::Memory::{VirtualQueryEx, MEMORY_BASIC_INFORMATION};
use windows::Win32::System::ProcessStatus::{
GetModuleBaseNameW, GetProcessMemoryInfo, K32EnumProcesses, PROCESS_MEMORY_COUNTERS,
PROCESS_MEMORY_COUNTERS_EX,
};
use winapi::um::securitybaseapi::{AdjustTokenPrivileges, GetTokenInformation};
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};
use winapi::um::winbase::{GetProcessIoCounters, LookupAccountSidW, LookupPrivilegeValueW};
use winapi::um::winnt::{
TokenGroups, TokenUser, HANDLE, IO_COUNTERS, MEMORY_BASIC_INFORMATION,
PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PSID, RTL_OSVERSIONINFOEXW, SE_DEBUG_NAME,
SE_PRIVILEGE_ENABLED, SID, TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, TOKEN_PRIVILEGES,
TOKEN_QUERY, TOKEN_USER,
use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW;
use windows::Win32::System::Threading::{
GetCurrentProcess, GetPriorityClass, GetProcessIoCounters, GetProcessTimes, OpenProcess,
OpenProcessToken, IO_COUNTERS, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION,
PROCESS_VM_READ,
};
use windows::Win32::UI::Shell::CommandLineToArgvW;
pub struct ProcessInfo {
pub pid: i32,
pub command: String,
@ -235,7 +247,7 @@ pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo>
}
unsafe {
CloseHandle(handle);
let _ = CloseHandle(handle);
}
}
}
@ -249,32 +261,20 @@ fn set_privilege() -> bool {
let handle = GetCurrentProcess();
let mut token: HANDLE = zeroed();
let ret = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, &mut token);
if ret == 0 {
if ret.is_err() {
return false;
}
let mut tps: TOKEN_PRIVILEGES = zeroed();
let se_debug_name: Vec<u16> = format!("{}\0", SE_DEBUG_NAME).encode_utf16().collect();
tps.PrivilegeCount = 1;
let ret = LookupPrivilegeValueW(
ptr::null(),
se_debug_name.as_ptr(),
&mut tps.Privileges[0].Luid,
);
if ret == 0 {
if LookupPrivilegeValueW(PCWSTR::null(), SE_DEBUG_NAME, &mut tps.Privileges[0].Luid)
.is_err()
{
return false;
}
tps.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
let ret = AdjustTokenPrivileges(
token,
FALSE,
&mut tps,
0,
ptr::null::<TOKEN_PRIVILEGES>() as *mut TOKEN_PRIVILEGES,
ptr::null::<u32>() as *mut u32,
);
if ret == 0 {
if AdjustTokenPrivileges(token, FALSE, Some(&tps), 0, None, None).is_err() {
return false;
}
@ -284,21 +284,21 @@ fn set_privilege() -> bool {
#[cfg_attr(tarpaulin, skip)]
fn get_pids() -> Vec<i32> {
let dword_size = size_of::<DWORD>();
let mut pids: Vec<DWORD> = Vec::with_capacity(10192);
let dword_size = size_of::<u32>();
let mut pids: Vec<u32> = Vec::with_capacity(10192);
let mut cb_needed = 0;
unsafe {
pids.set_len(10192);
let result = K32EnumProcesses(
pids.as_mut_ptr(),
(dword_size * pids.len()) as DWORD,
(dword_size * pids.len()) as u32,
&mut cb_needed,
);
if result == 0 {
if !result.as_bool() {
return Vec::new();
}
let pids_len = cb_needed / dword_size as DWORD;
let pids_len = cb_needed / dword_size as u32;
pids.set_len(pids_len as usize);
}
@ -311,18 +311,20 @@ fn get_ppid_threads() -> (HashMap<i32, i32>, HashMap<i32, i32>) {
let mut threads = HashMap::new();
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
let Ok(snapshot) = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) else {
return (ppids, threads);
};
let mut entry: PROCESSENTRY32 = zeroed();
entry.dwSize = size_of::<PROCESSENTRY32>() as u32;
let mut not_the_end = Process32First(snapshot, &mut entry);
while not_the_end != 0 {
while not_the_end.is_ok() {
ppids.insert(entry.th32ProcessID as i32, entry.th32ParentProcessID as i32);
threads.insert(entry.th32ProcessID as i32, entry.cntThreads as i32);
not_the_end = Process32Next(snapshot, &mut entry);
}
CloseHandle(snapshot);
let _ = CloseHandle(snapshot);
}
(ppids, threads)
@ -338,14 +340,14 @@ fn get_handle(pid: i32) -> Option<HANDLE> {
OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
pid as DWORD,
pid as u32,
)
};
}
.ok();
if handle.is_null() {
None
} else {
Some(handle)
match handle {
Some(h) if h.is_invalid() => None,
h => h,
}
}
@ -370,7 +372,7 @@ fn get_times(handle: HANDLE) -> Option<(u64, u64, u64, u64)> {
let sys = u64::from(sys.dwHighDateTime) << 32 | u64::from(sys.dwLowDateTime);
let user = u64::from(user.dwHighDateTime) << 32 | u64::from(user.dwLowDateTime);
if ret != 0 {
if ret.is_ok() {
Some((start, exit, sys, user))
} else {
None
@ -386,10 +388,10 @@ fn get_memory_info(handle: HANDLE) -> Option<MemoryInfo> {
handle,
&mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void
as *mut PROCESS_MEMORY_COUNTERS,
size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD,
size_of::<PROCESS_MEMORY_COUNTERS_EX>() as u32,
);
if ret != 0 {
if ret.is_ok() {
let info = MemoryInfo {
page_fault_count: u64::from(pmc.PageFaultCount),
peak_working_set_size: pmc.PeakWorkingSetSize as u64,
@ -412,15 +414,10 @@ fn get_memory_info(handle: HANDLE) -> Option<MemoryInfo> {
#[cfg_attr(tarpaulin, skip)]
fn get_command(handle: HANDLE) -> Option<String> {
unsafe {
let mut exe_buf = [0u16; MAX_PATH + 1];
let h_mod = std::ptr::null_mut();
let mut exe_buf = [0u16; MAX_PATH as usize + 1];
let h_mod = HMODULE::default();
let ret = GetModuleBaseNameW(
handle,
h_mod as _,
exe_buf.as_mut_ptr(),
MAX_PATH as DWORD + 1,
);
let ret = GetModuleBaseNameW(handle, h_mod, exe_buf.as_mut_slice());
let mut pos = 0;
for x in exe_buf.iter() {
@ -460,7 +457,7 @@ macro_rules! impl_RtlUserProcessParameters {
fn get_environ(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str> {
let ptr = self.Environment;
unsafe {
let size = get_region_size(handle, ptr as LPVOID)?;
let size = get_region_size(handle, ptr as _)?;
get_process_data(handle, ptr as _, size as _)
}
}
@ -483,30 +480,41 @@ unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String {
#[allow(clippy::uninit_vec)]
unsafe fn get_process_data(
handle: HANDLE,
ptr: LPVOID,
ptr: *const c_void,
size: usize,
) -> Result<Vec<u16>, &'static str> {
let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1);
buffer.set_len(size / 2);
let mut bytes_read = 0;
if ReadProcessMemory(
handle,
ptr as *mut _,
buffer.as_mut_ptr() as *mut _,
ptr,
buffer.as_mut_ptr().cast(),
size,
std::ptr::null_mut(),
) != TRUE
Some(&mut bytes_read),
)
.is_err()
{
return Err("Unable to read process data");
}
// Documentation states that the function fails if not all data is accessible.
if bytes_read != size {
return Err("ReadProcessMemory returned unexpected number of bytes read");
}
buffer.set_len(size / 2);
buffer.push(0);
Ok(buffer)
}
unsafe fn get_region_size(handle: HANDLE, ptr: LPVOID) -> Result<usize, &'static str> {
unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result<usize, &'static str> {
let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit();
if VirtualQueryEx(
handle,
ptr,
meminfo.as_mut_ptr() as *mut _,
Some(ptr),
meminfo.as_mut_ptr().cast(),
size_of::<MEMORY_BASIC_INFORMATION>(),
) == 0
{
@ -521,46 +529,51 @@ unsafe fn ph_query_process_variable_size(
process_handle: HANDLE,
process_information_class: PROCESSINFOCLASS,
) -> Option<Vec<u16>> {
let mut return_length = MaybeUninit::<ULONG>::uninit();
let mut return_length = MaybeUninit::<u32>::uninit();
let mut status = NtQueryInformationProcess(
if let Err(err) = NtQueryInformationProcess(
process_handle,
process_information_class,
std::ptr::null_mut(),
0,
return_length.as_mut_ptr() as *mut _,
);
if status != STATUS_BUFFER_OVERFLOW
&& status != STATUS_BUFFER_TOO_SMALL
&& status != STATUS_INFO_LENGTH_MISMATCH
)
.ok()
{
return None;
if ![
STATUS_BUFFER_OVERFLOW.into(),
STATUS_BUFFER_TOO_SMALL.into(),
STATUS_INFO_LENGTH_MISMATCH.into(),
]
.contains(&err.code())
{
return None;
}
}
let mut return_length = return_length.assume_init();
let buf_len = (return_length as usize) / 2;
let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1);
buffer.set_len(buf_len);
status = NtQueryInformationProcess(
if NtQueryInformationProcess(
process_handle,
process_information_class,
buffer.as_mut_ptr() as *mut _,
return_length,
&mut return_length as *mut _,
);
if !NT_SUCCESS(status) {
)
.is_err()
{
return None;
}
buffer.set_len(buf_len);
buffer.push(0);
Some(buffer)
}
unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> {
unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec<String> {
// Get argc and argv from the command line
let mut argc = MaybeUninit::<i32>::uninit();
let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr());
let argv_p = CommandLineToArgvW(buffer, argc.as_mut_ptr());
if argv_p.is_null() {
return Vec::new();
}
@ -569,12 +582,10 @@ unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> {
let mut res = Vec::new();
for arg in argv {
let len = libc::wcslen(*arg);
let str_slice = std::slice::from_raw_parts(*arg, len);
res.push(String::from_utf16_lossy(str_slice));
res.push(String::from_utf16_lossy(arg.as_wide()));
}
winapi::um::winbase::LocalFree(argv_p as *mut _);
let _err = LocalFree(HLOCAL(argv_p as _));
res
}
@ -587,15 +598,16 @@ unsafe fn get_process_params(
}
// First check if target process is running in wow64 compatibility emulator
let mut pwow32info = MaybeUninit::<LPVOID>::uninit();
let result = NtQueryInformationProcess(
let mut pwow32info = MaybeUninit::<*const c_void>::uninit();
if NtQueryInformationProcess(
handle,
ProcessWow64Information,
pwow32info.as_mut_ptr() as *mut _,
size_of::<LPVOID>() as u32,
pwow32info.as_mut_ptr().cast(),
size_of::<*const c_void>() as u32,
null_mut(),
);
if !NT_SUCCESS(result) {
)
.is_err()
{
return Err("Unable to check WOW64 information about the process");
}
let pwow32info = pwow32info.assume_init();
@ -604,14 +616,15 @@ unsafe fn get_process_params(
// target is a 64 bit process
let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit();
let result = NtQueryInformationProcess(
if NtQueryInformationProcess(
handle,
ProcessBasicInformation,
pbasicinfo.as_mut_ptr() as *mut _,
pbasicinfo.as_mut_ptr().cast(),
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
null_mut(),
);
if !NT_SUCCESS(result) {
)
.is_err()
{
return Err("Unable to get basic process information");
}
let pinfo = pbasicinfo.assume_init();
@ -619,11 +632,12 @@ unsafe fn get_process_params(
let mut peb = MaybeUninit::<PEB>::uninit();
if ReadProcessMemory(
handle,
pinfo.PebBaseAddress as *mut _,
peb.as_mut_ptr() as *mut _,
size_of::<PEB>() as SIZE_T,
std::ptr::null_mut(),
) != TRUE
pinfo.PebBaseAddress.cast(),
peb.as_mut_ptr().cast(),
size_of::<PEB>(),
None,
)
.is_err()
{
return Err("Unable to read process PEB");
}
@ -633,11 +647,12 @@ unsafe fn get_process_params(
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit();
if ReadProcessMemory(
handle,
peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _,
proc_params.as_mut_ptr() as *mut _,
size_of::<RTL_USER_PROCESS_PARAMETERS>() as SIZE_T,
std::ptr::null_mut(),
) != TRUE
peb.ProcessParameters.cast(),
proc_params.as_mut_ptr().cast(),
size_of::<RTL_USER_PROCESS_PARAMETERS>(),
None,
)
.is_err()
{
return Err("Unable to read process parameters");
}
@ -655,10 +670,11 @@ unsafe fn get_process_params(
if ReadProcessMemory(
handle,
pwow32info,
peb32.as_mut_ptr() as *mut _,
size_of::<PEB32>() as SIZE_T,
std::ptr::null_mut(),
) != TRUE
peb32.as_mut_ptr().cast(),
size_of::<PEB32>(),
None,
)
.is_err()
{
return Err("Unable to read PEB32");
}
@ -667,11 +683,12 @@ unsafe fn get_process_params(
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit();
if ReadProcessMemory(
handle,
peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _,
proc_params.as_mut_ptr() as *mut _,
size_of::<RTL_USER_PROCESS_PARAMETERS32>() as SIZE_T,
std::ptr::null_mut(),
) != TRUE
peb32.ProcessParameters as *mut _,
proc_params.as_mut_ptr().cast(),
size_of::<RTL_USER_PROCESS_PARAMETERS32>(),
None,
)
.is_err()
{
return Err("Unable to read 32 bit process parameters");
}
@ -683,13 +700,11 @@ unsafe fn get_process_params(
))
}
static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| {
let mut version_info: RTL_OSVERSIONINFOEXW = unsafe { MaybeUninit::zeroed().assume_init() };
static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| unsafe {
let mut version_info: OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init();
version_info.dwOSVersionInfoSize = std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32;
if !NT_SUCCESS(unsafe {
RtlGetVersion(&mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _)
}) {
version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
if RtlGetVersion((&mut version_info as *mut OSVERSIONINFOEXW).cast()).is_err() {
return true;
}
@ -713,16 +728,16 @@ fn get_cmd_line_new(handle: HANDLE) -> Vec<String> {
{
let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer;
get_cmdline_from_buffer(buffer)
get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr()))
} else {
vec![]
Vec::new()
}
}
}
fn get_cmd_line_old<T: RtlUserProcessParameters>(params: &T, handle: HANDLE) -> Vec<String> {
match params.get_cmdline(handle) {
Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) },
Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) },
Err(_e) => Vec::new(),
}
}
@ -769,7 +784,7 @@ fn get_io(handle: HANDLE) -> Option<(u64, u64)> {
let mut io: IO_COUNTERS = zeroed();
let ret = GetProcessIoCounters(handle, &mut io);
if ret != 0 {
if ret.is_ok() {
Some((io.ReadTransferCount, io.WriteTransferCount))
} else {
None
@ -790,7 +805,7 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
let mut token: HANDLE = zeroed();
let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
if ret == 0 {
if ret.is_err() {
return None;
}
@ -798,7 +813,7 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
let _ = GetTokenInformation(
token,
TokenUser,
ptr::null::<c_void>() as *mut c_void,
Some(ptr::null::<c_void>() as *mut c_void),
0,
&mut cb_needed,
);
@ -808,13 +823,13 @@ fn get_user(handle: HANDLE) -> Option<SidName> {
let ret = GetTokenInformation(
token,
TokenUser,
buf.as_mut_ptr() as *mut c_void,
Some(buf.as_mut_ptr() as *mut c_void),
cb_needed,
&mut cb_needed,
);
buf.set_len(cb_needed as usize);
if ret == 0 {
if ret.is_err() {
return None;
}
@ -843,7 +858,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
let mut token: HANDLE = zeroed();
let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token);
if ret == 0 {
if ret.is_err() {
return None;
}
@ -851,7 +866,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
let _ = GetTokenInformation(
token,
TokenGroups,
ptr::null::<c_void>() as *mut c_void,
Some(ptr::null::<c_void>() as *mut c_void),
0,
&mut cb_needed,
);
@ -861,13 +876,13 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
let ret = GetTokenInformation(
token,
TokenGroups,
buf.as_mut_ptr() as *mut c_void,
Some(buf.as_mut_ptr() as *mut c_void),
cb_needed,
&mut cb_needed,
);
buf.set_len(cb_needed as usize);
if ret == 0 {
if ret.is_err() {
return None;
}
@ -901,7 +916,7 @@ fn get_groups(handle: HANDLE) -> Option<Vec<SidName>> {
fn get_sid(psid: PSID) -> Vec<u64> {
unsafe {
let mut ret = Vec::new();
let psid = psid as *const SID;
let psid = psid.0 as *const SID;
let mut ia = 0;
ia |= u64::from((*psid).IdentifierAuthority.Value[0]) << 40;
@ -924,7 +939,7 @@ fn get_sid(psid: PSID) -> Vec<u64> {
}
thread_local!(
pub static NAME_CACHE: RefCell<HashMap<PSID, Option<(String, String)>>> =
pub static NAME_CACHE: RefCell<HashMap<*mut c_void, Option<(String, String)>>> =
RefCell::new(HashMap::new());
);
@ -932,11 +947,11 @@ thread_local!(
fn get_name_cached(psid: PSID) -> Option<(String, String)> {
NAME_CACHE.with(|c| {
let mut c = c.borrow_mut();
if let Some(x) = c.get(&psid) {
if let Some(x) = c.get(&psid.0) {
x.clone()
} else {
let x = get_name(psid);
c.insert(psid, x.clone());
c.insert(psid.0, x.clone());
x
}
})
@ -947,13 +962,13 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
unsafe {
let mut cc_name = 0;
let mut cc_domainname = 0;
let mut pe_use = 0;
let mut pe_use = SID_NAME_USE::default();
let _ = LookupAccountSidW(
ptr::null::<u16>() as *mut u16,
PCWSTR::null(),
psid,
ptr::null::<u16>() as *mut u16,
PWSTR::null(),
&mut cc_name,
ptr::null::<u16>() as *mut u16,
PWSTR::null(),
&mut cc_domainname,
&mut pe_use,
);
@ -966,17 +981,17 @@ fn get_name(psid: PSID) -> Option<(String, String)> {
let mut domainname: Vec<u16> = Vec::with_capacity(cc_domainname as usize);
name.set_len(cc_name as usize);
domainname.set_len(cc_domainname as usize);
let ret = LookupAccountSidW(
ptr::null::<u16>() as *mut u16,
if LookupAccountSidW(
PCWSTR::null(),
psid,
name.as_mut_ptr(),
PWSTR::from_raw(name.as_mut_ptr()),
&mut cc_name,
domainname.as_mut_ptr(),
PWSTR::from_raw(domainname.as_mut_ptr()),
&mut cc_domainname,
&mut pe_use,
);
if ret == 0 {
)
.is_err()
{
return None;
}

View File

@ -45,7 +45,7 @@ pub fn pipeline(commands: &str) -> String {
}
pub fn nu_repl_code(source_lines: &[&str]) -> String {
let mut out = String::from("nu --testbin=nu_repl [ ");
let mut out = String::from("nu --testbin=nu_repl ...[ ");
for line in source_lines.iter() {
// convert each "line" to really be a single line to prevent nu! macro joining the newlines

View File

@ -14,5 +14,5 @@ nu-protocol = { path = "../nu-protocol", version = "0.88.2", features = ["plugin
indexmap = "2.1"
eml-parser = "0.1"
ical = "0.8"
ical = "0.9"
rust-ini = "0.20.0"

View File

@ -16,4 +16,4 @@ profile = "default"
# use in nushell, we may opt to use the bleeding edge stable version of rust.
# I believe rust is on a 6 week release cycle and nushell is on a 4 week release cycle.
# So, every two nushell releases, this version number should be bumped by one.
channel = "1.72.1"
channel = "1.73.0"

View File

@ -99,8 +99,8 @@ fn known_external_misc_values() -> TestResult {
run_test(
r#"
let x = 'abc'
extern echo []
echo $x [ a b c ]
extern echo [...args]
echo $x ...[ a b c ]
"#,
"abc a b c",
)

View File

@ -763,3 +763,14 @@ fn properly_typecheck_rest_param() -> TestResult {
fn implied_collect_has_compatible_type() -> TestResult {
run_test(r#"let idx = 3 | $in; $idx < 1"#, "false")
}
#[test]
fn record_expected_colon() -> TestResult {
fail_test(r#"{ a: 2 b }"#, "expected ':'")?;
fail_test(r#"{ a: 2 b 3 }"#, "expected ':'")
}
#[test]
fn record_missing_value() -> TestResult {
fail_test(r#"{ a: 2 b: }"#, "expected value for record field")
}

View File

@ -1,4 +1,5 @@
use crate::tests::{fail_test, run_test, TestResult};
use nu_test_support::nu;
#[test]
fn spread_in_list() -> TestResult {
@ -24,30 +25,6 @@ fn spread_in_list() -> TestResult {
)
}
#[test]
fn const_spread_in_list() -> TestResult {
run_test(r#"const x = [...[]]; $x | to nuon"#, "[]").unwrap();
run_test(
r#"const x = [1 2 ...[[3] {x: 1}] 5]; $x | to nuon"#,
"[1, 2, [3], {x: 1}, 5]",
)
.unwrap();
run_test(
r#"const x = [...([f o o]) 10]; $x | to nuon"#,
"[f, o, o, 10]",
)
.unwrap();
run_test(
r#"const l = [1, 2, [3]]; const x = [...$l $l]; $x | to nuon"#,
"[1, 2, [3], [1, 2, [3]]]",
)
.unwrap();
run_test(
r#"[ ...[ ...[ ...[ a ] b ] c ] d ] | to nuon"#,
"[a, b, c, d]",
)
}
#[test]
fn not_spread() -> TestResult {
run_test(r#"def ... [x] { $x }; ... ..."#, "...").unwrap();
@ -95,15 +72,6 @@ fn spread_in_record() -> TestResult {
)
}
#[test]
fn const_spread_in_record() -> TestResult {
run_test(r#"const x = {...{...{...{}}}}; $x | to nuon"#, "{}").unwrap();
run_test(
r#"const x = {foo: bar ...{a: {x: 1}} b: 3}; $x | to nuon"#,
"{foo: bar, a: {x: 1}, b: 3}",
)
}
#[test]
fn duplicate_cols() -> TestResult {
fail_test(r#"{a: 1, ...{a: 3}}"#, "column used twice").unwrap();
@ -111,16 +79,6 @@ fn duplicate_cols() -> TestResult {
fail_test(r#"{...{a: 0, x: 2}, ...{x: 5}}"#, "column used twice")
}
#[test]
fn const_duplicate_cols() -> TestResult {
fail_test(r#"const _ = {a: 1, ...{a: 3}}"#, "column used twice").unwrap();
fail_test(r#"const _ = {...{a: 4, x: 3}, x: 1}"#, "column used twice").unwrap();
fail_test(
r#"const _ = {...{a: 0, x: 2}, ...{x: 5}}"#,
"column used twice",
)
}
#[test]
fn bad_spread_on_non_record() -> TestResult {
fail_test(r#"let x = 5; { ...$x }"#, "cannot spread").unwrap();
@ -139,3 +97,96 @@ fn spread_type_record() -> TestResult {
"type_mismatch",
)
}
#[test]
fn spread_external_args() {
assert_eq!(
nu!(r#"nu --testbin cococo ...[1 "foo"] 2 ...[3 "bar"]"#).out,
"1 foo 2 3 bar",
);
// exec doesn't have rest parameters but allows unknown arguments
assert_eq!(
nu!(r#"exec nu --testbin cococo "foo" ...[5 6]"#).out,
"foo 5 6"
);
}
#[test]
fn spread_internal_args() -> TestResult {
run_test(
r#"
let list = ["foo" 4]
def f [a b c? d? ...x] { [$a $b $c $d $x] | to nuon }
f 1 2 ...[5 6] 7 ...$list"#,
"[1, 2, null, null, [5, 6, 7, foo, 4]]",
)
.unwrap();
run_test(
r#"
def f [a b c? d? ...x] { [$a $b $c $d $x] | to nuon }
f 1 2 3 ...[5 6]"#,
"[1, 2, 3, null, [5, 6]]",
)
.unwrap();
run_test(
r#"
def f [--flag: int ...x] { [$flag $x] | to nuon }
f 2 ...[foo] 4 --flag 5 6 ...[7 8]"#,
"[5, [2, foo, 4, 6, 7, 8]]",
)
.unwrap();
run_test(
r#"
def f [a b? --flag: int ...x] { [$a $b $flag $x] | to nuon }
f 1 ...[foo] 4 --flag 5 6 ...[7 8]"#,
"[1, null, 5, [foo, 4, 6, 7, 8]]",
)
}
#[test]
fn bad_spread_internal_args() -> TestResult {
fail_test(
r#"
def f [a b c? d? ...x] { echo $a $b $c $d $x }
f 1 ...[5 6]"#,
"Missing required positional argument",
)
.unwrap();
fail_test(
r#"
def f [a b?] { echo a b c d }
f ...[5 6]"#,
"unexpected spread argument",
)
}
#[test]
fn spread_non_list_args() {
fail_test(r#"echo ...(1)"#, "cannot spread value").unwrap();
assert!(nu!(r#"nu --testbin cococo ...(1)"#)
.err
.contains("cannot spread value"));
}
#[test]
fn spread_args_type() -> TestResult {
fail_test(r#"def f [...x: int] {}; f ...["abc"]"#, "expected int")
}
#[test]
fn explain_spread_args() -> TestResult {
run_test(
r#"(explain { || echo ...[1 2] }).cmd_args.0 | select arg_type name type | to nuon"#,
r#"[[arg_type, name, type]; [spread, "[1 2]", list<int>]]"#,
)
}
#[test]
fn deprecate_implicit_spread_for_externals() {
// TODO: When automatic spreading is removed, test that list literals fail at parse time
let result = nu!(r#"nu --testbin cococo [1 2]"#);
assert!(result
.err
.contains("Automatically spreading lists is deprecated"));
assert_eq!(result.out, "1 2");
}

View File

@ -304,6 +304,19 @@ fn const_captures_work() {
assert_eq!(actual.out, "xy");
}
#[test]
fn const_captures_in_closures_work() {
let module = "module foo {
const a = 'world'
export def bar [] {
'hello ' + $a
}
}";
let inp = &[module, "use foo", "do { foo bar }"];
let actual = nu!(&inp.join("; "));
assert_eq!(actual.out, "hello world");
}
#[ignore = "TODO: Need to fix `overlay hide` to hide the constants brough by `overlay use`"]
#[test]
fn complex_const_overlay_use_hide() {

View File

@ -85,7 +85,7 @@ fn execute_binary_in_string() {
#[test]
fn single_quote_dollar_external() {
let actual = nu!("let author = 'JT'; ^echo $'foo=($author)'");
let actual = nu!("let author = 'JT'; nu --testbin cococo $'foo=($author)'");
assert_eq!(actual.out, "foo=JT");
}
@ -354,7 +354,7 @@ mod nu_commands {
#[test]
fn command_list_arg_test() {
let actual = nu!("
nu ['-c' 'version']
nu ...['-c' 'version']
");
assert!(actual.out.contains("version"));
@ -365,7 +365,7 @@ mod nu_commands {
#[test]
fn command_cell_path_arg_test() {
let actual = nu!("
nu ([ '-c' 'version' ])
nu ...([ '-c' 'version' ])
");
assert!(actual.out.contains("version"));
@ -436,7 +436,7 @@ mod external_command_arguments {
let actual = nu!(
cwd: dirs.test(), pipeline(
"
nu --testbin cococo (ls | get name)
nu --testbin cococo ...(ls | get name)
"
));
@ -493,18 +493,16 @@ mod external_command_arguments {
)
}
#[cfg(not(windows))]
#[test]
fn semicolons_are_sanitized_before_passing_to_subshell() {
let actual = nu!("^echo \"a;b\"");
let actual = nu!("nu --testbin cococo \"a;b\"");
assert_eq!(actual.out, "a;b");
}
#[cfg(not(windows))]
#[test]
fn ampersands_are_sanitized_before_passing_to_subshell() {
let actual = nu!("^echo \"a&b\"");
let actual = nu!("nu --testbin cococo \"a&b\"");
assert_eq!(actual.out, "a&b");
}